Repository: aeon-php/calendar Branch: 1.x Commit: fe51e9e0e676 Files: 90 Total size: 465.9 KB Directory structure: gitextract_0agqa1a6/ ├── .gitattributes ├── .github/ │ └── workflows/ │ └── readonly.yaml ├── .gitignore ├── README.md ├── benchmark/ │ └── Aeon/ │ └── Calendar/ │ └── Benchmark/ │ └── Gregorian/ │ ├── DateTimeFormatBench.php │ ├── DateTimeInitBench.php │ ├── DateTimeUnixTimestampBench.php │ ├── DayIterationBench.php │ ├── TimeUnitInitBench.php │ └── TimeZoneInitBench.php ├── composer.json ├── phpbench.json ├── src/ │ └── Aeon/ │ ├── Calculator/ │ │ ├── BCMathCalculator.php │ │ ├── Calculator.php │ │ ├── Exception/ │ │ │ ├── Exception.php │ │ │ └── InvalidTypeException.php │ │ ├── PHPCalculator.php │ │ └── PreciseCalculator.php │ └── Calendar/ │ ├── Exception/ │ │ ├── Exception.php │ │ └── InvalidArgumentException.php │ ├── Gregorian/ │ │ ├── Calendar.php │ │ ├── DateTime.php │ │ ├── DateTimeIntervalIterator.php │ │ ├── DateTimeIterator.php │ │ ├── Day/ │ │ │ └── WeekDay.php │ │ ├── Day.php │ │ ├── Days.php │ │ ├── DaysIterator.php │ │ ├── GregorianCalendar.php │ │ ├── GregorianCalendarStub.php │ │ ├── Interval.php │ │ ├── LeapSecond.php │ │ ├── LeapSeconds.php │ │ ├── Month.php │ │ ├── MonthDays.php │ │ ├── Months.php │ │ ├── MonthsIterator.php │ │ ├── Psr20CalendarAdapter.php │ │ ├── Quarter.php │ │ ├── Time.php │ │ ├── TimeEpoch.php │ │ ├── TimePeriod.php │ │ ├── TimePeriods.php │ │ ├── TimePeriodsIterator.php │ │ ├── TimePeriodsSort.php │ │ ├── TimeZone/ │ │ │ └── TimeOffset.php │ │ ├── TimeZone.php │ │ ├── Year.php │ │ ├── YearMonths.php │ │ └── Years.php │ ├── RelativeTimeUnit.php │ ├── Stopwatch.php │ ├── TimeUnit/ │ │ └── HRTime.php │ ├── TimeUnit.php │ └── Unit.php └── tests/ └── Aeon/ ├── Calculator/ │ └── Tests/ │ └── Unit/ │ ├── BCMathCalculatorTest.php │ ├── PHPCalculatorTest.php │ └── PreciseCalculatorTest.php └── Calendar/ └── Tests/ ├── Functional/ │ └── Gregorian/ │ └── GregorianCalendarTest.php └── Unit/ ├── Gregorian/ │ ├── DateTimeIntervalIteratorTest.php │ ├── DateTimeIteratorTest.php │ ├── DateTimeTest.php │ ├── Day/ │ │ └── WeekDayTest.php │ ├── DayTest.php │ ├── DaysIteratorTest.php │ ├── DaysTest.php │ ├── LeapSecondTest.php │ ├── LeapSecondsTest.php │ ├── MonthDaysTest.php │ ├── MonthTest.php │ ├── MonthsIteratorTest.php │ ├── MonthsTest.php │ ├── Psr20ClockAdapterTest.php │ ├── QuarterTest.php │ ├── TimeEpochTest.php │ ├── TimePeriodTest.php │ ├── TimePeriodsIteratorTest.php │ ├── TimePeriodsSortTest.php │ ├── TimePeriodsTest.php │ ├── TimeTest.php │ ├── TimeZone/ │ │ └── TimeOffsetTest.php │ ├── TimeZoneTest.php │ ├── YearMonthsTest.php │ ├── YearTest.php │ └── YearsTest.php ├── IntervalTest.php ├── RelativeTimeUnitTest.php ├── StopwatchTest.php ├── TimeUnit/ │ └── HRTimeTest.php └── TimeUnitTest.php ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ *.php text eol=lf *.phpt text eol=lf /composer.lock export-ignore /tools/ export-ignore /.github/ export-ignore /docs export-ignore /tests export-ignore /.phive export-ignore /benchmark export-ignore /.gitattributes export-ignore /.gitignore export-ignore /.php_cs export-ignore /phpstan.neon export-ignore /phpunit.xml export-ignore /psalm.xml export-ignore /infection.json export-ignore /phpbench.json export-ignore ================================================ FILE: .github/workflows/readonly.yaml ================================================ name: Readonly on: pull_request_target: types: [opened] jobs: run: runs-on: ubuntu-latest steps: - uses: superbrothers/close-pull-request@v3 with: comment: | Hi, thank you for your contribution. Unfortunately, this repository is read-only. It's a split from our main monorepo repository. In order to proceed with this PR please open it against https://github.com/aeon-php/aeon repository. Thank you. ================================================ FILE: .gitignore ================================================ vendor *.cache var ================================================ FILE: README.md ================================================ # Aeon [![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.4-8892BF.svg)](https://php.net/) [![Latest Stable Version](https://poser.pugx.org/aeon-php/calendar/v)](https://packagist.org/packages/aeon-php/calendar) [![Latest Unstable Version](https://poser.pugx.org/aeon-php/calendar/v/unstable)](https://packagist.org/packages/aeon-php/calendar) [![License](https://poser.pugx.org/aeon-php/calendar/license)](https://packagist.org/packages/aeon-php/calendar) ![Tests](https://github.com/aeon-php/calendar/workflows/Tests/badge.svg?branch=1.x) [![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Faeon-php%2Fcalendar%2F1.x)](https://dashboard.stryker-mutator.io/reports/github.com/aeon-php/calendar/1.x) Time Management Framework for PHP > The word aeon /ˈiːɒn/, also spelled eon (in American English), originally meant "life", "vital force" or "being", > "generation" or "a period of time", though it tended to be translated as "age" in the sense of "ages", "forever", > "timeless" or "for eternity". [Source: Wikipedia](https://en.wikipedia.org/wiki/Aeon) Aeon is a set of libraries that makes easier to work with PHP Date & Time in elegant Object Oriented way. * [Documentation & Examples](https://aeon-php.org/docs/calendar/) * [Contributing & Development](https://github.com/aeon-php/.github/blob/master/CONTRIBUTING.md) * [Community](https://github.com/orgs/aeon-php/discussions) ================================================ FILE: benchmark/Aeon/Calendar/Benchmark/Gregorian/DateTimeFormatBench.php ================================================ aeonDateTime = DateTime::fromString('2020-01-01 00:00:00'); $this->dateTime = new \DateTimeImmutable('2020-01-01 00:00:00'); } public function bench_aeon_format() : void { $this->aeonDateTime->format('Y-m-d H:i:s.u P'); } public function bench_php_format() : void { $this->dateTime->format('Y-m-d H:i:s.u P'); } } ================================================ FILE: benchmark/Aeon/Calendar/Benchmark/Gregorian/DateTimeInitBench.php ================================================ aeonDateTime = DateTime::fromString('2020-01-01 00:00:00'); $this->dateTime = new \DateTimeImmutable('2020-01-01 00:00:00'); } public function bench_aeon_unix_timestamp() : void { $this->aeonDateTime->timestampUNIX(); } public function bench_php_unix_timestamp() : void { $this->dateTime->getTimestamp(); } } ================================================ FILE: benchmark/Aeon/Calendar/Benchmark/Gregorian/DayIterationBench.php ================================================ currentDay(); $start = $end->subMonths(6); $days = []; foreach ($start->until($end, Interval::rightOpen())->all() as $nextDay) { $days[] = $nextDay->format('Y-m-d'); } } public function bench_php_iteration_over_last_half_of_the_year() : void { \date_default_timezone_set('UTC'); $end = new \DateTime('today'); $start = new \DateTime('-6 months'); $interval = \DateInterval::createFromDateString('1 day'); $period = new \DatePeriod($start, $interval, $end); $days = []; foreach ($period as $nextDay) { $days[] = $nextDay->format('Y-m-d'); } } } ================================================ FILE: benchmark/Aeon/Calendar/Benchmark/Gregorian/TimeUnitInitBench.php ================================================ precision = $precision; } /** * @psalm-pure */ public static function supported() : bool { return \extension_loaded('bcmath'); } public function precision() : int { return $this->precision; } /** * @psalm-suppress InvalidNullableReturnType * @psalm-suppress NullableReturnStatement */ public function divide(string $value, string $divisor) : string { if (!\is_numeric($value) || !\is_numeric($divisor)) { throw new InvalidTypeException('Expected values to be numeric string'); } if (\floatval($divisor) === \floatval('0')) { throw new \LogicException("Divisor can't be 0"); } return \bcdiv($value, $divisor, $this->precision); } /** * @psalm-suppress InvalidNullableReturnType * @psalm-suppress NullableReturnStatement */ public function modulo(string $value, string $divisor) : string { if (!\is_numeric($value) || !\is_numeric($divisor)) { throw new InvalidTypeException('Expected values to be numeric string'); } if (\floatval($divisor) === \floatval('0')) { throw new \LogicException("Divisor can't be 0"); } return \bcmod($value, $divisor, $this->precision); } public function multiply(string $value, string $multiplier) : string { if (!\is_numeric($value) || !\is_numeric($multiplier)) { throw new InvalidTypeException('Expected values to be numeric string'); } return \bcmul($value, $multiplier, $this->precision); } public function add(string $value, string $nextValue) : string { if (!\is_numeric($value) || !\is_numeric($nextValue)) { throw new InvalidTypeException('Expected values to be numeric string'); } return \bcadd($value, $nextValue, $this->precision); } public function sub(string $value, string $nextValue) : string { if (!\is_numeric($value) || !\is_numeric($nextValue)) { throw new InvalidTypeException('Expected values to be numeric string'); } return \bcsub($value, $nextValue, $this->precision); } public function isGreaterThan(string $value, string $nextValue) : bool { if (!\is_numeric($value) || !\is_numeric($nextValue)) { throw new InvalidTypeException('Expected values to be numeric string'); } return \bccomp($value, $nextValue, $this->precision) === 1; } /** * @infection-ignore-all * * @deprecated Use `isGreaterThanOrEqualTo` instead. Will be removed with 2.0 */ public function isGreaterThanEq(string $value, string $nextValue) : bool { return $this->isGreaterThanOrEqualTo($value, $nextValue); } public function isGreaterThanOrEqualTo(string $value, string $nextValue) : bool { if (!\is_numeric($value) || !\is_numeric($nextValue)) { throw new InvalidTypeException('Expected values to be numeric string'); } return \in_array(\bccomp($value, $nextValue, $this->precision), [0, 1], true); } public function isLessThan(string $value, string $nextValue) : bool { if (!\is_numeric($value) || !\is_numeric($nextValue)) { throw new InvalidTypeException('Expected values to be numeric string'); } return \bccomp($value, $nextValue, $this->precision) === -1; } /** * @infection-ignore-all * * @deprecated Use `isLessThanOrEqualTo` instead. Will be removed with 2.0 */ public function isLessThanEq(string $value, string $nextValue) : bool { return $this->isLessThanOrEqualTo($value, $nextValue); } public function isLessThanOrEqualTo(string $value, string $nextValue) : bool { if (!\is_numeric($value) || !\is_numeric($nextValue)) { throw new InvalidTypeException('Expected values to be numeric string'); } return \in_array(\bccomp($value, $nextValue, $this->precision), [-1, 0], true); } /** * @infection-ignore-all * * @deprecated Use `isEqualTo` instead. Will be removed with 2.0 */ public function isEqual(string $value, string $nextValue) : bool { return $this->isEqualTo($value, $nextValue); } public function isEqualTo(string $value, string $nextValue) : bool { if (!\is_numeric($value) || !\is_numeric($nextValue)) { throw new InvalidTypeException('Expected values to be numeric string'); } return \bccomp($value, $nextValue, $this->precision) === 0; } } ================================================ FILE: src/Aeon/Calculator/Calculator.php ================================================ precision = $precision; } public function precision() : int { return $this->precision; } public function divide(string $value, string $divisor) : string { return \number_format(\floatval($value) / \floatval($divisor), $this->precision, '.', ''); } public function modulo(string $value, string $divisor) : string { return \number_format(\fmod(\floatval($value), \floatval($divisor)), $this->precision, '.', ''); } public function multiply(string $value, string $multiplier) : string { return \number_format(\floatval($value) * \floatval($multiplier), $this->precision, '.', ''); } public function add(string $value, string $nextValue) : string { return \number_format(\floatval($value) + \floatval($nextValue), $this->precision, '.', ''); } public function sub(string $value, string $nextValue) : string { return \number_format(\floatval($value) - \floatval($nextValue), $this->precision, '.', ''); } public function isGreaterThan(string $value, string $nextValue) : bool { return \floatval(\number_format(\floatval($value), $this->precision, '.', '')) > \floatval(\number_format(\floatval($nextValue), $this->precision, '.', '')); } /** * @infection-ignore-all * * @deprecated Use `isGreaterThanOrEqualTo` instead. Will be removed with 2.0 */ public function isGreaterThanEq(string $value, string $nextValue) : bool { return $this->isGreaterThanOrEqualTo($value, $nextValue); } public function isGreaterThanOrEqualTo(string $value, string $nextValue) : bool { return \floatval(\number_format(\floatval($value), $this->precision, '.', '')) >= \floatval(\number_format(\floatval($nextValue), $this->precision, '.', '')); } public function isLessThan(string $value, string $nextValue) : bool { return \floatval(\number_format(\floatval($value), $this->precision, '.', '')) < \floatval(\number_format(\floatval($nextValue), $this->precision, '.', '')); } /** * @infection-ignore-all * * @deprecated Use `isLessThanOrEqualTo` instead. Will be removed with 2.0 */ public function isLessThanEq(string $value, string $nextValue) : bool { return $this->isLessThanOrEqualTo($value, $nextValue); } public function isLessThanOrEqualTo(string $value, string $nextValue) : bool { return \floatval(\number_format(\floatval($value), $this->precision, '.', '')) <= \floatval(\number_format(\floatval($nextValue), $this->precision, '.', '')); } /** * @infection-ignore-all * * @deprecated Use `isEqualTo` instead. Will be removed with 2.0 */ public function isEqual(string $value, string $nextValue) : bool { return $this->isEqualTo($value, $nextValue); } public function isEqualTo(string $value, string $nextValue) : bool { return \floatval(\number_format(\floatval($value), $this->precision, '.', '')) == \floatval(\number_format(\floatval($nextValue), $this->precision, '.', '')); } } ================================================ FILE: src/Aeon/Calculator/PreciseCalculator.php ================================================ precision() === $precision) { return self::$instance; } if (BCMathCalculator::supported()) { self::$instance = new BCMathCalculator($precision); return self::$instance; } // @codeCoverageIgnoreStart self::$instance = new PHPCalculator($precision); return self::$instance; // @codeCoverageIgnoreEnd } } ================================================ FILE: src/Aeon/Calendar/Exception/Exception.php ================================================ day = $day; $this->time = $time; $this->timeZone = $timeZone; } /** * @psalm-pure */ public static function create(int $year, int $month, int $day, int $hour, int $minute, int $second, int $microsecond = 0, string $timezone = 'UTC') : self { return new self( new Day( new Month( new Year($year), $month ), $day ), new Time($hour, $minute, $second, $microsecond), TimeZone::fromString($timezone) ); } /** * @psalm-pure * * @psalm-suppress ImpureMethodCall * @psalm-suppress ImpureStaticProperty * @psalm-suppress PropertyTypeCoercion * @psalm-suppress ImpurePropertyAssignment * @psalm-suppress InaccessibleProperty */ public static function fromDateTime(\DateTimeInterface $dateTime) : self { $day = Day::fromDateTime($dateTime); $time = Time::fromDateTime($dateTime); $timeZone = TimeZone::fromDateTimeZone($dateTime->getTimezone()); return new self( $day, $time, $timeZone, ); } /** * @psalm-pure * * @psalm-suppress ImpureFunctionCall * @psalm-suppress ImpureMethodCall */ public static function fromString(string $date) : self { $currentPHPTimeZone = \date_default_timezone_get(); \date_default_timezone_set('UTC'); try { $dateTime = self::fromDateTime(new \DateTimeImmutable($date)); } catch (\Exception $e) { throw new InvalidArgumentException("Value \"{$date}\" is not valid date time format."); } if ($currentPHPTimeZone !== 'UTC') { \date_default_timezone_set($currentPHPTimeZone); } return $dateTime; } /** * @psalm-pure * * @psalm-suppress ImpureFunctionCall * @psalm-suppress ImpureMethodCall */ public static function fromTimestampUnix(int $timestamp) : self { $currentPHPTimeZone = \date_default_timezone_get(); \date_default_timezone_set('UTC'); $dateTime = self::fromDateTime((new \DateTimeImmutable)->setTimestamp($timestamp)); if ($currentPHPTimeZone !== 'UTC') { \date_default_timezone_set($currentPHPTimeZone); } return $dateTime; } public function __toString() : string { return $this->toISO8601(); } /** * @return array{datetime: string, day: Day, time: Time, timeZone: TimeZone} */ public function __debugInfo() : array { return [ 'datetime' => $this->toISO8601(), 'day' => $this->day, 'time' => $this->time, 'timeZone' => $this->timeZone, ]; } /** * @return array{day: Day, time: Time, timeZone: TimeZone} */ public function __serialize() : array { return [ 'day' => $this->day, 'time' => $this->time, 'timeZone' => $this->timeZone, ]; } /** * @param array{day: Day, time: Time, timeZone: TimeZone} $data */ public function __unserialize(array $data) : void { $this->day = $data['day']; $this->time = $data['time']; $this->timeZone = $data['timeZone']; } public function year() : Year { return $this->month()->year(); } public function month() : Month { return $this->day->month(); } public function day() : Day { return $this->day; } public function time() : Time { return $this->time; } public function setTime(Time $time) : self { return new self( $this->day, $time, $this->timeZone ); } public function setTimeIn(Time $time, TimeZone $timeZone) : self { return (new self( $this->day, $time, $this->timeZone ))->toTimeZone($timeZone) ->setTime($time); } public function setDay(Day $day) : self { return new self( $day, $this->time, $this->timeZone ); } public function toDateTimeImmutable() : \DateTimeImmutable { return new \DateTimeImmutable( \sprintf( '%d-%02d-%02d %02d:%02d:%02d.%06d', $this->day->year()->number(), $this->day->month()->number(), $this->day->number(), $this->time->hour(), $this->time->minute(), $this->time->second(), $this->time->microsecond(), ), $this->timeZone->toDateTimeZone() ); } public function toAtomicTime() : self { return $this->add(LeapSeconds::load()->until($this)->offsetTAI()); } public function toGPSTime() : self { return $this->add(LeapSeconds::load() ->since(TimeEpoch::GPS()->date()) ->until($this)->count()); } public function format(string $format) : string { return $this->toDateTimeImmutable()->format($format); } public function timeZone() : TimeZone { return $this->timeZone; } public function timeZoneAbbreviation() : TimeZone { if ($this->timeZone->isOffset()) { throw new Exception("TimeZone offset {$this->timeZone->name()} can't be converted into abbreviation."); } return TimeZone::fromString($this->toDateTimeImmutable()->format('T')); } public function toTimeZone(TimeZone $dateTimeZone) : self { return self::fromDateTime($this->toDateTimeImmutable()->setTimezone($dateTimeZone->toDateTimeZone())); } public function toISO8601(bool $extended = true) : string { return $extended ? $this->toDateTimeImmutable()->format('Y-m-d\TH:i:sP') : $this->toDateTimeImmutable()->format('Ymd\THisO'); } public function isDaylightSaving() : bool { return (bool) $this->toDateTimeImmutable()->format('I'); } public function isDaylight() : bool { return !$this->isDaylightSaving(); } public function timestamp(TimeEpoch $timeEpoch) : TimeUnit { if ($this->isBefore($timeEpoch->date())) { throw new Exception('Given epoch started at ' . $timeEpoch->date()->toISO8601() . ' which was after ' . $this->toISO8601()); } switch ($timeEpoch->type()) { case TimeEpoch::UTC: return $this->timestampUNIX() ->sub(TimeEpoch::UNIX()->distanceTo($timeEpoch)) ->add(LeapSeconds::load()->offsetTAI()); case TimeEpoch::GPS: return $this->timestampUNIX() ->sub(TimeEpoch::UNIX()->distanceTo($timeEpoch)) ->add( LeapSeconds::load()->offsetTAI() ->sub(LeapSeconds::load()->until($timeEpoch->date())->offsetTAI()) ); case TimeEpoch::TAI: return $timeEpoch->date()->until($this)->distance() ->add($timeEpoch->date()->until($this)->leapSeconds()->offsetTAI()); default: return $this->timestampUNIX(); } } /** * Number of seconds elapsed since Unix epoch started at 1970-01-01 (1970-01-01T00:00:00Z) * Not including leap seconds. */ public function timestampUNIX() : TimeUnit { $timestamp = $this->toDateTimeImmutable()->getTimestamp(); if ($timestamp >= 0) { return TimeUnit::positive($timestamp, $this->time->microsecond()); } return TimeUnit::negative(\abs($timestamp), $this->time->microsecond()); } public function modify(string $modifier) : self { $dateTimeParts = \date_parse($modifier); if (!\is_array($dateTimeParts)) { throw new InvalidArgumentException('Value "{modify}" is not valid relative time format.'); } if ($dateTimeParts['error_count'] > 0) { throw new InvalidArgumentException("Value \"{$modifier}\" is not valid relative time format."); } if (!isset($dateTimeParts['relative']) || !\is_array($dateTimeParts['relative'])) { throw new InvalidArgumentException("Value \"{$modifier}\" is not valid relative time format."); } $dateTime = $this; if ( !\is_int($dateTimeParts['relative']['year']) || !\is_int($dateTimeParts['relative']['month']) || !\is_int($dateTimeParts['relative']['day']) || !\is_int($dateTimeParts['relative']['hour']) || !\is_int($dateTimeParts['relative']['minute']) || !\is_int($dateTimeParts['relative']['second']) ) { throw new InvalidArgumentException("Value \"{$modifier}\" is not valid relative time format."); } if ($dateTimeParts['relative']['year'] !== 0) { $dateTime = $dateTime->add(RelativeTimeUnit::years($dateTimeParts['relative']['year'])); } if ($dateTimeParts['relative']['month'] !== 0) { $dateTime = $dateTime->add(RelativeTimeUnit::months($dateTimeParts['relative']['month'])); } if (\array_key_exists('weekday', $dateTimeParts['relative'])) { if ($dateTimeParts['relative']['weekday'] !== 0) { $dateTimeWeekDay = $dateTime->day()->weekDay(); if ($dateTime->day()->weekDay()->number() < (int) $dateTimeParts['relative']['weekday']) { $dateTime = $dateTime->add(TimeUnit::days((int) $dateTimeParts['relative']['weekday'] - $dateTimeWeekDay->number())); } } } return $dateTime->add( TimeUnit::days($dateTimeParts['relative']['day']) ->add(TimeUnit::hours($dateTimeParts['relative']['hour'])) ->add(TimeUnit::minutes($dateTimeParts['relative']['minute'])) ->add(TimeUnit::seconds($dateTimeParts['relative']['second'])) ); } public function addHour() : self { return $this->add(TimeUnit::hour()); } public function subHour() : self { return $this->sub(TimeUnit::hour()); } public function addHours(int $hours) : self { return $this->add(TimeUnit::hours($hours)); } public function subHours(int $hours) : self { return $this->sub(TimeUnit::hours($hours)); } public function addMinute() : self { return $this->add(TimeUnit::minute()); } public function subMinute() : self { return $this->sub(TimeUnit::minute()); } public function addMinutes(int $minutes) : self { return $this->add(TimeUnit::minutes($minutes)); } public function subMinutes(int $minutes) : self { return $this->sub(TimeUnit::minutes($minutes)); } public function addSecond() : self { return $this->add(TimeUnit::second()); } public function subSecond() : self { return $this->sub(TimeUnit::second()); } public function addSeconds(int $seconds) : self { return $this->add(TimeUnit::seconds($seconds)); } public function subSeconds(int $seconds) : self { return $this->sub(TimeUnit::seconds($seconds)); } public function addDay() : self { return $this->add(TimeUnit::day()); } public function subDay() : self { return $this->sub(TimeUnit::day()); } public function addDays(int $days) : self { return $this->add(TimeUnit::days($days)); } public function subDays(int $days) : self { return $this->sub(TimeUnit::days($days)); } public function addMonth() : self { return $this->add(RelativeTimeUnit::month()); } public function subMonth() : self { return $this->sub(RelativeTimeUnit::month()); } public function addMonths(int $months) : self { return $this->add(RelativeTimeUnit::months($months)); } public function subMonths(int $months) : self { return $this->sub(RelativeTimeUnit::months($months)); } public function addYear() : self { return $this->add(RelativeTimeUnit::year()); } public function subYear() : self { return $this->sub(RelativeTimeUnit::year()); } public function addYears(int $years) : self { return $this->add(RelativeTimeUnit::years($years)); } public function subYears(int $years) : self { return $this->sub(RelativeTimeUnit::years($years)); } public function midnight() : self { return new self( $this->day, new Time(0, 0, 0, 0), $this->timeZone ); } public function noon() : self { return new self( $this->day, new Time(12, 0, 0, 0), $this->timeZone ); } public function endOfDay() : self { return new self( $this->day, new Time(23, 59, 59, 999999), $this->timeZone ); } public function yesterday() : self { return $this->sub(TimeUnit::day())->midnight(); } public function tomorrow() : self { return $this->add(TimeUnit::day())->midnight(); } /** * When adding RelativeTimeUnit::month() this method will try to change month and adjust * day of the month. * Examples: * - 2021-02-28 + RelativeTimeUnit::month() = 2021-03-28. */ public function add(Unit $timeUnit) : self { if ($timeUnit instanceof RelativeTimeUnit && $timeUnit->inMonths()) { $years = $timeUnit->toPositive()->inYears(); $months = $timeUnit->inCalendarMonths(); $newMonth = $timeUnit->isNegative() ? $this->month()->sub($years, $months) : $this->month()->add($years, $months); if ($newMonth->lastDay()->number() < $this->day->number()) { return new self(new Day($newMonth, $newMonth->lastDay()->number()), $this->time, $this->timeZone); } return new self(new Day($newMonth, $this->day->number()), $this->time, $this->timeZone); } return self::fromDateTime($this->toDateTimeImmutable()->add($timeUnit->toDateInterval())); } /** * When subtracting RelativeTimeUnit::month() this method will try to change month and adjust * day of the month. * Examples: * - 2021-03-31 - RelativeTimeUnit::month() = 2021-02-28. */ public function sub(Unit $timeUnit) : self { return $this->add($timeUnit->invert()); } /** * @infection-ignore-all * * @deprecated Use `isEqualTo` instead. Will be removed with 2.0 */ public function isEqual(self $dateTime) : bool { return $this->isEqualTo($dateTime); } public function isEqualTo(self $dateTime) : bool { return $this->toDateTimeImmutable() == $dateTime->toDateTimeImmutable(); } public function isAfter(self $dateTime) : bool { return $this->toDateTimeImmutable() > $dateTime->toDateTimeImmutable(); } /** * @infection-ignore-all * * @deprecated Use `isAfterOrEqualTo` instead. Will be removed with 2.0 */ public function isAfterOrEqual(self $dateTime) : bool { return $this->isAfterOrEqualTo($dateTime); } public function isAfterOrEqualTo(self $dateTime) : bool { return $this->toDateTimeImmutable() >= $dateTime->toDateTimeImmutable(); } /** * @infection-ignore-all * * @deprecated Use `isBeforeOrEqualTo` instead. Will be removed with 2.0 */ public function isBeforeOrEqual(self $dateTime) : bool { return $this->isBeforeOrEqualTo($dateTime); } public function isBeforeOrEqualTo(self $dateTime) : bool { return $this->toDateTimeImmutable() <= $dateTime->toDateTimeImmutable(); } public function isBefore(self $dateTime) : bool { return $this->toDateTimeImmutable() < $dateTime->toDateTimeImmutable(); } public function until(self $dateTime) : TimePeriod { return new TimePeriod($this, $dateTime); } public function since(self $dateTime) : TimePeriod { return new TimePeriod($dateTime, $this); } public function distance(self $dateTime) : TimeUnit { return $this->until($dateTime)->distance(); } public function distanceSince(self $dateTime) : TimeUnit { return $this->since($dateTime)->distance(); } public function distanceUntil(self $dateTime) : TimeUnit { return $this->until($dateTime)->distance(); } public function iterate(self $pointInTime, TimeUnit $by) : TimePeriods { return $pointInTime->isBefore($this) ? $this->since($pointInTime)->iterateBackward($by, Interval::closed()) : $this->until($pointInTime)->iterate($by, Interval::closed()); } public function isAmbiguous() : bool { $tz = $this->timeZone; if ($tz->timeOffset($this)->isUTC()) { return false; } /** * @var array $transitions */ $transitions = $tz->toDateTimeZone()->getTransitions( $this->timestampUNIX()->sub(TimeUnit::hours(1)->add(TimeUnit::minute()))->inSeconds(), $this->timestampUNIX()->add(TimeUnit::hours(1))->inSeconds(), ); if (\count($transitions) === 1) { return false; } if ($transitions[1]['offset'] - $transitions[0]['offset'] === 3600) { return false; } return true; } public function quarter() : Quarter { return $this->year()->quarter((int) \ceil($this->month()->number() / 3)); } public function compareTo(self $dateTime) : int { if ($this->isEqualTo($dateTime)) { return 0; } return $this->isBefore($dateTime) ? -1 : 1; } } ================================================ FILE: src/Aeon/Calendar/Gregorian/DateTimeIntervalIterator.php ================================================ interval = $interval; parent::__construct(new DateTimeIterator($start, $end, $timeUnit)); } public function start() : DateTime { return $this->getInnerIterator()->start(); } public function end() : DateTime { return $this->getInnerIterator()->end(); } public function unit() : Unit { return $this->getInnerIterator()->unit(); } public function isForward() : bool { return $this->getInnerIterator()->isForward(); } public function interval() : Interval { return $this->interval; } public function hasNext() : bool { $dateTimeIterator = clone $this->getInnerIterator(); $dateTimeIterator->next(); if ($dateTimeIterator->isForward()) { if ($this->interval->isRightOpen() && !$dateTimeIterator->hasNext()) { return false; } if ($dateTimeIterator->current()->isAfter($dateTimeIterator->end())) { return false; } } else { if ($this->interval->isLeftOpen() && !$dateTimeIterator->hasNext()) { return false; } if ($dateTimeIterator->current()->isBefore($dateTimeIterator->end())) { return false; } } return true; } public function accept() : bool { $dateTimeIterator = $this->getInnerIterator(); if ($dateTimeIterator->isForward()) { if (($this->interval->isRightOpen() || $this->interval->isOpen()) && !$dateTimeIterator->hasNext()) { return false; } if (($this->interval->isLeftOpen() || $this->interval->isOpen()) && $dateTimeIterator->isFirst()) { return false; } } else { if (($this->interval->isRightOpen() || $this->interval->isOpen()) && $dateTimeIterator->isFirst()) { return false; } if (($this->interval->isLeftOpen() || $this->interval->isOpen()) && !$dateTimeIterator->hasNext()) { return false; } } return true; } public function key() : int { $dateTimeIterator = $this->getInnerIterator(); if ($dateTimeIterator->isForward()) { if ($this->interval->isLeftOpen() || $this->interval->isOpen()) { return $dateTimeIterator->key() - 1; } } else { if ($this->interval->isRightOpen() || $this->interval->isOpen()) { return $dateTimeIterator->key() - 1; } } return $dateTimeIterator->key(); } /** * @psalm-suppress MixedReturnStatement */ public function current() : ?DateTime { /** * @phpstan-ignore-next-line */ return parent::current(); } /** * @psalm-suppress InvalidReturnStatement * @psalm-suppress InvalidReturnType */ public function getInnerIterator() : DateTimeIterator { /** @phpstan-ignore-next-line */ return parent::getInnerIterator(); } } ================================================ FILE: src/Aeon/Calendar/Gregorian/DateTimeIterator.php ================================================ */ final class DateTimeIterator implements \Iterator { private DateTime $currentDate; private DateTime $start; private DateTime $end; private Unit $timeUnit; private int $key; private bool $forward; private bool $empty; /** * @throws InvalidArgumentException */ public function __construct(DateTime $start, DateTime $end, Unit $timeUnit) { $this->start = $start; $this->end = $end; $this->timeUnit = $timeUnit; $this->currentDate = $start; $this->key = 0; $this->forward = $start->isBeforeOrEqualTo($end); $this->empty = $start->isEqualTo($end); if ($this->forward && $timeUnit->isNegative()) { throw new InvalidArgumentException("Forward DateTimeIterator {$start->format('Y-m-d H:i:sP')}...{$end->format('Y-m-d H:i:sP')} requires positive TimeUnit"); } if (!$this->forward && !$timeUnit->isNegative()) { throw new InvalidArgumentException("Backward DateTimeIterator {$start->format('Y-m-d H:i:sP')}...{$end->format('Y-m-d H:i:sP')} requires negative TimeUnit"); } } public function isForward() : bool { return $this->forward; } public function unit() : Unit { return $this->timeUnit; } public function start() : DateTime { return $this->start; } public function end() : DateTime { return $this->end; } public function current() : DateTime { return $this->currentDate; } public function next() : void { $this->currentDate = $this->currentDate->add($this->timeUnit); $this->key++; } public function hasNext() : bool { if ($this->forward) { return !$this->current()->add($this->timeUnit)->isAfter($this->end); } return !$this->current()->add($this->timeUnit)->isBefore($this->end); } public function key() : int { return $this->key; } public function isFirst() : bool { return $this->key() === 0; } public function valid() : bool { if ($this->empty) { return false; } if ($this->forward) { return $this->currentDate->isBeforeOrEqualTo($this->end); } return $this->currentDate->isAfterOrEqualTo($this->end); } public function rewind() : void { $this->currentDate = $this->start; $this->key = 0; } } ================================================ FILE: src/Aeon/Calendar/Gregorian/Day/WeekDay.php ================================================ 'Monday', 2 => 'Tuesday', 3 => 'Wednesday', 4 => 'Thursday', 5 => 'Friday', 6 => 'Saturday', 7 => 'Sunday', ]; private const NAMES_SHORT = [ 1 => 'Mon', 2 => 'Tue', 3 => 'Wed', 4 => 'Thu', 5 => 'Fri', 6 => 'Sat', 7 => 'Sun', ]; private int $number; public function __construct(int $number) { if ($number <= 0 || $number > 7) { throw new InvalidArgumentException('Day number must be greater or equal 1 and less or equal than 7'); } $this->number = $number; } /** * @psalm-pure */ public static function monday() : self { return new self(1); } /** * @psalm-pure */ public static function tuesday() : self { return new self(2); } /** * @psalm-pure */ public static function wednesday() : self { return new self(3); } /** * @psalm-pure */ public static function thursday() : self { return new self(4); } /** * @psalm-pure */ public static function friday() : self { return new self(5); } /** * @psalm-pure */ public static function saturday() : self { return new self(6); } /** * @psalm-pure */ public static function sunday() : self { return new self(7); } public function number() : int { return $this->number; } public function name() : string { return self::NAMES[$this->number]; } public function shortName() : string { return self::NAMES_SHORT[$this->number]; } /** * @infection-ignore-all * * @deprecated Use `isEqualTo` instead. Will be removed with 2.0 */ public function isEqual(self $weekDay) : bool { return $this->isEqualTo($weekDay); } public function isEqualTo(self $weekDay) : bool { return $this->number() === $weekDay->number(); } public function isWeekend() : bool { return \in_array($this->number(), [6, 7], true); } } ================================================ FILE: src/Aeon/Calendar/Gregorian/Day.php ================================================ $month->numberOfDays()) { throw new InvalidArgumentException('Day number must be greater or equal 1 and less or equal than ' . $month->numberOfDays()); } $this->number = $number; $this->month = $month; } /** * @psalm-pure */ public static function create(int $year, int $month, int $day) : self { return new self( new Month( new Year($year), $month ), $day ); } /** * @psalm-pure * * @psalm-suppress PossiblyNullArrayAccess * @psalm-suppress PossiblyNullArgument * @psalm-suppress ImpureMethodCall * @psalm-suppress PossiblyInvalidArgument */ public static function fromDateTime(\DateTimeInterface $dateTime) : self { /** * @phpstan-ignore-next-line */ [$year, $month, $day] = \sscanf($dateTime->format('Y-m-d'), '%d-%d-%d'); /** @phpstan-ignore-next-line */ return new self(new Month(new Year($year), $month), $day, ); } /** * @psalm-pure * * @psalm-suppress ImpureMethodCall */ public static function fromString(string $date) : self { try { return self::fromDateTime(new \DateTimeImmutable($date)); } catch (\Exception $e) { throw new InvalidArgumentException("Value \"{$date}\" is not valid day format."); } } /** * @return array{year: int, month:int, day: int} */ public function __debugInfo() : array { return [ 'year' => $this->month->year()->number(), 'month' => $this->month->number(), 'day' => $this->number, ]; } /** * @return array{month: Month, number: int} */ public function __serialize() : array { return [ 'month' => $this->month, 'number' => $this->number, ]; } /** * @param array{month: Month, number: int} $data */ public function __unserialize(array $data) : void { $this->month = $data['month']; $this->number = $data['number']; } public function toString() : string { return $this->format('Y-m-d'); } public function timeBetween(self $day) : TimeUnit { return TimeUnit::seconds(\abs(($this->toDateTimeImmutable()->getTimestamp() - $day->toDateTimeImmutable()->getTimestamp()))); } /** @deprecated Use `add` instead. Will be removed with 2.0 */ public function plus(int $years, int $months, int $days) : self { return $this->add($years, $months, $days); } public function add(int $years, int $months, int $days) : self { $dateTime = $this->midnight(TimeZone::UTC()); if ($years !== 0) { $dateTime = $dateTime->add(RelativeTimeUnit::years($years)); } if ($months !== 0) { $dateTime = $dateTime->add(RelativeTimeUnit::months($months)); } if ($days !== 0) { $dateTime = $dateTime->add(TimeUnit::days($days)); } return $dateTime->day(); } /** @deprecated Use `sub` instead. Will be removed with 2.0 */ public function minus(int $years, int $months, int $days) : self { return $this->sub($years, $months, $days); } public function sub(int $years, int $months, int $days) : self { $dateTime = $this->midnight(TimeZone::UTC()); if ($years !== 0) { $dateTime = $dateTime->sub(RelativeTimeUnit::years($years)); } if ($months !== 0) { $dateTime = $dateTime->sub(RelativeTimeUnit::months($months)); } if ($days !== 0) { $dateTime = $dateTime->sub(TimeUnit::days($days)); } return $dateTime->day(); } /** * @infection-ignore-all * * @deprecated Use `addDays` instead. Will be removed with 2.0 */ public function plusDays(int $days) : self { return $this->addDays($days); } public function addDays(int $days) : self { return $this->midnight(TimeZone::UTC())->add(TimeUnit::days($days))->day(); } /** * @infection-ignore-all * * @deprecated Use `subDays` instead. Will be removed with 2.0 */ public function minusDays(int $days) : self { return $this->subDays($days); } public function subDays(int $days) : self { return $this->midnight(TimeZone::UTC())->sub(TimeUnit::days($days))->day(); } /** * @infection-ignore-all * * @deprecated Use `addMonths` instead. Will be removed with 2.0 */ public function plusMonths(int $months) : self { return $this->addMonths($months); } public function addMonths(int $months) : self { return $this->midnight(TimeZone::UTC())->add(RelativeTimeUnit::months($months))->day(); } /** * @infection-ignore-all * * @deprecated Use `subMonths` instead. Will be removed with 2.0 */ public function minusMonths(int $months) : self { return $this->subMonths($months); } public function subMonths(int $months) : self { return $this->midnight(TimeZone::UTC())->sub(RelativeTimeUnit::months($months))->day(); } /** * @infection-ignore-all * * @deprecated Use `addYears` instead. Will be removed with 2.0 */ public function plusYears(int $years) : self { return $this->addYears($years); } public function addYears(int $years) : self { return $this->midnight(TimeZone::UTC())->add(RelativeTimeUnit::years($years))->day(); } /** * @infection-ignore-all * * @deprecated Use `subYears` instead. Will be removed with 2.0 */ public function minusYears(int $years) : self { return $this->subYears($years); } public function subYears(int $years) : self { return $this->midnight(TimeZone::UTC())->sub(RelativeTimeUnit::years($years))->day(); } public function previous() : self { return $this->midnight(TimeZone::UTC())->sub(TimeUnit::day())->day(); } public function next() : self { return $this->midnight(TimeZone::UTC())->add(TimeUnit::day())->day(); } public function midnight(TimeZone $timeZone) : DateTime { return new DateTime($this, new Time(0, 0, 0, 0), $timeZone); } public function noon(TimeZone $timeZone) : DateTime { return new DateTime($this, new Time(12, 0, 0, 0), $timeZone); } public function endOfDay(TimeZone $timeZone) : DateTime { return new DateTime($this, new Time(23, 59, 59, 999999), $timeZone); } public function setTime(Time $time, TimeZone $timeZone) : DateTime { return new DateTime( $this, $time, $timeZone ); } public function month() : Month { return $this->month; } public function year() : Year { return $this->month->year(); } public function number() : int { return $this->number; } public function weekDay() : WeekDay { return new WeekDay((int) $this->toDateTimeImmutable()->format('N')); } /** * Week of year starting from 1. */ public function weekOfYear() : int { return (int) $this->toDateTimeImmutable()->format('W'); } /** * Week of year starting from 1. */ public function weekOfMonth() : int { return $this->weekOfYear() - $this->month->days()->first()->weekOfYear() + 1; } /** * Day of year starting from 1. */ public function dayOfYear() : int { return \intval($this->toDateTimeImmutable()->format('z')) + 1; } public function isWeekend() : bool { return $this->weekDay()->isWeekend(); } public function toDateTimeImmutable() : \DateTimeImmutable { return new \DateTimeImmutable(\sprintf( '%d-%d-%d 00:00:00.000000 UTC', $this->month->year()->number(), $this->month->number(), $this->number )); } public function format(string $format) : string { return $this->toDateTimeImmutable()->format($format); } /** * @infection-ignore-all * * @deprecated Use `isEqualTo` instead. Will be removed with 2.0 */ public function isEqual(self $day) : bool { return $this->isEqualTo($day); } public function isEqualTo(self $day) : bool { return $this->number === $day->number() && $this->month->isEqualTo($day->month()); } public function isBefore(self $day) : bool { if ($this->month->isBefore($day->month())) { return true; } if ($this->month->isAfter($day->month())) { return false; } return $this->number < $day->number(); } /** * @infection-ignore-all * * @deprecated Use `isBeforeOrEqualTo` instead. Will be removed with 2.0 */ public function isBeforeOrEqual(self $day) : bool { return $this->isBeforeOrEqualTo($day); } public function isBeforeOrEqualTo(self $day) : bool { if ($this->month->isBefore($day->month())) { return true; } if ($this->month->isAfter($day->month())) { return false; } return $this->number <= $day->number(); } public function isAfter(self $day) : bool { if ($this->month->isAfter($day->month())) { return true; } if ($this->month->isBefore($day->month())) { return false; } return $this->number > $day->number(); } /** * @infection-ignore-all * * @deprecated Use `isAfterOrEqualTo` instead. Will be removed with 2.0 */ public function isAfterOrEqual(self $day) : bool { return $this->isAfterOrEqualTo($day); } public function isAfterOrEqualTo(self $day) : bool { if ($this->month->isAfter($day->month())) { return true; } if ($this->month->isBefore($day->month())) { return false; } return $this->number >= $day->number(); } public function iterate(self $destination, Interval $interval) : Days { return $this->isAfter($destination) ? $this->since($destination, $interval) : $this->until($destination, $interval); } public function until(self $day, Interval $interval) : Days { if ($this->isAfter($day)) { throw new InvalidArgumentException( \sprintf( '%d %s %d is after %d %s %d', $this->number, $this->month->name(), $this->month->year()->number(), $day->number(), $day->month()->name(), $day->month()->year()->number(), ) ); } return Days::fromDateTimeIterator( new DateTimeIntervalIterator( $this->midnight(TimeZone::UTC()), $day->midnight(TimeZone::UTC()), TimeUnit::day(), $interval ) ); } public function since(self $day, Interval $interval) : Days { if ($this->isBefore($day)) { throw new InvalidArgumentException( \sprintf( '%d %s %d is before %d %s %d', $this->number, $this->month->name(), $this->month->year()->number(), $day->number(), $day->month()->name(), $day->month()->year()->number(), ) ); } return Days::fromDateTimeIterator( new DateTimeIntervalIterator( $this->midnight(TimeZone::UTC()), $day->midnight(TimeZone::UTC()), TimeUnit::day()->toNegative(), $interval ) ); } public function distance(self $to) : TimeUnit { return (new TimePeriod($this->midnight(TimeZone::UTC()), $to->midnight(TimeZone::UTC())))->distance(); } public function quarter() : Quarter { return $this->year()->quarter((int) \ceil($this->month->number() / 3)); } public function compareTo(self $day) : int { if ($this->isEqualTo($day)) { return 0; } return $this->isBefore($day) ? -1 : 1; } } ================================================ FILE: src/Aeon/Calendar/Gregorian/Days.php ================================================ */ final class Days implements \Countable, \IteratorAggregate { /** * @var \Iterator */ private \Iterator $days; /** * @param \Iterator $days */ private function __construct(\Iterator $days) { $this->days = $days; } /** * @psalm-pure */ public static function fromArray(Day ...$days) : self { /** @psalm-suppress ImpureMethodCall */ return new self(new \ArrayIterator($days)); } /** * @psalm-pure */ public static function fromDateTimeIterator(DateTimeIntervalIterator $iterator) : self { /** @psalm-suppress ImpureMethodCall */ return new self(DaysIterator::fromDateTimeIterator($iterator)); } /** * @return array * * @psalm-suppress ImpureFunctionCall */ public function all() : array { return \iterator_to_array($this->days); } /** * @psalm-template MapResultType * * @param callable(Day $day) : MapResultType $iterator * * @psalm-param pure-callable(Day $day) : MapResultType $iterator * * @return array */ public function map(callable $iterator) : array { return \array_map($iterator, $this->all()); } /** * @psalm-suppress InvalidScalarArgument * * @param callable(Day $day) : bool $iterator * * @psalm-param pure-callable(Day $day) : bool $iterator */ public function filter(callable $iterator) : self { return new self(new \CallbackFilterIterator($this->days, $iterator)); } public function count() : int { return \iterator_count($this->days); } /** * @return \Traversable */ public function getIterator() : \Traversable { return $this->days; } } ================================================ FILE: src/Aeon/Calendar/Gregorian/DaysIterator.php ================================================ $iterator */ private function __construct(\Traversable $iterator) { parent::__construct($iterator); } /** * @psalm-suppress MixedArgumentTypeCoercion */ public static function fromDateTimeIterator(DateTimeIntervalIterator $datePeriod) : self { return new self($datePeriod); } public function current() : ?Day { /** @var null|DateTime|Day $current */ $current = parent::current(); if ($current === null) { return null; } if ($current instanceof Day) { return $current; } return $current->day(); } public function reverse() : self { /** @phpstan-ignore-next-line */ return new self(new \ArrayIterator(\array_reverse(\iterator_to_array($this)))); } } ================================================ FILE: src/Aeon/Calendar/Gregorian/GregorianCalendar.php ================================================ timeZone = $timeZone; } /** * @psalm-pure */ public static function UTC() : self { return new self(TimeZone::UTC()); } /** * @psalm-pure * * @psalm-suppress ImpureFunctionCall */ public static function systemDefault() : self { return new self(TimeZone::fromString(\date_default_timezone_get())); } public function timeZone() : TimeZone { return $this->timeZone; } public function currentYear() : Year { return $this->now()->year(); } public function currentMonth() : Month { return $this->now()->month(); } public function currentDay() : Day { return $this->now()->day(); } public function now() : DateTime { if ($this->timeZone->name() === 'UTC') { return DateTime::fromDateTime(new \DateTimeImmutable('now', new \DateTimeZone('UTC'))); } return DateTime::fromDateTime(new \DateTimeImmutable('now', new \DateTimeZone('UTC'))) ->toTimeZone($this->timeZone); } public function yesterday() : DateTime { return $this->now()->yesterday(); } public function tomorrow() : DateTime { return $this->now()->tomorrow(); } } ================================================ FILE: src/Aeon/Calendar/Gregorian/GregorianCalendarStub.php ================================================ timeZone = $timeZone; $this->currentDate = $currentDate; } /** * @psalm-pure */ public static function UTC(?DateTime $currentDate = null) : self { return new self(TimeZone::UTC(), $currentDate); } /** * @psalm-pure * * @psalm-suppress ImpureFunctionCall */ public static function systemDefault(?DateTime $currentDate = null) : self { return new self(TimeZone::fromString(\date_default_timezone_get()), $currentDate); } public function timeZone() : TimeZone { return $this->timeZone; } public function currentYear() : Year { return $this->now()->year(); } public function currentMonth() : Month { return $this->now()->month(); } public function currentDay() : Day { return $this->now()->day(); } public function now() : DateTime { return $this->currentDate ? $this->currentDate : DateTime::fromDateTime(new \DateTimeImmutable('now', $this->timeZone->toDateTimeZone())); } public function yesterday() : DateTime { return $this->now()->yesterday(); } public function tomorrow() : DateTime { return $this->now()->tomorrow(); } /** * @psalm-suppress InaccessibleProperty */ public function setNow(DateTime $dateTime) : void { $this->currentDate = $dateTime; } } ================================================ FILE: src/Aeon/Calendar/Gregorian/Interval.php ================================================ type = $type; } /** * A <= x <= B. * * @psalm-pure */ public static function closed() : self { return new self(self::CLOSED); } /** * A <= x < B. * * @psalm-pure */ public static function rightOpen() : self { return new self(self::RIGHT_OPEN); } /** * A < x <= B. * * @psalm-pure */ public static function leftOpen() : self { return new self(self::LEFT_OPEN); } /** * A < x < B. * * @psalm-pure */ public static function open() : self { return new self(self::OPEN); } public function isOpen() : bool { return $this->type === self::OPEN; } public function isLeftOpen() : bool { return $this->type === self::LEFT_OPEN || $this->type === self::OPEN; } public function isRightOpen() : bool { return $this->type === self::RIGHT_OPEN || $this->type === self::OPEN; } public function isClosed() : bool { return $this->type === self::CLOSED; } } ================================================ FILE: src/Aeon/Calendar/Gregorian/LeapSecond.php ================================================ inSeconds() < 10) { throw new InvalidArgumentException('Leap second TAI offset must be greater or equal 10'); } $this->dateTime = $dateTime; $this->offsetTAI = $offsetTAI; } /** * @return array{dateTime: DateTime, offsetTAI: TimeUnit} */ public function __serialize() : array { return [ 'dateTime' => $this->dateTime, 'offsetTAI' => $this->offsetTAI, ]; } public function dateTime() : DateTime { return $this->dateTime; } public function offsetTAI() : TimeUnit { return $this->offsetTAI; } /** * @infection-ignore-all * * @deprecated Use `isEqualTo` instead. Will be removed with 2.0 */ public function isEqual(self $second) : bool { return $this->isEqualTo($second); } public function isEqualTo(self $second) : bool { return $this->dateTime->isEqualTo($second->dateTime()) && $this->offsetTAI->isEqualTo($second->offsetTAI()); } } ================================================ FILE: src/Aeon/Calendar/Gregorian/LeapSeconds.php ================================================ */ private array $leapSeconds; private function __construct(DateTime $listExpiration, LeapSecond ...$leapSeconds) { $this->leapSeconds = $leapSeconds; $this->listExpirationDate = $listExpiration; } /** * List taken from https://www.ietf.org/timezones/data/leap-seconds.list. * * Leap seconds are announced by https://www.iers.org/ (The International Earth Rotation and Reference Systems Service) * in their Bulletin C, list of all available releases https://www.iers.org/IERS/EN/Publications/Bulletins/bulletins.html * * @psalm-pure * * @psalm-suppress ImpureStaticProperty */ public static function load() : self { if (self::$instance instanceof self) { return self::$instance; } self::$instance = new self( DateTime::fromString('2026-06-28 00:00:00 UTC'), new LeapSecond(DateTime::fromString('1972-01-01 00:00:00 UTC'), TimeUnit::seconds(10)), new LeapSecond(DateTime::fromString('1972-07-01 00:00:00 UTC'), TimeUnit::seconds(11)), new LeapSecond(DateTime::fromString('1973-01-01 00:00:00 UTC'), TimeUnit::seconds(12)), new LeapSecond(DateTime::fromString('1974-01-01 00:00:00 UTC'), TimeUnit::seconds(13)), new LeapSecond(DateTime::fromString('1975-01-01 00:00:00 UTC'), TimeUnit::seconds(14)), new LeapSecond(DateTime::fromString('1976-01-01 00:00:00 UTC'), TimeUnit::seconds(15)), new LeapSecond(DateTime::fromString('1977-01-01 00:00:00 UTC'), TimeUnit::seconds(16)), new LeapSecond(DateTime::fromString('1978-01-01 00:00:00 UTC'), TimeUnit::seconds(17)), new LeapSecond(DateTime::fromString('1979-01-01 00:00:00 UTC'), TimeUnit::seconds(18)), new LeapSecond(DateTime::fromString('1980-01-01 00:00:00 UTC'), TimeUnit::seconds(19)), new LeapSecond(DateTime::fromString('1981-07-01 00:00:00 UTC'), TimeUnit::seconds(20)), new LeapSecond(DateTime::fromString('1982-07-01 00:00:00 UTC'), TimeUnit::seconds(21)), new LeapSecond(DateTime::fromString('1983-07-01 00:00:00 UTC'), TimeUnit::seconds(22)), new LeapSecond(DateTime::fromString('1985-07-01 00:00:00 UTC'), TimeUnit::seconds(23)), new LeapSecond(DateTime::fromString('1988-01-01 00:00:00 UTC'), TimeUnit::seconds(24)), new LeapSecond(DateTime::fromString('1990-01-01 00:00:00 UTC'), TimeUnit::seconds(24)), new LeapSecond(DateTime::fromString('1991-01-01 00:00:00 UTC'), TimeUnit::seconds(25)), new LeapSecond(DateTime::fromString('1992-07-01 00:00:00 UTC'), TimeUnit::seconds(26)), new LeapSecond(DateTime::fromString('1993-07-01 00:00:00 UTC'), TimeUnit::seconds(27)), new LeapSecond(DateTime::fromString('1994-07-01 00:00:00 UTC'), TimeUnit::seconds(28)), new LeapSecond(DateTime::fromString('1996-01-01 00:00:00 UTC'), TimeUnit::seconds(29)), new LeapSecond(DateTime::fromString('1997-07-01 00:00:00 UTC'), TimeUnit::seconds(30)), new LeapSecond(DateTime::fromString('1999-01-01 00:00:00 UTC'), TimeUnit::seconds(31)), new LeapSecond(DateTime::fromString('2006-01-01 00:00:00 UTC'), TimeUnit::seconds(32)), new LeapSecond(DateTime::fromString('2009-01-01 00:00:00 UTC'), TimeUnit::seconds(33)), new LeapSecond(DateTime::fromString('2012-07-01 00:00:00 UTC'), TimeUnit::seconds(34)), new LeapSecond(DateTime::fromString('2015-07-01 00:00:00 UTC'), TimeUnit::seconds(36)), new LeapSecond(DateTime::fromString('2017-01-01 00:00:00 UTC'), TimeUnit::seconds(37)), ); return self::$instance; } public function expirationDate() : DateTime { return $this->listExpirationDate; } public function since(DateTime $dateTime) : self { /* @phpstan-ignore-next-line */ return $this->filter(function (LeapSecond $leapSecond) use ($dateTime) : bool { return $dateTime ->toTimeZone(TimeZone::UTC()) ->isBefore($leapSecond->dateTime()); }); } public function until(DateTime $dateTime) : self { /* @phpstan-ignore-next-line */ return $this->filter(function (LeapSecond $leapSecond) use ($dateTime) : bool { return $dateTime ->toTimeZone(TimeZone::UTC()) ->isAfterOrEqualTo($leapSecond->dateTime()); }); } public function findAllBetween(TimePeriod $timePeriod) : self { /* @phpstan-ignore-next-line */ return $this->filter(function (LeapSecond $leapSecond) use ($timePeriod) : bool { return $timePeriod->start() ->toTimeZone(TimeZone::UTC()) ->isBeforeOrEqualTo($leapSecond->dateTime()) && $timePeriod->end() ->toTimeZone(TimeZone::UTC()) ->isAfter($leapSecond->dateTime()); }); } public function offsetTAI() : TimeUnit { return $this->leapSeconds[\count($this->leapSeconds) - 1]->offsetTAI(); } /** * @param callable(LeapSecond $leapSecond) : bool $filter * * @psalm-param pure-callable(LeapSecond $leapSecond) : bool $filter */ public function filter(callable $filter) : self { return new self( $this->expirationDate(), ...\array_filter($this->all(), $filter) ); } public function count() : TimeUnit { return TimeUnit::seconds(\count($this->all())); } /** * @return array */ public function all() : array { return $this->leapSeconds; } } ================================================ FILE: src/Aeon/Calendar/Gregorian/Month.php ================================================ self::TOTAL_MONTHS) { throw new InvalidArgumentException('Month number must be greater or equal 1 and less or equal than 12'); } $this->year = $year; $this->number = $number; $this->days = new MonthDays($this); } /** * @psalm-pure */ public static function create(int $year, int $month) : self { return new self( new Year($year), $month ); } /** * @psalm-pure * * @psalm-suppress ImpureMethodCall */ public static function fromDateTime(\DateTimeInterface $dateTime) : self { return new self( new Year((int) $dateTime->format('Y')), (int) $dateTime->format('m') ); } /** * @psalm-pure * * @psalm-suppress ImpureMethodCall */ public static function fromString(string $date) : self { try { return self::fromDateTime(new \DateTimeImmutable($date)); } catch (\Exception $e) { throw new InvalidArgumentException("Value \"{$date}\" is not valid month format."); } } /** * @return array{year: int, month: int} */ public function __debugInfo() : array { return [ 'year' => $this->year()->number(), 'month' => $this->number(), ]; } /** * @return array{year: Year, number: int} */ public function __serialize() : array { return [ 'year' => $this->year(), 'number' => $this->number(), ]; } /** * @param array{year: Year, number: int} $data */ public function __unserialize(array $data) : void { $this->year = $data['year']; $this->number = $data['number']; $this->days = new MonthDays($this); } public function __toString() : string { return $this->toString(); } public function toString() : string { return $this->toDateTimeImmutable()->format('Y-m'); } public function previous() : self { return $this->subMonths(1); } public function next() : self { return $this->addMonths(1); } /** * @infection-ignore-all * * @deprecated Use `add` instead. Will be removed with 2.0 */ public function plus(int $years, int $months) : self { return $this->add($years, $months); } public function add(int $years, int $months) : self { $month = $this; if ($months !== 0) { $month = $month->addMonths($months); } if ($years !== 0) { $month = $month->addYears($years); } return $month; } /** * @infection-ignore-all * * @deprecated Use `sub` instead. Will be removed with 2.0 */ public function minus(int $years, int $months) : self { return $this->sub($years, $months); } public function sub(int $years, int $months) : self { $month = $this; if ($months !== 0) { $month = $month->subMonths($months); } if ($years !== 0) { $month = $month->subYears($years); } return $month; } /** * @infection-ignore-all * * @deprecated Use `addMonths` instead. Will be removed with 2.0 */ public function plusMonths(int $months) : self { return $this->addMonths($months); } public function addMonths(int $months) : self { $years = (int) ($months / self::TOTAL_MONTHS); $monthsRemainder = $months % self::TOTAL_MONTHS; $year = $this->year->number() + $years; $month = $this->number() + $monthsRemainder; if ($month > self::TOTAL_MONTHS) { $year += ((int) ($month / self::TOTAL_MONTHS)); $month %= self::TOTAL_MONTHS; } if ($month === 0) { $month = 12; } return new self(new Year($year), $month); } /** * @infection-ignore-all * * @deprecated Use `subMonths` instead. Will be removed with 2.0 */ public function minusMonths(int $months) : self { return $this->subMonths($months); } public function subMonths(int $months) : self { $years = (int) ($months / self::TOTAL_MONTHS); $monthsRemainder = $months % self::TOTAL_MONTHS; $year = $this->year->number() - $years; $month = $this->number() - $monthsRemainder; if ($month === 0) { $month = self::TOTAL_MONTHS; $year--; } if ($month < 0) { $month = self::TOTAL_MONTHS - \abs($month); $year--; } return new self(new Year($year), $month); } /** * @infection-ignore-all * * @deprecated Use `addYears` instead. Will be removed with 2.0 */ public function plusYears(int $years) : self { return $this->addYears($years); } public function addYears(int $years) : self { return new self($this->year->add($years), $this->number); } /** * @infection-ignore-all * * @deprecated Use `subYears` instead. Will be removed with 2.0 */ public function minusYears(int $years) : self { return $this->subYears($years); } public function subYears(int $years) : self { return new self($this->year->sub($years), $this->number); } public function firstDay() : Day { return $this->days()->first(); } public function lastDay() : Day { return $this->days()->last(); } public function number() : int { return $this->number; } public function year() : Year { return $this->year; } public function days() : MonthDays { return $this->days; } public function numberOfDays() : int { return 31 - (($this->number == 2) ? (3 - (int) $this->year->isLeap()) : (($this->number - 1) % 7 % 2)); } public function shortName() : string { return $this->toDateTimeImmutable()->format('M'); } public function name() : string { return $this->toDateTimeImmutable()->format('F'); } public function toDateTimeImmutable() : \DateTimeImmutable { return new \DateTimeImmutable(\sprintf( '%d-%d-01 00:00:00.000000 UTC', $this->year()->number(), $this->number() )); } public function iterate(self $destination, Interval $interval) : Months { return $this->isAfter($destination) ? $this->since($destination, $interval) : $this->until($destination, $interval); } public function until(self $month, Interval $interval) : Months { if ($this->isAfter($month)) { throw new InvalidArgumentException( \sprintf( '%s %d is after %s %d', $this->name(), $this->year->number(), $month->name(), $month->year->number(), ) ); } return Months::fromDateTimeIterator( new DateTimeIntervalIterator( $this->firstDay()->midnight(TimeZone::UTC()), $month->firstDay()->midnight(TimeZone::UTC()), RelativeTimeUnit::month(), $interval ) ); } public function since(self $month, Interval $interval) : Months { if ($this->isBefore($month)) { throw new InvalidArgumentException( \sprintf( '%s %d is before %s %d', $this->name(), $this->year->number(), $month->name(), $month->year->number(), ) ); } return Months::fromDateTimeIterator( new DateTimeIntervalIterator( $month->firstDay()->midnight(TimeZone::UTC()), $this->firstDay()->midnight(TimeZone::UTC()), RelativeTimeUnit::month(), $interval ) ); } /** * @infection-ignore-all * * @deprecated Use `isEqualTo` instead. Will be removed with 2.0 */ public function isEqual(self $month) : bool { return $this->isEqualTo($month); } public function isEqualTo(self $month) : bool { return $this->number() == $month->number() && $this->year()->isEqualTo($month->year()); } public function isBefore(self $month) : bool { if ($this->year()->isBefore($month->year())) { return true; } if ($this->year()->isAfter($month->year())) { return false; } return $this->number() < $month->number(); } /** * @infection-ignore-all * * @deprecated Use `isBeforeOrEqualTo` instead. Will be removed with 2.0 */ public function isBeforeOrEqual(self $month) : bool { return $this->isBeforeOrEqualTo($month); } public function isBeforeOrEqualTo(self $month) : bool { if ($this->year()->isBefore($month->year())) { return true; } if ($this->year()->isAfter($month->year())) { return false; } return $this->number() <= $month->number(); } public function isAfter(self $month) : bool { if ($this->year()->isAfter($month->year())) { return true; } if ($this->year()->isBefore($month->year())) { return false; } return $this->number() > $month->number(); } /** * @infection-ignore-all * * @deprecated Use `isAfterOrEqualTo` instead. Will be removed with 2.0 */ public function isAfterOrEqual(self $month) : bool { return $this->isAfterOrEqualTo($month); } public function isAfterOrEqualTo(self $month) : bool { if ($this->year()->isAfter($month->year())) { return true; } if ($this->year()->isBefore($month->year())) { return false; } return $this->number() >= $month->number(); } public function distance(self $to) : TimeUnit { return (new TimePeriod($this->firstDay()->midnight(TimeZone::UTC()), $to->firstDay()->midnight(TimeZone::UTC())))->distance(); } public function quarter() : Quarter { return $this->year->quarter((int) \ceil($this->number / 3)); } public function compareTo(self $month) : int { if ($this->isEqualTo($month)) { return 0; } return $this->isBefore($month) ? -1 : 1; } } ================================================ FILE: src/Aeon/Calendar/Gregorian/MonthDays.php ================================================ month = $month; } public function count() : int { return $this->month->numberOfDays(); } public function first() : Day { return new Day($this->month, 1); } public function last() : Day { return new Day($this->month, $this->month->numberOfDays()); } /** * @return array */ public function all() : array { /** @psalm-suppress ImpureFunctionCall */ return \array_map( fn (int $dayNumber) : Day => new Day($this->month, $dayNumber), \range(1, $this->month->numberOfDays()) ); } /** * @param callable(Day $day) : void $iterator * * @psalm-param pure-callable(Day $day) : void $iterator * * @return array */ public function map(callable $iterator) : array { return \array_map( $iterator, $this->all() ); } /** * @param callable(Day $day) : bool $iterator * * @psalm-param pure-callable(Day $day) : bool $iterator * * @return Days */ public function filter(callable $iterator) : Days { return Days::fromArray( ...\array_filter( $this->all(), $iterator ) ); } } ================================================ FILE: src/Aeon/Calendar/Gregorian/Months.php ================================================ */ final class Months implements \Countable, \IteratorAggregate { /** * @var \Iterator */ private \Iterator $months; /** * @param \Iterator $months */ private function __construct(\Iterator $months) { $this->months = $months; } /** * @psalm-pure */ public static function fromArray(Month ...$days) : self { /** @psalm-suppress ImpureMethodCall */ return new self(new \ArrayIterator($days)); } /** * @psalm-pure */ public static function fromDateTimeIterator(DateTimeIntervalIterator $iterator) : self { /** @psalm-suppress ImpureMethodCall */ return new self(MonthsIterator::fromDateTimeIterator($iterator)); } /** * @return array * * @psalm-suppress ImpureFunctionCall */ public function all() : array { return \iterator_to_array($this->months); } /** * @param callable(Month $month) : mixed $iterator * * @psalm-param pure-callable(Month $month) : mixed $iterator * * @return array */ public function map(callable $iterator) : array { return \array_map($iterator, $this->all()); } /** * @psalm-suppress InvalidScalarArgument * * @param callable(Month $month) : bool $iterator * * @psalm-param pure-callable(Month $month) : bool $iterator */ public function filter(callable $iterator) : self { return new self(new \CallbackFilterIterator($this->months, $iterator)); } public function count() : int { return \iterator_count($this->months); } /** * @return \Traversable */ public function getIterator() : \Traversable { return $this->months; } } ================================================ FILE: src/Aeon/Calendar/Gregorian/MonthsIterator.php ================================================ $iterator */ private function __construct(\Traversable $iterator) { parent::__construct($iterator); } /** * @psalm-suppress MixedArgumentTypeCoercion */ public static function fromDateTimeIterator(DateTimeIntervalIterator $iterator) : self { return new self($iterator); } public function current() : ?Month { /** @var null|DateTime|Month $current */ $current = parent::current(); if ($current === null) { return null; } if ($current instanceof Month) { return $current; } return $current->month(); } public function reverse() : self { /** @phpstan-ignore-next-line */ return new self(new \ArrayIterator(\array_reverse(\iterator_to_array($this)))); } } ================================================ FILE: src/Aeon/Calendar/Gregorian/Psr20CalendarAdapter.php ================================================ calendar->now()->toDateTimeImmutable(); } } ================================================ FILE: src/Aeon/Calendar/Gregorian/Quarter.php ================================================ 4) { throw new InvalidArgumentException('Quarter number must be greater or equal 1 and less or equal than 4'); } if (\count($months) !== 3) { throw new InvalidArgumentException('Quarter must have exactly 3 months'); } switch ($number) { case 1: /* @phpstan-ignore-next-line */ if ($months->map(fn (Month $month) => $month->number()) !== [1, 2, 3]) { throw new InvalidArgumentException('Quarter 1 must must have Jan, Feb and Mar'); } break; case 2: /* @phpstan-ignore-next-line */ if ($months->map(fn (Month $month) => $month->number()) !== [4, 5, 6]) { throw new InvalidArgumentException('Quarter 2 must must have Apr, May and Jun'); } break; case 3: /* @phpstan-ignore-next-line */ if ($months->map(fn (Month $month) => $month->number()) !== [7, 8, 9]) { throw new InvalidArgumentException('Quarter 3 must must have Jul, Aug and Sep'); } break; case 4: /* @phpstan-ignore-next-line */ if ($months->map(fn (Month $month) => $month->number()) !== [10, 11, 12]) { throw new InvalidArgumentException('Quarter 4 must must have Oct, Nov and Dec'); } break; } $this->number = $number; $this->months = $months; } public function number() : int { return $this->number; } public function months() : Months { return $this->months; } } ================================================ FILE: src/Aeon/Calendar/Gregorian/Time.php ================================================ 23) { throw new InvalidArgumentException('Hour must be greater or equal 0 and less or equal than 23'); } if ($minute < 0 || $minute >= 60) { throw new InvalidArgumentException('Minut must be greater or equal 0 and less than 60'); } if ($second < 0 || $second >= 60) { throw new InvalidArgumentException('Second must be greater or equal 0 and less than 60'); } if ($microsecond < 0 || $microsecond >= 1000000) { throw new InvalidArgumentException('Microsecond must be greater or equal 0 and less than 1000000'); } $this->hour = $hour; $this->minute = $minute; $this->second = $second; $this->microsecond = $microsecond; } /** * @psalm-pure * * @psalm-suppress ImpureMethodCall * @psalm-suppress PossiblyNullArrayAccess * @psalm-suppress PossiblyInvalidArgument */ public static function fromDateTime(\DateTimeInterface $dateTime) : self { /** * @phpstan-ignore-next-line */ [$hour, $minute, $second, $microsecond] = \sscanf($dateTime->format('H-i-s.u'), '%d-%d-%d.%d'); /** @phpstan-ignore-next-line */ return new self($hour, $minute, $second, $microsecond); } /** * @psalm-pure * * @psalm-suppress ImpureMethodCall */ public static function fromString(string $time) : self { try { return self::fromDateTime(new \DateTimeImmutable($time)); } catch (\Exception $e) { throw new InvalidArgumentException("Value \"{$time}\" is not valid time format."); } } /** * @return array{hour: int, minute: int, second: int, microsecond: int} */ public function __debugInfo() : array { return [ 'hour' => $this->hour, 'minute' => $this->minute, 'second' => $this->second, 'microsecond' => $this->microsecond, ]; } /** * @return array{hour: int, minute: int, second: int, microsecond: int} */ public function __serialize() : array { return [ 'hour' => $this->hour, 'minute' => $this->minute, 'second' => $this->second, 'microsecond' => $this->microsecond, ]; } /** * @param array{hour: int, minute: int, second: int, microsecond: int} $data */ public function __unserialize(array $data) : void { $this->hour = $data['hour']; $this->minute = $data['minute']; $this->second = $data['second']; $this->microsecond = $data['microsecond']; } public function format(string $format) : string { return $this->toDateTimeImmutable()->format($format); } public function toTimeUnit() : TimeUnit { return TimeUnit::positive($this->second, $this->microsecond) ->add(TimeUnit::minutes($this->minute)) ->add(TimeUnit::hours($this->hour)); } public function toString() : string { return \str_pad((string) $this->hour, 2, '0', STR_PAD_LEFT) . ':' . \str_pad((string) $this->minute, 2, '0', STR_PAD_LEFT) . ':' . \str_pad((string) $this->second, 2, '0', STR_PAD_LEFT) . '.' . \str_pad((string) $this->microsecond, self::PRECISION_MICROSECOND, '0', STR_PAD_LEFT); } public function hour() : int { return $this->hour; } public function minute() : int { return $this->minute; } public function second() : int { return $this->second; } public function microsecond() : int { return $this->microsecond; } public function millisecond() : int { return \intval($this->microsecond / 1000); } public function isAM() : bool { return $this->toDateTimeImmutable()->format('a') === 'am'; } public function isPM() : bool { return $this->toDateTimeImmutable()->format('a') === 'pm'; } /** * @infection-ignore-all * * @deprecated Use `isAfter` instead. Will be removed with 2.0 */ public function isGreaterThan(self $time) : bool { return $this->isAfter($time); } public function isAfter(self $time) : bool { $dateTimeImmutable = $this->toDateTimeImmutable(); $nextDateTimeImmutable = $dateTimeImmutable->setTime($time->hour, $time->minute, $time->second, $time->microsecond); return $dateTimeImmutable > $nextDateTimeImmutable; } /** * @infection-ignore-all * * @deprecated Use `isAfterOrEqualTo` instead. Will be removed with 2.0 */ public function isGreaterThanEq(self $time) : bool { return $this->isAfterOrEqualTo($time); } public function isAfterOrEqualTo(self $time) : bool { $dateTimeImmutable = $this->toDateTimeImmutable(); $nextDateTimeImmutable = $dateTimeImmutable->setTime($time->hour, $time->minute, $time->second, $time->microsecond); return $dateTimeImmutable >= $nextDateTimeImmutable; } /** * @infection-ignore-all * * @deprecated Use `isEqualTo` instead. Will be removed with 2.0 */ public function isEqual(self $time) : bool { return $this->isEqualTo($time); } public function isEqualTo(self $time) : bool { $dateTimeImmutable = $this->toDateTimeImmutable(); $nextDateTimeImmutable = $dateTimeImmutable->setTime($time->hour, $time->minute, $time->second, $time->microsecond); return $dateTimeImmutable == $nextDateTimeImmutable; } /** * @infection-ignore-all * * @deprecated Use `isBefore` instead. Will be removed with 2.0 */ public function isLessThan(self $time) : bool { return $this->isBefore($time); } public function isBefore(self $time) : bool { $dateTimeImmutable = $this->toDateTimeImmutable(); $nextDateTimeImmutable = $dateTimeImmutable->setTime($time->hour, $time->minute, $time->second, $time->microsecond); return $dateTimeImmutable < $nextDateTimeImmutable; } /** * @infection-ignore-all * * @deprecated Use `isBeforeOrEqualTo` instead. Will be removed with 2.0 */ public function isLessThanEq(self $time) : bool { return $this->isBeforeOrEqualTo($time); } public function isBeforeOrEqualTo(self $time) : bool { $dateTimeImmutable = $this->toDateTimeImmutable(); $nextDateTimeImmutable = $dateTimeImmutable->setTime($time->hour, $time->minute, $time->second, $time->microsecond); return $dateTimeImmutable <= $nextDateTimeImmutable; } public function isMidnight() : bool { return $this->hour() === 0 && $this->minute() === 0 && $this->second() === 0 && $this->microsecond() === 0; } public function isNotMidnight() : bool { return !$this->isMidnight(); } public function add(TimeUnit $timeUnit) : self { return self::fromDateTime($this->toDateTimeImmutable()->add($timeUnit->toDateInterval())); } public function sub(TimeUnit $timeUnit) : self { /** @psalm-suppress PossiblyFalseArgument */ return self::fromDateTime($this->toDateTimeImmutable()->sub($timeUnit->toDateInterval())); } public function compareTo(self $time) : int { if ($this->isEqualTo($time)) { return 0; } return $this->isBefore($time) ? -1 : 1; } private function toDateTimeImmutable() : \DateTimeImmutable { return (new \DateTimeImmutable('now', new \DateTimeZone('UTC'))) ->setTime( $this->hour, $this->minute, $this->second, $this->microsecond ); } } ================================================ FILE: src/Aeon/Calendar/Gregorian/TimeEpoch.php ================================================ type = $type; $this->dateTime = $dateTime; } /** * Unix Epoch, started at 1970-01-01 00:00:00 UTC, not including leap seconds. * * @psalm-pure */ public static function UNIX() : self { return new self(self::UNIX, DateTime::fromString('1970-01-01 00:00:00 UTC')); } /** * Other name for UNIX epoch. * * @psalm-pure */ public static function POSIX() : self { return self::UNIX(); } /** * UTC Epoch, started at 1972-01-01 00:00:00 UTC, including leap seconds. * * @psalm-pure */ public static function UTC() : self { return new self(self::UTC, DateTime::fromString('1972-01-01 00:00:00 UTC')); } /** * GPS Epoch, started at 1980-01-06 00:00:00 UTC, including leap seconds * except first 9 there were added before epoch. * * @psalm-pure */ public static function GPS() : self { return new self(self::GPS, DateTime::fromString('1980-01-06 00:00:00 UTC')); } /** * TAI Epoch, started at 1958-01-01 00:00:00 UTC, including leap seconds. * * @psalm-pure */ public static function TAI() : self { return new self(self::TAI, DateTime::fromString('1958-01-01 00:00:00 UTC')); } public function type() : int { return $this->type; } public function date() : DateTime { return $this->dateTime; } /** * Returns difference in seconds between epoches without leap seconds. */ public function distanceTo(self $timeEpoch) : TimeUnit { switch ($this->type) { case self::UTC: switch ($timeEpoch->type()) { case self::GPS: return TimeUnit::seconds(252892800); // 1972-01-01 00:00:00 UTC - 1980-01-06 00:00:00 UTC case self::UNIX: return TimeUnit::seconds(63072000)->invert(); // 1972-01-01 00:00:00 UTC - 1970-01-01 00:00:00 UTC case self::TAI: return TimeUnit::seconds(441763200)->invert(); // 1972-01-01 00:00:00 UTC - 1958-01-01 00:00:00 UTC default: return TimeUnit::seconds(0); } // no break case self::GPS: switch ($timeEpoch->type()) { case self::UTC: return TimeUnit::seconds(252892800)->invert(); // 1980-01-06 00:00:00 UTC - 1972-01-01 00:00:00 UTC case self::UNIX: return TimeUnit::seconds(315964800)->invert(); // 1980-01-06 00:00:00 UTC - 1970-01-01 00:00:00 UTC case self::TAI: return TimeUnit::seconds(694656000)->invert(); // 1980-01-06 00:00:00 UTC - 1958-01-01 00:00:00 UTC default: return TimeUnit::seconds(0); } // no break case self::TAI: switch ($timeEpoch->type()) { case self::UTC: return TimeUnit::seconds(441763200); // 1958-01-01 00:00:00 UTC - 1972-01-01 00:00:00 UTC case self::UNIX: return TimeUnit::seconds(378691200); // 1958-01-01 00:00:00 UTC - 1970-01-00 00:00:00 UTC case self::GPS: return TimeUnit::seconds(694656000); // 1958-01-01 00:00:00 UTC - 1980-01-06 00:00:00 UTC default: return TimeUnit::seconds(0); } // no break default: // UNIX switch ($timeEpoch->type()) { case self::UTC: return TimeUnit::seconds(63072000); // 1970-01-01 00:00:00 UTC - 1972-01-01 00:00:00 UTC case self::GPS: return TimeUnit::seconds(315964800); // 1970-01-01 00:00:00 UTC - 1980-01-06 00:00:00 UTC case self::TAI: return TimeUnit::seconds(378691200)->invert(); // 1970-01-01 00:00:00 UTC - 1958-01-01 00:00:00 UTC default: return TimeUnit::seconds(0); } } } } ================================================ FILE: src/Aeon/Calendar/Gregorian/TimePeriod.php ================================================ start = $start; $this->end = $end; } /** * @return array{start: DateTime, end: DateTime} */ public function __serialize() : array { return [ 'start' => $this->start, 'end' => $this->end, ]; } public function start() : DateTime { return $this->start; } public function end() : DateTime { return $this->end; } public function isForward() : bool { return $this->distance()->isPositive(); } public function isBackward() : bool { return $this->distance()->isNegative(); } /** * Calculate distance between 2 points in time without leap seconds. */ public function distance() : TimeUnit { $startUnixTimestamp = $this->start->timestampUNIX(); $endUnixTimestamp = $this->end->timestampUNIX(); $result = $endUnixTimestamp ->sub($startUnixTimestamp) ->toPositive(); return $this->start->isAfter($this->end) ? $result->invert() : $result; } public function leapSeconds() : LeapSeconds { return LeapSeconds::load()->findAllBetween($this); } /** * @psalm-suppress ImpureMethodCall */ public function iterate(Unit $timeUnit, Interval $interval) : TimePeriods { return TimePeriods::fromIterator(new TimePeriodsIterator($this->start, $this->end, $timeUnit, $interval)); } /** * @psalm-suppress ImpureMethodCall */ public function iterateBackward(Unit $timeUnit, Interval $interval) : TimePeriods { return TimePeriods::fromIterator(new TimePeriodsIterator($this->end, $this->start, $timeUnit->toNegative(), $interval)); } public function overlaps(self $timePeriod) : bool { if ($this->isBackward()) { $thisPeriodForward = $this->revert(); } else { $thisPeriodForward = $this; } if ($timePeriod->isBackward()) { $otherPeriodForward = $timePeriod->revert(); } else { $otherPeriodForward = $timePeriod; } $thisPeriodStart = $thisPeriodForward->start(); $thisPeriodEnd = $thisPeriodForward->end(); $otherPeriodStart = $otherPeriodForward->start(); $otherPeriodEnd = $otherPeriodForward->end(); if ($thisPeriodForward->abuts($otherPeriodForward)) { return false; } if ($thisPeriodStart->isBefore($otherPeriodStart) && $thisPeriodEnd->isBefore($otherPeriodStart) && $thisPeriodEnd->isBefore($otherPeriodEnd) ) { return false; } if ($thisPeriodEnd->isBefore($otherPeriodEnd)) { return true; } if ($thisPeriodStart->isAfter($otherPeriodStart) && $thisPeriodStart->isBefore($otherPeriodEnd) && $thisPeriodEnd->isAfter($otherPeriodStart) ) { return true; } if ($thisPeriodStart->isAfter($otherPeriodStart) && $thisPeriodEnd->isAfter($otherPeriodStart) && $thisPeriodEnd->isAfter($otherPeriodEnd) ) { return false; } return true; } public function contains(self $timePeriod) : bool { return $this->start->isBeforeOrEqualTo($timePeriod->start()) && $this->end->isAfterOrEqualTo($timePeriod->end()); } public function revert() : self { return new self($this->end(), $this->start()); } public function merge(self $timePeriod) : self { if (!$this->overlaps($timePeriod) && !$this->abuts($timePeriod)) { throw new InvalidArgumentException("Can't merge not overlapping time periods."); } return new self( $this->start->isBeforeOrEqualTo($timePeriod->start) ? $this->start() : $timePeriod->start, $this->end->isAfterOrEqualTo($timePeriod->end) ? $this->end() : $timePeriod->end() ); } public function abuts(self $timePeriod) : bool { $thisPeriodForward = $this->isBackward() ? $this->revert() : $this; $otherPeriodForward = $timePeriod->isBackward() ? $timePeriod->revert() : $timePeriod; if ($thisPeriodForward->end()->isEqualTo($otherPeriodForward->start())) { return true; } if ($thisPeriodForward->start()->isEqualTo($otherPeriodForward->end())) { return true; } return false; } /** * @infection-ignore-all * * @deprecated Use `isEqualTo` instead. Will be removed with 2.0 */ public function isEqual(self $period) : bool { return $this->isEqualTo($period); } public function isEqualTo(self $period) : bool { return $this->start->isEqualTo($period->start()) && $this->end->isEqualTo($period->end()); } } ================================================ FILE: src/Aeon/Calendar/Gregorian/TimePeriods.php ================================================ */ final class TimePeriods implements \Countable, \IteratorAggregate { /** * @var \Iterator */ private \Iterator $periods; /** * @param \Iterator $periods */ private function __construct(\Iterator $periods) { $this->periods = $periods; } public static function fromArray(TimePeriod ...$periods) : self { return new self(new \ArrayIterator($periods)); } public static function fromIterator(TimePeriodsIterator $timePeriodsIterator) : self { return new self($timePeriodsIterator); } /** * @return array */ public function all() : array { return \iterator_to_array($this->periods); } /** * @param callable(TimePeriod $timePeriod) : void $iterator * * @psalm-param pure-callable(TimePeriod $timePeriod) : void $iterator */ public function each(callable $iterator) : void { foreach ($this->periods as $period) { $iterator($period); } } /** * @param callable(TimePeriod $timePeriod) : mixed $iterator * * @psalm-param pure-callable(TimePeriod $timePeriod) : mixed $iterator * * @return array */ public function map(callable $iterator) : array { return \array_map($iterator, $this->all()); } /** * @psalm-suppress InvalidScalarArgument * * @param callable(TimePeriod $timePeriod) : bool $iterator * * @psalm-param pure-callable(TimePeriod $timePeriod) : bool $iterator * * @return self */ public function filter(callable $iterator) : self { return new self(new \CallbackFilterIterator($this->periods, $iterator)); } public function count() : int { return \iterator_count($this->periods); } /** * @return \Traversable */ public function getIterator() : \Traversable { return $this->periods; } /** * Find all gaps between time periods. */ public function gaps() : self { $periods = \array_map( function (TimePeriod $timePeriod) : TimePeriod { return $timePeriod->isBackward() ? $timePeriod->revert() : $timePeriod; }, $this->sort()->all() ); if (!\count($periods)) { return self::fromArray(...[]); } $gaps = []; $totalPeriod = \current($periods); while ($period = \next($periods)) { if ($totalPeriod->overlaps($period) || $totalPeriod->abuts($period)) { $totalPeriod = $totalPeriod->merge($period); } else { $gaps[] = new TimePeriod($totalPeriod->end(), $period->start()); $totalPeriod = $period; } } return self::fromArray(...$gaps); } public function sort() : self { return $this->sortBy(TimePeriodsSort::asc()); } public function sortBy(TimePeriodsSort $sort) : self { $periods = $this->all(); \uasort( $periods, function (TimePeriod $timePeriodA, TimePeriod $timePeriodB) use ($sort) : int { if ($sort->byStartDate()) { return $sort->isAscending() ? $timePeriodA->start()->toDateTimeImmutable() <=> $timePeriodB->start()->toDateTimeImmutable() : $timePeriodB->start()->toDateTimeImmutable() <=> $timePeriodA->start()->toDateTimeImmutable(); } return $sort->isAscending() ? $timePeriodA->end()->toDateTimeImmutable() <=> $timePeriodB->end()->toDateTimeImmutable() : $timePeriodB->end()->toDateTimeImmutable() <=> $timePeriodA->end()->toDateTimeImmutable(); } ); return self::fromArray(...$periods); } public function first() : ?TimePeriod { $this->periods->rewind(); return $this->periods->current(); } public function last() : ?TimePeriod { $periods = $this->all(); if (!\count($periods)) { return null; } return \end($periods); } public function add(TimePeriod ...$timePeriods) : self { return self::fromArray(...\array_merge($this->all(), $timePeriods)); } public function merge(self $timePeriods) : self { return self::fromArray(...\array_merge($this->all(), $timePeriods->all())); } /** * @infection-ignore-all * * @deprecated Use `isEqualTo` instead. Will be removed with 2.0 */ public function isEqual(self $periods) : bool { return $this->isEqualTo($periods); } public function isEqualTo(self $periods) : bool { if ($periods->count() !== $this->count()) { return false; } $selfArray = \array_values($this->sort()->all()); $periodsArray = \array_values($periods->sort()->all()); foreach ($selfArray as $i => $timePeriod) { if (!$periodsArray[$i]->isEqualTo($timePeriod)) { return false; } } return true; } } ================================================ FILE: src/Aeon/Calendar/Gregorian/TimePeriodsIterator.php ================================================ interval = $interval; parent::__construct(new DateTimeIterator($start, $end, $timeUnit)); } public function current() : ?TimePeriod { $dateTimeIterator = $this->getInnerIterator(); $end = $dateTimeIterator->end(); $currentStart = $dateTimeIterator->current(); $currentEnd = $dateTimeIterator->current()->add($dateTimeIterator->unit()); if ($dateTimeIterator->isForward()) { if ($currentEnd->isAfterOrEqualTo($end)) { $currentEnd = $end; } } else { if ($currentEnd->isBeforeOrEqualTo($end)) { $currentEnd = $end; } } return new TimePeriod($currentStart, $currentEnd); } public function accept() : bool { $dateTimeIterator = $this->getInnerIterator(); $current = $dateTimeIterator->current(); $end = $dateTimeIterator->end(); if ($dateTimeIterator->isForward()) { if ($current->isAfterOrEqualTo($end)) { return false; } if ($this->interval->isRightOpen() && $current->add($dateTimeIterator->unit())->isAfterOrEqualTo($end)) { return false; } if ($this->interval->isLeftOpen() && $dateTimeIterator->isFirst()) { return false; } } else { if ($current->isBeforeOrEqualTo($end)) { return false; } if (($this->interval->isRightOpen()) && $dateTimeIterator->isFirst()) { return false; } if (($this->interval->isLeftOpen()) && $current->add($dateTimeIterator->unit())->isBeforeOrEqualTo($end)) { return false; } } return true; } public function key() : int { $dateTimeIterator = $this->getInnerIterator(); if ($dateTimeIterator->isForward()) { if ($this->interval->isLeftOpen()) { return $dateTimeIterator->key() - 1; } } else { if ($this->interval->isRightOpen()) { return $dateTimeIterator->key() - 1; } } return $dateTimeIterator->key(); } /** * @psalm-suppress InvalidReturnType * @psalm-suppress InvalidReturnStatement */ public function getInnerIterator() : DateTimeIterator { /** @phpstan-ignore-next-line */ return parent::getInnerIterator(); } } ================================================ FILE: src/Aeon/Calendar/Gregorian/TimePeriodsSort.php ================================================ type = $type; } /** * @psalm-pure */ public static function asc() : self { return self::startDate(); } /** * @psalm-pure */ public static function desc() : self { return self::startDate(false); } /** * @psalm-pure */ public static function startDate(bool $ascending = true) : self { return new self($ascending ? self::START_DATE_ASC : self::START_DATE_DESC); } /** * @psalm-pure */ public static function endDate(bool $ascending = true) : self { return new self($ascending ? self::END_DATE_ASC : self::END_DATE_DESC); } public function byStartDate() : bool { return $this->type === self::START_DATE_ASC || $this->type === self::START_DATE_DESC; } public function isAscending() : bool { return $this->type === self::START_DATE_ASC || $this->type === self::END_DATE_ASC; } } ================================================ FILE: src/Aeon/Calendar/Gregorian/TimeZone/TimeOffset.php ================================================ negative = $negative; $this->hours = $hours; $this->minutes = $minutes; } /** @psalm-pure */ public static function UTC() : self { return new self(false, 0, 0); } /** @psalm-pure */ public static function fromString(string $offset) : self { if (!\preg_match(self::OFFSET_REGEXP, $offset, $matches)) { throw new InvalidArgumentException("\"{$offset}\" is not a valid UTC Offset."); } return new self($matches[2] === '-', (int) $matches[3], (int) $matches[4]); } /** @psalm-pure */ public static function isValid(string $offset) : bool { return (bool) \preg_match(self::OFFSET_REGEXP, $offset, $matches); } /** @psalm-pure */ public static function fromTimeUnit(TimeUnit $timeUnit) : self { return self::fromString( ($timeUnit->isNegative() ? '-' : '+') . \str_pad((string) $timeUnit->inHoursAbs(), 2, '0', STR_PAD_LEFT) . \str_pad((string) $timeUnit->inTimeMinutes(), 2, '0', STR_PAD_LEFT) ); } /** * @return array{hours: int, minutes: int, negative: bool} */ public function __serialize() : array { return [ 'hours' => $this->hours, 'minutes' => $this->minutes, 'negative' => $this->negative, ]; } public function toString() : string { return ($this->negative ? '-' : '+') . \str_pad((string) $this->hours, 2, '0', STR_PAD_LEFT) . ':' . \str_pad((string) $this->minutes, 2, '0', STR_PAD_LEFT); } public function toTimeUnit() : TimeUnit { return $this->negative ? TimeUnit::minutes($this->minutes)->add(TimeUnit::hours($this->hours))->invert() : TimeUnit::minutes($this->minutes)->add(TimeUnit::hours($this->hours)); } public function toDateTimeZone() : \DateTimeZone { return new \DateTimeZone($this->toString()); } public function isUTC() : bool { return $this->hours === 0 && $this->minutes === 0; } /** * @infection-ignore-all * * @deprecated Use `isEqualTo` instead. Will be removed with 2.0 */ public function isEqual(self $timeOffset) : bool { return $this->isEqualTo($timeOffset); } public function isEqualTo(self $timeOffset) : bool { return $this->negative === $timeOffset->negative && $this->hours === $timeOffset->hours && $this->minutes === $timeOffset->minutes; } } ================================================ FILE: src/Aeon/Calendar/Gregorian/TimeZone.php ================================================ name = $name; $this->type = $type; } /** * @throws InvalidArgumentException * * @psalm-pure */ public static function UTC() : self { return self::abbreviation('UTC'); } /** * @psalm-suppress PossiblyFalseIterator * * @psalm-pure */ public static function id(string $identifier) : self { $normalized = \strtolower($identifier); if ($normalized === 'utc') { throw new InvalidArgumentException('"UTC" is timezone abbreviation, not identifier.'); } /** @var string $dateTimeZoneIdentifier */ foreach (\DateTimeZone::listIdentifiers() as $dateTimeZoneIdentifier) { $normalizedDateTimeZoneIdentifier = \strtolower($dateTimeZoneIdentifier); if (\strtolower($dateTimeZoneIdentifier) === $normalized) { return new self($dateTimeZoneIdentifier, self::TYPE_IDENTIFIER); } if (\str_replace(['/', '_'], '', $normalizedDateTimeZoneIdentifier) === $normalized) { return new self($dateTimeZoneIdentifier, self::TYPE_IDENTIFIER); } } throw new InvalidArgumentException("\"{$identifier}\" is not a valid timezone identifier."); } /** * @psalm-suppress PossiblyFalseArgument * * @psalm-pure */ public static function abbreviation(string $abbreviation) : self { $normalized = \strtoupper($abbreviation); /** * @var string $dateTimeZoneAbbreviation */ foreach (\array_keys(\DateTimeZone::listAbbreviations()) as $dateTimeZoneAbbreviation) { if (\strtoupper($dateTimeZoneAbbreviation) === $normalized) { return new self(\strtoupper($dateTimeZoneAbbreviation), self::TYPE_ABBREVIATION); } } throw new InvalidArgumentException("\"{$abbreviation}\" is not a valid timezone abbreviation."); } /** * @psalm-pure */ public static function offset(string $offset) : self { if (!TimeOffset::isValid($offset)) { throw new InvalidArgumentException("\"{$offset}\" is not a valid time offset."); } return new self(TimeOffset::fromString($offset)->toString(), self::TYPE_OFFSET); } /** * @psalm-pure */ public static function fromString(string $name) : self { try { return self::id($name); } catch (InvalidArgumentException $identifierException) { try { return self::abbreviation($name); } catch (InvalidArgumentException $abbreviationException) { try { return self::offset($name); } catch (InvalidArgumentException $offsetException) { throw new InvalidArgumentException("\"{$name}\" is not a valid time zone."); } } } } /** * @psalm-pure */ public static function isValid(string $name) : bool { try { new \DateTimeZone($name); return true; } catch (\Exception $e) { return false; } } /** * @psalm-pure */ public static function fromDateTimeZone(\DateTimeZone $dateTimeZone) : self { $name = $dateTimeZone->getName(); $type = self::TYPE_IDENTIFIER; if ($name === 'UTC') { $type = self::TYPE_ABBREVIATION; } if (TimeOffset::isValid($name)) { $type = self::TYPE_OFFSET; } if (\timezone_name_from_abbr($name) !== false) { $type = self::TYPE_ABBREVIATION; } return new self($name, $type); } /** * @psalm-pure * * @psalm-suppress PossiblyFalseArgument * * @return array */ public static function allIdentifiers() : array { return \array_map( fn (string $identifier) : self => self::id($identifier), \array_filter(\DateTimeZone::listIdentifiers(), fn (string $identifier) : bool => $identifier !== 'UTC') ); } /** * @psalm-pure * * @psalm-suppress PossiblyFalseArgument * * @return array */ public static function allAbbreviations() : array { /** @var array $abbreviations */ $abbreviations = \array_keys(\DateTimeZone::listAbbreviations()); return \array_map( fn (string $abbreviation) : self => self::abbreviation($abbreviation), \array_filter($abbreviations, fn (string $abbreviation) : bool => \strlen($abbreviation) > 1) ); } /** * @param string $name * @param array $arguments * * @psalm-pure */ public static function __callStatic(string $name, array $arguments) : self { try { return self::id($name); } catch (InvalidArgumentException $identifierException) { try { return self::abbreviation($name); } catch (InvalidArgumentException $abbreviationException) { throw new InvalidArgumentException("\"{$name}\" is not a valid time zone identifier or abbreviation."); } } } /** * @return array{name: string, type: int} */ public function __serialize() : array { return [ 'name' => $this->name, 'type' => $this->type, ]; } /** * @param array{name: string, type: int} $data */ public function __unserialize(array $data) : void { $this->name = $data['name']; $this->type = $data['type']; } public function isOffset() : bool { return $this->type === self::TYPE_OFFSET; } public function isAbbreviation() : bool { return $this->type === self::TYPE_ABBREVIATION; } public function isIdentifier() : bool { return $this->type === self::TYPE_IDENTIFIER; } public function toDateTimeZone() : \DateTimeZone { return new \DateTimeZone($this->name); } public function name() : string { return $this->name; } /** * Offset depends on date because daylight & saving time will have it different and * the only way to get it is to take it from date time. */ public function timeOffset(DateTime $dateTime) : TimeOffset { return TimeOffset::fromTimeUnit(TimeUnit::seconds($this->toDateTimeZone()->getOffset($dateTime->toDateTimeImmutable()))); } } ================================================ FILE: src/Aeon/Calendar/Gregorian/Year.php ================================================ year = $year; $this->months = new YearMonths($this); } /** * @psalm-pure * * @psalm-suppress ImpureMethodCall */ public static function fromDateTime(\DateTimeInterface $dateTime) : self { return new self((int) $dateTime->format('Y')); } /** * @psalm-pure * * @psalm-suppress ImpureMethodCall */ public static function fromString(string $date) : self { try { if (\is_numeric($date)) { return new self((int) $date); } return self::fromDateTime(new \DateTimeImmutable($date)); } catch (\Exception $e) { throw new InvalidArgumentException("Value \"{$date}\" is not valid year format."); } } /** * @return array{year:int} */ public function __debugInfo() : array { return [ 'year' => $this->year, ]; } /** * @return array{year:int} */ public function __serialize() : array { return [ 'year' => $this->year, ]; } /** * @param array{year:int} $data */ public function __unserialize(array $data) : void { $this->year = $data['year']; $this->months = new YearMonths($this); } public function toString() : string { return (string) $this->year; } public function january() : Month { return $this->months()->byNumber(1); } public function february() : Month { return $this->months()->byNumber(2); } public function march() : Month { return $this->months()->byNumber(3); } public function april() : Month { return $this->months()->byNumber(4); } public function may() : Month { return $this->months()->byNumber(5); } public function june() : Month { return $this->months()->byNumber(6); } public function july() : Month { return $this->months()->byNumber(7); } public function august() : Month { return $this->months()->byNumber(8); } public function september() : Month { return $this->months()->byNumber(9); } public function october() : Month { return $this->months()->byNumber(10); } public function november() : Month { return $this->months()->byNumber(11); } public function december() : Month { return $this->months()->byNumber(12); } public function months() : YearMonths { return $this->months; } public function number() : int { return $this->year; } /** * @infection-ignore-all * * @deprecated Use `add` instead. Will be removed with 2.0 */ public function plus(int $years) : self { return $this->add($years); } public function add(int $years) : self { return new self($this->year + $years); } /** * @infection-ignore-all * * @deprecated Use `sub` instead. Will be removed with 2.0 */ public function minus(int $years) : self { return $this->sub($years); } public function sub(int $years) : self { return new self($this->year - $years); } public function next() : self { return new self($this->year + 1); } public function previous() : self { return new self($this->year - 1); } public function numberOfMonths() : int { return 12; } public function numberOfDays() : int { return $this->isLeap() ? 366 : 365; } public function quarter(int $number) : Quarter { switch ($number) { case 1: return new Quarter($number, $this->months()->slice(0, 3)); case 2: return new Quarter($number, $this->months()->slice(3, 3)); case 3: return new Quarter($number, $this->months()->slice(6, 3)); case 4: return new Quarter($number, $this->months()->slice(9, 3)); default: throw new InvalidArgumentException('Quarter number must be greater or equal 1 and less or equal than 4'); } } /** * @param callable(Day $day) : void $iterator * * @psalm-param pure-callable(Day $day) : void $iterator * * @return array */ public function mapDays(callable $iterator) : array { /** @psalm-suppress ImpureFunctionCall */ return \array_map( $iterator, \array_merge( ...\array_map( fn (int $month) : array => $this->months()->byNumber($month)->days()->all(), \range(1, 12) ) ) ); } /** * @param callable(Day $day) : bool $iterator * * @psalm-param pure-callable(Day $day) : bool $iterator * * @return Days */ public function filterDays(callable $iterator) : Days { /** @psalm-suppress ImpureFunctionCall */ return Days::fromArray(...\array_filter( \array_merge( ...\array_map( fn (int $month) : array => $this->months()->byNumber($month)->days()->all(), \range(1, 12) ) ), $iterator )); } public function isLeap() : bool { return $this->year % 4 == 0 && ($this->year % 100 != 0 || $this->year % 400 == 0); } public function toDateTimeImmutable() : \DateTimeImmutable { return new \DateTimeImmutable(\sprintf('%d-01-01 00:00:00.000000 UTC', $this->number())); } /** * @infection-ignore-all * * @deprecated Use `isEqualTo` instead. Will be removed with 2.0 */ public function isEqual(self $year) : bool { return $this->isEqualTo($year); } public function isEqualTo(self $year) : bool { return $this->number() === $year->number(); } public function isBefore(self $year) : bool { return $this->number() < $year->number(); } /** * @infection-ignore-all * * @deprecated Use `isBeforeOrEqualTo` instead. Will be removed with 2.0 */ public function isBeforeOrEqual(self $year) : bool { return $this->isBeforeOrEqualTo($year); } public function isBeforeOrEqualTo(self $year) : bool { return $this->number() <= $year->number(); } public function isAfter(self $year) : bool { return $this->number() > $year->number(); } /** * @infection-ignore-all * * @deprecated Use `isAfterOrEqualTo` instead. Will be removed with 2.0 */ public function isAfterOrEqual(self $year) : bool { return $this->isAfterOrEqualTo($year); } public function isAfterOrEqualTo(self $year) : bool { return $this->number() >= $year->number(); } public function iterate(self $destination, Interval $interval) : Years { return $this->isAfter($destination) ? $this->since($destination, $interval) : $this->until($destination, $interval); } public function until(self $month, Interval $interval) : Years { if ($this->isAfter($month)) { throw new InvalidArgumentException( \sprintf( '%d is after %d', $this->number(), $month->number(), ) ); } /** * @var array $years * * @psalm-suppress ImpureMethodCall * @psalm-suppress ImpureFunctionCall */ $years = \iterator_to_array( new DateTimeIntervalIterator( $this->january()->firstDay()->midnight(TimeZone::UTC()), $month->january()->firstDay()->midnight(TimeZone::UTC()), RelativeTimeUnit::year(), $interval ) ); return new Years( ...\array_map( function (DateTime $dateTime) : self { return $dateTime->year(); }, $years ) ); } public function since(self $month, Interval $interval) : Years { if ($this->isBefore($month)) { throw new InvalidArgumentException( \sprintf( '%d is before %d', $this->number(), $month->number(), ) ); } /** * @var array $years * * @psalm-suppress ImpureMethodCall * @psalm-suppress ImpureFunctionCall */ $years = \iterator_to_array( new DateTimeIntervalIterator( $this->january()->firstDay()->midnight(TimeZone::UTC()), $month->january()->firstDay()->midnight(TimeZone::UTC()), RelativeTimeUnit::year()->toNegative(), $interval ) ); return new Years( ...\array_map( function (DateTime $dateTime) : self { return $dateTime->year(); }, \array_reverse($years) ) ); } public function distance(self $to) : TimeUnit { return (new TimePeriod($this->january()->firstDay()->midnight(TimeZone::UTC()), $to->january()->firstDay()->midnight(TimeZone::UTC())))->distance(); } public function compareTo(self $year) : int { if ($this->isEqualTo($year)) { return 0; } return $this->isBefore($year) ? -1 : 1; } } ================================================ FILE: src/Aeon/Calendar/Gregorian/YearMonths.php ================================================ year = $year; } public function count() : int { return $this->year->numberOfMonths(); } public function byNumber(int $number) : Month { return new Month($this->year, $number); } /** * @return array */ public function all() : array { /** @psalm-suppress ImpureFunctionCall */ return \array_map( fn (int $monthNumber) : Month => new Month($this->year, $monthNumber), \range(1, $this->year->numberOfMonths()) ); } /** * @param callable(Month $day) : void $iterator * * @psalm-param pure-callable(Month $day) : void $iterator * * @return array */ public function map(callable $iterator) : array { return \array_map( $iterator, $this->all() ); } /** * @param callable(Month $day) : bool $iterator * * @psalm-param pure-callable(Month $day) : bool $iterator * * @return array */ public function filter(callable $iterator) : array { return \array_filter( $this->all(), $iterator ); } public function slice(int $from, int $size) : Months { if ($from < 0) { throw new InvalidArgumentException('Slice out of range.'); } if ($from + $size > 12) { throw new InvalidArgumentException('Slice out of range.'); } return Months::fromArray(...\array_slice($this->all(), $from, $size)); } } ================================================ FILE: src/Aeon/Calendar/Gregorian/Years.php ================================================ * @implements \ArrayAccess */ final class Years implements \ArrayAccess, \Countable, \IteratorAggregate { /** * @var array */ private array $years; public function __construct(Year ...$years) { $this->years = $years; } public function offsetExists($offset) : bool { return isset($this->all()[\intval($offset)]); } public function offsetGet($offset) : ?Year { return isset($this->all()[\intval($offset)]) ? $this->all()[\intval($offset)] : null; } public function offsetSet($offset, $value) : void { throw new \RuntimeException(__CLASS__ . ' is immutable.'); } public function offsetUnset($offset) : void { throw new \RuntimeException(__CLASS__ . ' is immutable.'); } /** * @return array */ public function all() : array { return $this->years; } /** * @param callable(Year $year) : mixed $iterator * * @psalm-param pure-callable(Year $year) : mixed $iterator * * @return array */ public function map(callable $iterator) : array { return \array_map($iterator, $this->all()); } /** * @param callable(Year $year) : bool $iterator * * @psalm-param pure-callable(Year $year) : bool $iterator */ public function filter(callable $iterator) : self { return new self(...\array_filter($this->all(), $iterator)); } public function count() : int { return \count($this->all()); } public function getIterator() : \Traversable { return new \ArrayIterator($this->all()); } } ================================================ FILE: src/Aeon/Calendar/RelativeTimeUnit.php ================================================ months = $months; $this->years = $years; } /** @psalm-pure */ public static function month() : self { return new self(1, null); } /** @psalm-pure */ public static function months(int $number) : self { return new self($number, null); } /** @psalm-pure */ public static function years(int $number) : self { return new self(null, $number); } /** @psalm-pure */ public static function year() : self { return new self(null, 1); } public function toDateInterval() : \DateInterval { $dateInterval = new \DateInterval(\sprintf('P%dY%dM', $this->inYears() ? \abs($this->inYears()) : 0, $this->inCalendarMonths() ? \abs($this->inCalendarMonths()) : 0)); if ($this->isNegative()) { /** @psalm-suppress ImpurePropertyAssignment */ $dateInterval->invert = 1; } return $dateInterval; } public function invert() : self { if ($this->years) { return $this->isNegative() ? self::years(\abs($this->years)) : self::years(-$this->years); } /** * @psalm-suppress PossiblyNullArgument * * @phpstan-ignore-next-line */ return $this->isNegative() ? self::months(\abs($this->months)) : self::months(-$this->months); } public function inCalendarMonths() : int { if ($this->months === null) { return 0; } return \abs($this->months % 12); } public function isNegative() : bool { return $this->years < 0 || $this->months < 0; } /** * @psalm-suppress PossiblyNullOperand * @psalm-suppress InvalidNullableReturnType */ public function inYears() : int { if ($this->years !== null) { return $this->years; } /** * @psalm-suppress PossiblyNullArgument * * @phpstan-ignore-next-line */ $years = (int) \floor(\abs($this->months) / 12); return $this->isNegative() ? -$years : $years; } public function toNegative() : self { if ($this->isNegative()) { return $this; } return $this->invert(); } public function toPositive() : self { if (!$this->isNegative()) { return $this; } return $this->invert(); } /** * @psalm-suppress NullableReturnStatement * @psalm-suppress InvalidNullableReturnType */ public function inMonths() : int { if ($this->years !== null) { return $this->years * 12; } /** * @phpstan-ignore-next-line */ return $this->months; } } ================================================ FILE: src/Aeon/Calendar/Stopwatch.php ================================================ */ private array $laps; /** * @var null|array{int, int} */ private ?array $end; public function __construct() { $this->start = null; $this->laps = []; $this->end = null; } public function isStarted() : bool { return $this->start !== null; } public function isStopped() : bool { return $this->end !== null; } /** * Stopwatch::lap() used once will generate two laps, first between start and lap[0] and * second between lap[0] and end. */ public function laps() : int { return \count($this->laps) > 0 ? \count($this->laps) + 1 : 0; } public function start() : void { $this->start = \hrtime(false); } public function lap() : void { if ($this->start === null) { throw new Exception('Stopwatch not started'); } $this->laps[] = \hrtime(false); } public function stop() : void { if ($this->start === null) { throw new Exception('Stopwatch not started'); } if ($this->end !== null) { throw new Exception('Stopwatch already stopped'); } $this->end = \hrtime(false); } public function elapsedTime(int $lap) : TimeUnit { if ($this->start === null) { throw new Exception('Stopwatch not started'); } if (\count($this->laps) === 0) { throw new Exception('Stopwatch does not have any laps.'); } if ($lap === 1) { return $this->firstLapElapsedTime(); } if ($lap - 1 === \count($this->laps)) { return $this->lastLapElapsedTime(); } if (!isset($this->laps[$lap - 1])) { throw new Exception(\sprintf('Lap %d not exists', $lap)); } return HRTime::convert($this->laps[$lap - 1][0], $this->laps[$lap - 1][1]) ->sub(HRTime::convert($this->laps[$lap - 2][0], $this->laps[$lap - 2][1])); } public function firstLapElapsedTime() : TimeUnit { if ($this->start === null) { throw new Exception('Stopwatch not started'); } if (\count($this->laps) === 0) { throw new Exception('Stopwatch does not have any laps.'); } return HRTime::convert($this->laps[0][0], $this->laps[0][1]) ->sub(HRTime::convert($this->start[0], $this->start[1])); } public function lastLapElapsedTime() : TimeUnit { if ($this->start === null) { throw new Exception('Stopwatch not started'); } if ($this->end === null) { throw new Exception('Stopwatch not stopped'); } if (\count($this->laps) === 0) { throw new Exception('Stopwatch does not have any laps.'); } return HRTime::convert($this->end[0], $this->end[1]) ->sub(HRTime::convert(\end($this->laps)[0], \end($this->laps)[1])); } public function totalElapsedTime() : TimeUnit { if ($this->start === null) { throw new Exception('Stopwatch not started'); } if ($this->end === null) { throw new Exception('Stopwatch not stopped'); } return HRTime::convert($this->end[0], $this->end[1]) ->sub(HRTime::convert($this->start[0], $this->start[1])); } } ================================================ FILE: src/Aeon/Calendar/TimeUnit/HRTime.php ================================================ */ private static ?\ReflectionClass $reflectionClass = null; private ?int $seconds = null; private ?int $microsecond = null; private ?\DateInterval $dateInterval = null; private bool $negative = false; private function __construct(bool $negative, int $seconds, int $microsecond) { if ($seconds < 0) { throw new InvalidArgumentException('Seconds must be greater or equal 0, got ' . $seconds); } if ($microsecond < 0 || $microsecond >= self::MICROSECONDS_IN_SECOND) { throw new InvalidArgumentException('Microsecond must be greater or equal 0 and less than 1000000, got ' . $microsecond); } $this->negative = $negative; $this->seconds = $seconds; $this->microsecond = $microsecond; $this->dateInterval = null; } /** * @psalm-pure * Create from number of seconds with 6 decimal point precision. * 0.500000 is half of the second, 500000 represents number of microseconds. */ public static function precise(float $seconds) : self { $secondsString = \number_format(\round($seconds, self::PRECISION_MICROSECOND, PHP_ROUND_HALF_UP), self::PRECISION_MICROSECOND, '.', ''); $secondsStringParts = \explode('.', $secondsString); return new self( \floatval($secondsString) < 0, \abs(\intval($secondsStringParts[0])), \abs(\intval($secondsStringParts[1])), ); } /** * @psalm-pure * * @psalm-suppress ImpureMethodCall * @psalm-suppress ImpurePropertyFetch * @psalm-suppress ImpureStaticProperty * @psalm-suppress ImpurePropertyAssignment * @psalm-suppress InaccessibleProperty * * Limitations: TimeUnit can't be created from relative DateIntervals like \DateInterval::createFromDateString('4 months') * or \DateInterval::createFromDateString('1 years'). It's because years and months are can't be precisely * converted into seconds/days/hours. */ public static function fromDateInterval(\DateInterval $dateInterval) : self { if ($dateInterval->y && !$dateInterval->days) { throw new Exception('Can\'t convert ' . $dateInterval->format('P%yY%mM%dDT%hH%iM%sS') . ' precisely to time unit because year can\'t be directly converted to number of seconds.'); } if ($dateInterval->m && !$dateInterval->days) { throw new Exception('Can\'t convert ' . $dateInterval->format('P%yY%mM%dDT%hH%iM%sS') . ' precisely to time unit because month can\'t be directly converted to number of seconds.'); } if (self::$reflectionClass === null) { self::$reflectionClass = new \ReflectionClass(self::class); } $newTimeUnit = self::$reflectionClass->newInstanceWithoutConstructor(); $newTimeUnit->dateInterval = $dateInterval; return $newTimeUnit; } public static function fromDateString(string $dateString) : self { /** @var \DateInterval $interval */ $interval = \DateInterval::createFromDateString($dateString); return self::fromDateInterval($interval); } /** @psalm-pure */ public static function millisecond() : self { return new self(false, 0, self::MICROSECONDS_IN_MILLISECOND); } /** @psalm-pure */ public static function milliseconds(int $milliseconds) : self { return new self( $milliseconds < 0, \abs(\intval($milliseconds / self::MILLISECONDS_IN_SECOND)), \abs(($milliseconds * self::MICROSECONDS_IN_MILLISECOND) % self::MICROSECONDS_IN_SECOND) ); } /** @psalm-pure */ public static function day() : self { return new self(false, self::HOURS_IN_DAY * self::MINUTES_IN_HOUR * self::SECONDS_IN_MINUTE, 0); } /** @psalm-pure */ public static function days(int $days) : self { return new self($days < 0, \abs($days * self::HOURS_IN_DAY * self::MINUTES_IN_HOUR * self::SECONDS_IN_MINUTE), 0); } /** @psalm-pure */ public static function hour() : self { return new self(false, self::MINUTES_IN_HOUR * self::SECONDS_IN_MINUTE, 0); } /** @psalm-pure */ public static function hours(int $hours) : self { return new self($hours < 0, \abs($hours * self::MINUTES_IN_HOUR * self::SECONDS_IN_MINUTE), 0); } /** @psalm-pure */ public static function minute() : self { return new self(false, self::SECONDS_IN_MINUTE, 0); } /** @psalm-pure */ public static function minutes(int $minutes) : self { return new self($minutes < 0, \abs($minutes * self::SECONDS_IN_MINUTE), 0); } /** @psalm-pure */ public static function second() : self { return new self(false, 1, 0); } /** @psalm-pure */ public static function seconds(int $seconds) : self { return new self($seconds < 0, \abs($seconds), 0); } /** @psalm-pure */ public static function negative(int $seconds, int $microsecond) : self { return new self(true, $seconds, $microsecond); } /** @psalm-pure */ public static function positive(int $seconds, int $microsecond) : self { return new self(false, $seconds, $microsecond); } /** * @return array{seconds: int, microsecond: int, negative: bool} */ public function __serialize() : array { return [ 'seconds' => $this->getSeconds(), 'microsecond' => $this->microsecond(), 'negative' => $this->isNegative(), ]; } /** * @psalm-suppress InaccessibleProperty * @psalm-suppress NullableReturnStatement * @psalm-suppress InvalidNullableReturnType */ public function toDateInterval() : \DateInterval { if ($this->dateInterval === null) { $interval = new \DateInterval(\sprintf('PT%dS', $this->getSeconds())); if ($this->negative) { /** @psalm-suppress ImpurePropertyAssignment */ $interval->invert = 1; } if ($this->microsecond) { /** @psalm-suppress ImpurePropertyAssignment */ $interval->f = $this->microsecond / self::MICROSECONDS_IN_SECOND; } $this->dateInterval = $interval; } return $this->dateInterval; } public function isZero() : bool { return $this->getSeconds() === 0 && $this->microsecond() === 0; } public function isNegative() : bool { /** @psalm-suppress UnusedMethodCall */ $this->getSeconds(); return $this->negative; } public function isPositive() : bool { return !$this->isNegative(); } public function add(self $timeUnit) : self { return self::precise((float) (PreciseCalculator::initialize(self::PRECISION_MICROSECOND)->add($this->inSecondsPrecise(), $timeUnit->inSecondsPrecise()))); } public function sub(self $timeUnit) : self { return self::precise((float) (PreciseCalculator::initialize(self::PRECISION_MICROSECOND)->sub($this->inSecondsPrecise(), $timeUnit->inSecondsPrecise()))); } public function multiply(self $multiplier) : self { return self::precise((float) (PreciseCalculator::initialize(self::PRECISION_MICROSECOND)->multiply($this->inSecondsPrecise(), $multiplier->inSecondsPrecise()))); } public function divide(self $divider) : self { return self::precise((float) (PreciseCalculator::initialize(self::PRECISION_MICROSECOND)->divide($this->inSecondsPrecise(), $divider->inSecondsPrecise()))); } public function modulo(self $divider) : self { return self::precise((float) (PreciseCalculator::initialize(self::PRECISION_MICROSECOND)->modulo($this->inSecondsPrecise(), $divider->inSecondsPrecise()))); } public function isGreaterThan(self $timeUnit) : bool { return PreciseCalculator::initialize(self::PRECISION_MICROSECOND)->isGreaterThan($this->inSecondsPrecise(), $timeUnit->inSecondsPrecise()); } /** @deprecated Use `isGreaterThanOrEqualTo` instead. Will be removed with 2.0 */ public function isGreaterThanEq(self $timeUnit) : bool { return $this->isGreaterThanOrEqualTo($timeUnit); } public function isGreaterThanOrEqualTo(self $timeUnit) : bool { return PreciseCalculator::initialize(self::PRECISION_MICROSECOND)->isGreaterThanOrEqualTo($this->inSecondsPrecise(), $timeUnit->inSecondsPrecise()); } public function isLessThan(self $timeUnit) : bool { return PreciseCalculator::initialize(self::PRECISION_MICROSECOND)->isLessThan($this->inSecondsPrecise(), $timeUnit->inSecondsPrecise()); } /** @deprecated Use `isLessThanOrEqualTo` instead. Will be removed with 2.0 */ public function isLessThanEq(self $timeUnit) : bool { return $this->isLessThanOrEqualTo($timeUnit); } public function isLessThanOrEqualTo(self $timeUnit) : bool { return PreciseCalculator::initialize(self::PRECISION_MICROSECOND)->isLessThanOrEqualTo($this->inSecondsPrecise(), $timeUnit->inSecondsPrecise()); } /** * @infection-ignore-all * * @deprecated Use `isEqualTo` instead. Will be removed with 2.0 */ public function isEqual(self $timeUnit) : bool { return $this->isEqualTo($timeUnit); } public function isEqualTo(self $timeUnit) : bool { return PreciseCalculator::initialize(self::PRECISION_MICROSECOND)->isEqualTo($this->inSecondsPrecise(), $timeUnit->inSecondsPrecise()); } /** * @psalm-suppress InvalidNullableReturnType */ public function inSeconds() : int { return $this->isNegative() ? -$this->getSeconds() : $this->getSeconds(); } public function inSecondsPrecise() : string { return \sprintf( '%s%d.%s', $this->isNegative() === true ? '-' : '', $this->getSeconds(), $this->microsecondString() ); } public function inSecondsAbs() : int { return \abs($this->getSeconds()); } public function inTimeSeconds() : int { return \abs($this->getSeconds() % 60); } public function inHours() : int { return $this->isNegative() ? -\intval(($this->getSeconds() / self::SECONDS_IN_MINUTE) / self::MINUTES_IN_HOUR) : \intval(($this->getSeconds() / self::SECONDS_IN_MINUTE) / self::MINUTES_IN_HOUR); } public function inHoursAbs() : int { return \abs($this->inHours()); } public function inTimeHours() : int { return \abs($this->inHours() % 24); } public function inMinutes() : int { return $this->negative ? -\intval($this->getSeconds() / self::SECONDS_IN_MINUTE) : \intval($this->getSeconds() / self::SECONDS_IN_MINUTE); } public function inMinutesAbs() : int { return \abs($this->inMinutes()); } public function inTimeMinutes() : int { return \abs($this->inMinutes() % 60); } public function inDays() : int { return $this->isNegative() ? -\intval((($this->getSeconds() / self::SECONDS_IN_MINUTE) / self::MINUTES_IN_HOUR) / self::HOURS_IN_DAY) : \intval((($this->getSeconds() / self::SECONDS_IN_MINUTE) / self::MINUTES_IN_HOUR) / self::HOURS_IN_DAY); } public function inDaysAbs() : int { return \abs($this->inDays()); } /** * @psalm-suppress NullableReturnStatement * @psalm-suppress InvalidNullableReturnType * * Number of microseconds from last full second to the next full second. * Do not use this method to combine float seconds because for 50000 it returns 50000 not "050000". */ public function microsecond() : int { /** @psalm-suppress UnusedMethodCall */ $this->getSeconds(); /** @phpstan-ignore-next-line */ return $this->microsecond; } /** * Number of microseconds from last full second to the next full second. * Use this method to combine float seconds because for 50000 it returns "050000" not 50000. */ public function microsecondString() : string { return \str_pad((string) $this->microsecond(), self::PRECISION_MICROSECOND, '0', STR_PAD_LEFT); } public function inMilliseconds() : int { return $this->isNegative() ? -($this->getSeconds() * 1000 + \intval($this->microsecond() / self::MICROSECONDS_IN_MILLISECOND)) : ($this->getSeconds() * 1000 + \intval($this->microsecond() / self::MICROSECONDS_IN_MILLISECOND)); } public function inTimeMilliseconds() : int { return \abs($this->inMilliseconds() % 1000); } public function inMillisecondsAbs() : int { return \abs($this->inMilliseconds()); } public function invert() : self { return new self(!$this->isNegative(), $this->getSeconds(), $this->microsecond()); } public function toNegative() : self { if ($this->isNegative()) { return $this; } return $this->invert(); } public function toPositive() : self { if (!$this->isNegative()) { return $this; } return $this->invert(); } /** * @psalm-suppress PossiblyNullArgument * @psalm-suppress PossiblyNullPropertyFetch * @psalm-suppress InaccessibleProperty * @psalm-suppress NullableReturnStatement * @psalm-suppress InvalidNullableReturnType */ private function getSeconds() : int { if ($this->seconds === null) { /** @phpstan-ignore-next-line */ $timeUnit = self::days($this->dateInterval->days ? \intval($this->dateInterval->days) : $this->dateInterval->d) /** @phpstan-ignore-next-line */ ->add(self::hours($this->dateInterval->h)) /** @phpstan-ignore-next-line */ ->add(self::minutes($this->dateInterval->i)) /** @phpstan-ignore-next-line */ ->add(self::seconds($this->dateInterval->s)) /** @phpstan-ignore-next-line */ ->add(self::precise($this->dateInterval->f)); /** @phpstan-ignore-next-line */ $timeUnit = $this->dateInterval->invert === 1 ? $timeUnit->invert() : $timeUnit; $this->negative = $timeUnit->isNegative(); $this->seconds = $timeUnit->getSeconds(); $this->microsecond = $timeUnit->microsecond(); } return $this->seconds; } } ================================================ FILE: src/Aeon/Calendar/Unit.php ================================================ */ public static function add_data_provider() : \Generator { yield ['3.000000', 2.0, 1.0]; yield ['3.000000', 2, 1]; yield ['-1.000000', -2, 1]; yield ['0.000200', 0.000_100, 0.000_100]; yield ['0.000201', 0.000_101, 0.000_100]; yield ['0.000000', 0.000_000, 0.000_000_1]; yield ['0.000000', 0.000_000_49, 0.000_000_1]; } /** * @return \Generator */ public static function sub_provider() : \Generator { yield ['1.000000', 2.0, 1.0]; yield ['1.000000', 2, 1]; yield ['0.000000', 0.000_100, 0.000_100]; yield ['0.000001', 0.000_101, 0.000_100]; yield ['-0.000001', 0.000_000, 0.000_001]; yield ['0.000000', 0.000_000_49, 0.000_000_1]; } /** * @return \Generator */ public static function multiply_provider() : \Generator { yield ['2.000000', 2.0, 1.0]; yield ['2.000000', 2, 1]; yield ['0.000000', 0.000_100, 0.000_100]; yield ['0.000000', 0.000_101, 0.000_100]; yield ['0.000000', 0.000_000, 0.000_000_1]; yield ['0.000000', 0.000_000_49, 0.000_000_1]; } /** * @return \Generator */ public static function divide_provider() : \Generator { yield ['1.000000', 1.0, 1.0]; yield ['2.000000', 2, 1]; yield ['1.000000', 0.000_100, 0.000_100]; yield ['1.010000', 0.000_101, 0.000_100]; yield ['0.000000', 0.000_000, 0.000_000_1]; yield ['4.900000', 0.000_000_49, 0.000_000_1]; } /** * @return \Generator */ public static function modulo_provider() : \Generator { yield ['0.000000', 1.0, 1.0]; yield ['0.000000', 2, 1]; yield ['1.000000', 7, 2]; yield ['1.999980', 7, 2.50001]; yield ['0.000000', 0.000_100, 0.000_100]; yield ['0.000001', 0.000_101, 0.000_100]; yield ['0.000000', 0.000_000, 0.000_000_1]; yield ['0.000000', 0.000_000_49, 0.000_000_1]; } /** * @return \Generator */ public static function is_equal_data_provider() : \Generator { yield [false, 2.0, 1.0]; yield [false, 2, 1]; yield [true, 0.000_100, 0.000_100]; yield [false, 0.000_101, 0.000_100]; yield [true, 0.000_000, 0.000_000_1]; yield [true, 0.000_000_49, 0.000_000_1]; } /** * @return \Generator */ public static function is_less_data_provider() : \Generator { yield [false, 2.0, 1.0]; yield [false, 2, 1]; yield [false, 0.000_000, 0.000_000_1]; yield [false, 0.000_000_49, 0.000_000_10]; } /** * @return \Generator */ public static function is_greater_data_provider() : \Generator { yield [true, 2.0, 1.0]; yield [true, 2, 1]; yield [false, 0.000_000, 0.000_000_1]; yield [false, 0.000_000_49, 0.000_000_1]; yield [false, 0.000_000_1, 0.000_000_51]; } /** * @return \Generator */ public static function is_greater_than_eq_data_provider() : \Generator { yield [true, 2.0, 1.0]; yield [true, 2, 1]; yield [true, 0.000_000, 0.000_000_1]; yield [true, 0.000_000_49, 0.000_000_1]; } /** * @return \Generator */ public static function is_less_than_eq_data_provider() : \Generator { yield [false, 2.0, 1.0]; yield [false, 2, 1]; yield [true, 0.000_000, 0.000_000_1]; yield [true, 0.000_000_49, 0.000_000_1]; yield [true, 0.000_000_1, 0.000_000_51]; } #[DataProvider('add_data_provider')] public function test_add(string $result, float $value, float $nextValue) : void { $this->assertSame($result, (new BCMathCalculator(6))->add(\number_format($value, 9), \number_format($nextValue, 9))); } #[DataProvider('sub_provider')] public function test_sub(string $result, float $value, float $nextValue) : void { $this->assertSame($result, (new BCMathCalculator(6))->sub(\number_format($value, 9), \number_format($nextValue, 9))); } #[DataProvider('multiply_provider')] public function test_multiply(string $result, float $value, float $nextValue) : void { $this->assertSame($result, (new BCMathCalculator(6))->multiply(\number_format($value, 9), \number_format($nextValue, 9))); } #[DataProvider('divide_provider')] public function test_divide(string $result, float $value, float $nextValue) : void { $this->assertSame($result, (new BCMathCalculator(6))->divide(\number_format($value, 9), \number_format($nextValue, 9))); } #[DataProvider('modulo_provider')] public function test_modulo(string $result, float $value, float $nextValue) : void { $this->assertSame($result, (new BCMathCalculator(6))->modulo(\number_format($value, 9), \number_format($nextValue, 9))); } #[DataProvider('is_equal_data_provider')] public function test_is_equal(bool $equal, float $value, float $nextValue) : void { $this->assertSame($equal, (new BCMathCalculator(6))->isEqualTo(\number_format($value, 9), \number_format($nextValue, 9))); } #[DataProvider('is_less_data_provider')] public function test_is_less(bool $equal, float $value, float $nextValue) : void { $this->assertSame($equal, (new BCMathCalculator(6))->isLessThan(\number_format($value, 8), \number_format($nextValue, 8))); } #[DataProvider('is_greater_data_provider')] public function test_is_greater(bool $equal, float $value, float $nextValue) : void { $this->assertSame($equal, (new BCMathCalculator(6))->isGreaterThan(\number_format($value, 9), \number_format($nextValue, 9))); } #[DataProvider('is_greater_than_eq_data_provider')] public function test_is_greater_than_eq(bool $equal, float $value, float $nextValue) : void { $this->assertSame($equal, (new BCMathCalculator(6))->isGreaterThanOrEqualTo(\number_format($value, 9), \number_format($nextValue, 9))); } #[DataProvider('is_less_than_eq_data_provider')] public function test_is_less_than_eq(bool $equal, float $value, float $nextValue) : void { $this->assertSame($equal, (new BCMathCalculator(6))->isLessThanOrEqualTo(\number_format($value, 9), \number_format($nextValue, 9))); } public function test_invalid_value_in_divide() : void { $this->expectException(InvalidTypeException::class); (new BCMathCalculator(6))->divide('test', '10'); } public function test_invalid_division_in_divide() : void { $this->expectException(InvalidTypeException::class); (new BCMathCalculator(6))->divide('10', 'invalid'); } public function test_invalid_value_in_modulo() : void { $this->expectException(InvalidTypeException::class); (new BCMathCalculator(6))->modulo('test', '10'); } public function test_invalid_division_in_modulo() : void { $this->expectException(InvalidTypeException::class); (new BCMathCalculator(6))->modulo('10', 'invalid'); } public function test_invalid_value_in_multiply() : void { $this->expectException(InvalidTypeException::class); (new BCMathCalculator(6))->multiply('test', '10'); } public function test_invalid_next_value_in_multiply() : void { $this->expectException(InvalidTypeException::class); (new BCMathCalculator(6))->multiply('10', 'invalid'); } public function test_invalid_value_in_add() : void { $this->expectException(InvalidTypeException::class); (new BCMathCalculator(6))->add('test', '10'); } public function test_invalid_next_value_in_add() : void { $this->expectException(InvalidTypeException::class); (new BCMathCalculator(6))->add('10', 'invalid'); } public function test_invalid_value_in_sub() : void { $this->expectException(InvalidTypeException::class); (new BCMathCalculator(6))->sub('test', '10'); } public function test_invalid_next_value_in_sub() : void { $this->expectException(InvalidTypeException::class); (new BCMathCalculator(6))->sub('10', 'invalid'); } public function test_invalid_value_in_is_greater_than() : void { $this->expectException(InvalidTypeException::class); (new BCMathCalculator(6))->multiply('test', '10'); } public function test_invalid_next_value_in_is_greater_than() : void { $this->expectException(InvalidTypeException::class); (new BCMathCalculator(6))->isGreaterThan('10', 'invalid'); } public function test_invalid_value_in_less_than() : void { $this->expectException(InvalidTypeException::class); (new BCMathCalculator(6))->isLessThan('test', '10'); } public function test_invalid_next_value_in_less_than() : void { $this->expectException(InvalidTypeException::class); (new BCMathCalculator(6))->isLessThan('10', 'invalid'); } public function test_invalid_value_in_is_greater_than_eq() : void { $this->expectException(InvalidTypeException::class); (new BCMathCalculator(6))->isGreaterThanOrEqualTo('test', '10'); } public function test_invalid_next_value_in_is_greater_than_eq() : void { $this->expectException(InvalidTypeException::class); (new BCMathCalculator(6))->isGreaterThanOrEqualTo('10', 'invalid'); } public function test_invalid_value_in_is_less_than_eq() : void { $this->expectException(InvalidTypeException::class); (new BCMathCalculator(6))->isLessThanOrEqualTo('test', '10'); } public function test_invalid_next_value_in_is_less_than_eq() : void { $this->expectException(InvalidTypeException::class); (new BCMathCalculator(6))->isLessThanOrEqualTo('10', 'invalid'); } public function test_invalid_value_in_is_equal() : void { $this->expectException(InvalidTypeException::class); (new BCMathCalculator(6))->isEqualTo('test', '10'); } public function test_invalid_next_value_in_is_equal() : void { $this->expectException(InvalidTypeException::class); (new BCMathCalculator(6))->isEqualTo('10', 'invalid'); } public function test_division_by_zero() : void { $this->expectException(\LogicException::class); (new BCMathCalculator(6))->divide('10', '0'); } public function test_mod_by_zero() : void { $this->expectException(\LogicException::class); (new BCMathCalculator(6))->modulo('10', '0'); } } ================================================ FILE: tests/Aeon/Calculator/Tests/Unit/PHPCalculatorTest.php ================================================ */ public static function add_data_provider() : \Generator { yield ['3.000000', 2.0, 1.0]; yield ['3.000000', 2, 1]; yield ['0.000200', 0.000_100, 0.000_100]; yield ['0.000201', 0.000_101, 0.000_100]; yield ['0.000000', 0.000_000, 0.000_000_1]; yield ['0.000001', 0.000_000_49, 0.000_000_1]; } /** * @return \Generator */ public static function add_sub_provider() : \Generator { yield ['1.000000', 2.0, 1.0]; yield ['1.000000', 2, 1]; yield ['0.000000', 0.000_100, 0.000_100]; yield ['0.000001', 0.000_101, 0.000_100]; yield ['0.000000', 0.000_000, 0.000_000_1]; yield ['0.000000', 0.000_000_49, 0.000_000_1]; } /** * @return \Generator */ public static function multiply_provider() : \Generator { yield ['2.000000', 2.0, 1.0]; yield ['2.000000', 2, 1]; yield ['0.000000', 0.000_100, 0.000_100]; yield ['0.000000', 0.000_101, 0.000_100]; yield ['0.000000', 0.000_000, 0.000_000_1]; yield ['0.000000', 0.000_000_49, 0.000_000_1]; } /** * @return \Generator */ public static function divide_provider() : \Generator { yield ['1.000000', 1.0, 1.0]; yield ['2.000000', 2, 1]; yield ['1.000000', 0.000_100, 0.000_100]; yield ['1.010000', 0.000_101, 0.000_100]; yield ['0.000000', 0.000_000, 0.000_000_1]; yield ['4.900000', 0.000_000_49, 0.000_000_1]; } /** * @return \Generator */ public static function modulo_provider() : \Generator { yield ['0.000000', 1.0, 1.0]; yield ['0.000000', 2, 1]; yield ['1.000000', 7, 2]; yield ['1.999980', 7, 2.50001]; yield ['0.000000', 0.000_100, 0.000_100]; yield ['0.000001', 0.000_101, 0.000_100]; yield ['0.000000', 0.000_000, 0.000_000_1]; yield ['0.000000', 0.000_000_49, 0.000_000_1]; } /** * @return \Generator */ public static function is_equal_data_provider() : \Generator { yield [false, 2.0, 1.0]; yield [false, 2, 1]; yield [true, 0.000_100, 0.000_100]; yield [false, 0.000_101, 0.000_100]; yield [true, 0.000_000, 0.000_000_1]; yield [true, 0.000_000_49, 0.000_000_1]; } /** * @return \Generator */ public static function is_less_data_provider() : \Generator { yield [false, 2.0, 1.0]; yield [false, 2, 1]; yield [false, 0.000_000, 0.000_000_1]; yield [false, 0.000_000_49, 0.000_000_1]; yield [true, 0.000_000_1, 0.000_000_51]; } /** * @return \Generator */ public static function is_greater_data_provider() : \Generator { yield [true, 2.0, 1.0]; yield [true, 2, 1]; yield [false, 0.000_000, 0.000_000_1]; yield [false, 0.000_000_49, 0.000_000_1]; yield [false, 0.000_000_1, 0.000_000_51]; } /** * @return \Generator */ public static function is_greater_than_eq_data_provider() : \Generator { yield [true, 2.0, 1.0]; yield [true, 2, 1]; yield [true, 0.000_000, 0.000_000_1]; yield [true, 0.000_000_49, 0.000_000_1]; yield [false, 0.000_000_1, 0.000_000_51]; } /** * @return \Generator */ public static function is_less_than_eq_data_provider() : \Generator { yield [false, 2.0, 1.0]; yield [false, 2, 1]; yield [true, 0.000_000, 0.000_000_1]; yield [true, 0.000_000_49, 0.000_000_1]; yield [true, 0.000_000_1, 0.000_000_51]; } public function test_precision() : void { $this->assertSame(6, (new PHPCalculator(6))->precision()); } #[DataProvider('add_data_provider')] public function test_add(string $result, float $value, float $nextValue) : void { $this->assertSame($result, (new PHPCalculator(6))->add(\number_format($value, 9), \number_format($nextValue, 9))); } #[DataProvider('add_sub_provider')] public function test_sub(string $result, float $value, float $nextValue) : void { $this->assertSame($result, (new PHPCalculator(6))->sub(\number_format($value, 9), \number_format($nextValue, 9))); } #[DataProvider('multiply_provider')] public function test_multiply(string $result, float $value, float $nextValue) : void { $this->assertSame($result, (new PHPCalculator(6))->multiply(\number_format($value, 9), \number_format($nextValue, 9))); } #[DataProvider('divide_provider')] public function test_divide(string $result, float $value, float $nextValue) : void { $this->assertSame($result, (new PHPCalculator(6))->divide(\number_format($value, 9), \number_format($nextValue, 9))); } #[DataProvider('modulo_provider')] public function test_modulo(string $result, float $value, float $nextValue) : void { $this->assertSame($result, (new PHPCalculator(6))->modulo(\number_format($value, 9), \number_format($nextValue, 9))); } #[DataProvider('is_equal_data_provider')] public function test_is_equal(bool $equal, float $value, float $nextValue) : void { $this->assertSame($equal, (new PHPCalculator(6))->isEqualTo(\number_format($value, 8), \number_format($nextValue, 8))); } #[DataProvider('is_less_data_provider')] public function test_is_less(bool $equal, float $value, float $nextValue) : void { $this->assertSame($equal, (new PHPCalculator(6))->isLessThan(\number_format($value, 8), \number_format($nextValue, 8))); } #[DataProvider('is_greater_data_provider')] public function test_is_greater(bool $equal, float $value, float $nextValue) : void { $this->assertSame($equal, (new PHPCalculator(6))->isGreaterThan(\number_format($value, 8), \number_format($nextValue, 8))); } #[DataProvider('is_greater_than_eq_data_provider')] public function test_is_greater_than_eq(bool $equal, float $value, float $nextValue) : void { $this->assertSame($equal, (new PHPCalculator(6))->isGreaterThanOrEqualTo(\number_format($value, 8), \number_format($nextValue, 8))); } #[DataProvider('is_less_than_eq_data_provider')] public function test_is_less_than_eq(bool $equal, float $value, float $nextValue) : void { $this->assertSame($equal, (new PHPCalculator(6))->isLessThanOrEqualTo(\number_format($value, 8), \number_format($nextValue, 8))); } } ================================================ FILE: tests/Aeon/Calculator/Tests/Unit/PreciseCalculatorTest.php ================================================ assertEquals($calculator, $secondInitialization); $this->assertNotEquals($calculator, $differentPrecision); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Functional/Gregorian/GregorianCalendarTest.php ================================================ now()->timeZone(); $this->assertSame( \date_default_timezone_get(), $timezone instanceof TimeZone ? $timezone->name() : null ); } public function test_current_date() : void { $this->assertSame( (new \DateTimeImmutable())->format('Y-m-d'), (GregorianCalendar::UTC())->currentDay()->toDateTimeImmutable()->format('Y-m-d') ); } public function test_timezone() : void { $this->assertEquals( TimeZone::UTC(), (GregorianCalendar::UTC())->timeZone() ); } public function test_current_year() : void { $this->assertSame( (int) (new \DateTimeImmutable())->format('Y'), (GregorianCalendar::UTC())->currentYear()->number() ); } public function test_current_month() : void { $this->assertSame( (int) (new \DateTimeImmutable())->format('m'), (GregorianCalendar::UTC())->currentMonth()->number() ); } public function test_yesterday() : void { $this->assertSame( (new \DateTimeImmutable('yesterday'))->format('Y-m-d'), GregorianCalendar::UTC()->yesterday()->format('Y-m-d') ); } public function test_tomorrow() : void { $this->assertSame( (new \DateTimeImmutable('tomorrow'))->format('Y-m-d'), (GregorianCalendar::UTC())->tomorrow()->format('Y-m-d') ); } public function test_today_midnight() : void { $this->assertSame( (new \DateTimeImmutable('midnight'))->format('Y-m-d 00:00:00'), (GregorianCalendar::UTC())->now()->midnight()->format('Y-m-d H:i:s') ); } public function test_today_midnight_with_custom_tz() : void { $this->assertSame( (new \DateTimeImmutable('midnight', new \DateTimeZone('Europe/Warsaw')))->format('c'), (new GregorianCalendar(TimeZone::europeWarsaw()))->now()->midnight()->format('c') ); } public function test_today_noon() : void { $this->assertSame( (new \DateTimeImmutable('noon'))->format('Y-m-d 12:00:00'), (GregorianCalendar::UTC())->now()->noon()->format('Y-m-d H:i:s') ); } public function test_today_noon_with_custom_tz() : void { $this->assertSame( (new \DateTimeImmutable('noon', new \DateTimeZone('Europe/Warsaw')))->format('c'), (new GregorianCalendar(TimeZone::europeWarsaw()))->now()->noon()->format('c') ); } public function test_today_end_of_day() : void { $this->assertSame( (new \DateTimeImmutable('midnight'))->format('Y-m-d 23:59:59'), (GregorianCalendar::UTC())->now()->endOfDay()->format('Y-m-d H:i:s') ); } public function test_today_end_of_day_with_custom_tz() : void { $this->assertSame( (new \DateTimeImmutable('tomorrow midnight', new \DateTimeZone('Europe/Warsaw')))->sub(new \DateInterval('PT1S'))->format('c'), (new GregorianCalendar(TimeZone::europeWarsaw()))->now()->endOfDay()->format('c') ); } public function test_iterating_overt_time() : void { $timePeriods = ($calendar = GregorianCalendar::UTC()) ->yesterday() ->midnight() ->until($calendar->tomorrow()->midnight()) ->iterate(TimeUnit::hour(), Interval::closed()) ->map(function (TimePeriod $timePeriod) use (&$iterations) { return $timePeriod->start()->toDateTimeImmutable()->format('Y-m-d H:i:s'); }); $this->assertCount(48, $timePeriods); $this->assertSame( (new \DateTimeImmutable('yesterday midnight'))->format('Y-m-d H:i:s'), $timePeriods[0] ); $this->assertSame( (new \DateTimeImmutable('tomorrow midnight -1 hour'))->format('Y-m-d H:i:s'), $timePeriods[47] ); } public function test_iterating_overt_time_and_taking_every_second_hour() : void { $timePeriods = ($calendar = GregorianCalendar::UTC()) ->yesterday() ->midnight() ->until($calendar->tomorrow()->midnight()) ->iterate(TimeUnit::hour(), Interval::closed()) ->filter(function (TimePeriod $timePeriod) use (&$iterations) : bool { return $timePeriod->start()->time()->hour() % 2 === 0; }); $this->assertCount(24, $timePeriods); } public function test_iterating_overt_time_backward() : void { $timePeriods = ($calendar = GregorianCalendar::UTC()) ->yesterday() ->midnight() ->until($calendar->tomorrow()->midnight()) ->iterateBackward(TimeUnit::hour(), Interval::leftOpen()) ->map(function (TimePeriod $interval) { return $interval->start()->toDateTimeImmutable()->format('Y-m-d H:i:s'); }); $this->assertCount(47, $timePeriods); $this->assertSame( (new \DateTimeImmutable('tomorrow midnight'))->format('Y-m-d H:i:s'), $timePeriods[0] ); $this->assertSame( (new \DateTimeImmutable('yesterday midnight +2 hours'))->format('Y-m-d H:i:s'), $timePeriods[46] ); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/Gregorian/DateTimeIntervalIteratorTest.php ================================================ assertEquals(TimeUnit::day(), $iterator->unit()); $this->assertEquals(Interval::rightOpen(), $iterator->interval()); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $iterator->start()); $this->assertEquals(DateTime::fromString('2020-01-05 00:00:00 UTC'), $iterator->end()); $this->assertTrue($iterator->isForward()); $iterator->rewind(); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $iterator->current()); $this->assertTrue($iterator->hasNext()); $iterator->next(); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $iterator->current()); $this->assertTrue($iterator->hasNext()); $iterator->next(); $this->assertEquals(DateTime::fromString('2020-01-03 00:00:00 UTC'), $iterator->current()); $this->assertTrue($iterator->hasNext()); $iterator->next(); $this->assertEquals(DateTime::fromString('2020-01-04 00:00:00 UTC'), $iterator->current()); $this->assertFalse($iterator->hasNext()); $array = \iterator_to_array($iterator); $this->assertCount(4, $iterator); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $array[0]); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $array[1]); } public function test_forward_even_iteration_left_open() : void { $iterator = new DateTimeIntervalIterator( DateTime::fromString('2020-01-01 00:00:00 UTC'), DateTime::fromString('2020-01-03 00:00:00 UTC'), TimeUnit::day(), Interval::leftOpen() ); $iterator->rewind(); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $iterator->current()); $this->assertTrue($iterator->hasNext()); $iterator->next(); $this->assertEquals(DateTime::fromString('2020-01-03 00:00:00 UTC'), $iterator->current()); $this->assertFalse($iterator->hasNext()); $array = \iterator_to_array($iterator); $this->assertCount(2, $iterator); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $array[0]); $this->assertEquals(DateTime::fromString('2020-01-03 00:00:00 UTC'), $array[1]); } public function test_forward_even_iteration_open() : void { $iterator = new DateTimeIntervalIterator( DateTime::fromString('2020-01-01 00:00:00 UTC'), DateTime::fromString('2020-01-03 00:00:00 UTC'), TimeUnit::day(), Interval::open() ); $iterator->rewind(); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $iterator->current()); $this->assertFalse($iterator->hasNext()); $array = \iterator_to_array($iterator); $this->assertCount(1, $iterator); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $array[0]); } public function test_forward_even_iteration_closed() : void { $iterator = new DateTimeIntervalIterator( DateTime::fromString('2020-01-01 00:00:00 UTC'), DateTime::fromString('2020-01-03 00:00:00 UTC'), TimeUnit::day(), Interval::closed() ); $iterator->rewind(); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $iterator->current()); $this->assertTrue($iterator->hasNext()); $iterator->next(); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $iterator->current()); $this->assertTrue($iterator->hasNext()); $iterator->next(); $this->assertEquals(DateTime::fromString('2020-01-03 00:00:00 UTC'), $iterator->current()); $this->assertFalse($iterator->hasNext()); $array = \iterator_to_array($iterator); $this->assertCount(3, $iterator); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $array[0]); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $array[1]); $this->assertEquals(DateTime::fromString('2020-01-03 00:00:00 UTC'), $array[2]); } public function test_forward_exceeded_iteration_right_open() : void { $iterator = new DateTimeIntervalIterator( DateTime::fromString('2020-01-01 00:00:00 UTC'), DateTime::fromString('2020-01-02 00:00:00 UTC'), TimeUnit::days(2), Interval::rightOpen() ); $iterator->rewind(); $this->assertEquals(null, $iterator->current()); $this->assertFalse($iterator->hasNext()); $this->assertCount(0, $iterator); } public function test_forward_exceeded_iteration_left_open() : void { $iterator = new DateTimeIntervalIterator( DateTime::fromString('2020-01-01 00:00:00 UTC'), DateTime::fromString('2020-01-02 00:00:00 UTC'), TimeUnit::days(2), Interval::leftOpen() ); $iterator->rewind(); $this->assertEquals(null, $iterator->current()); $this->assertFalse($iterator->hasNext()); $this->assertCount(0, $iterator); } public function test_forward_exceeded_iteration_open() : void { $iterator = new DateTimeIntervalIterator( DateTime::fromString('2020-01-01 00:00:00 UTC'), DateTime::fromString('2020-01-02 00:00:00 UTC'), TimeUnit::days(2), Interval::open() ); $iterator->rewind(); $this->assertEquals(null, $iterator->current()); $this->assertFalse($iterator->hasNext()); $this->assertCount(0, $iterator); } public function test_forward_exceeded_iteration_closed() : void { $iterator = new DateTimeIntervalIterator( DateTime::fromString('2020-01-01 00:00:00 UTC'), DateTime::fromString('2020-01-02 00:00:00 UTC'), TimeUnit::days(2), Interval::closed() ); $iterator->rewind(); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $iterator->current()); $this->assertFalse($iterator->hasNext()); $this->assertCount(1, $iterator); $array = \iterator_to_array($iterator); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $array[0]); } public function test_forward_odd_iteration_right_open() : void { $iterator = new DateTimeIntervalIterator( DateTime::fromString('2020-01-01 00:00:00 UTC'), DateTime::fromString('2020-01-04 00:00:00 UTC'), TimeUnit::days(3), Interval::rightOpen() ); $iterator->rewind(); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $iterator->current()); $this->assertFalse($iterator->hasNext()); $array = \iterator_to_array($iterator); $this->assertCount(1, $iterator); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $array[0]); } public function test_forward_odd_iteration_left_open() : void { $iterator = new DateTimeIntervalIterator( DateTime::fromString('2020-01-01 00:00:00 UTC'), DateTime::fromString('2020-01-04 00:00:00 UTC'), TimeUnit::days(3), Interval::leftOpen() ); $iterator->rewind(); $this->assertEquals(DateTime::fromString('2020-01-04 00:00:00 UTC'), $iterator->current()); $this->assertFalse($iterator->hasNext()); $array = \iterator_to_array($iterator); $this->assertCount(1, $iterator); $this->assertEquals(DateTime::fromString('2020-01-04 00:00:00 UTC'), $array[0]); } public function test_forward_odd_iteration_open() : void { $iterator = new DateTimeIntervalIterator( DateTime::fromString('2020-01-01 00:00:00 UTC'), DateTime::fromString('2020-01-04 00:00:00 UTC'), TimeUnit::days(3), Interval::open() ); $iterator->rewind(); $this->assertEquals(null, $iterator->current()); $this->assertFalse($iterator->hasNext()); $this->assertCount(0, $iterator); } public function test_forward_odd_iteration_closed() : void { $iterator = new DateTimeIntervalIterator( DateTime::fromString('2020-01-01 00:00:00 UTC'), DateTime::fromString('2020-01-04 00:00:00 UTC'), TimeUnit::days(3), Interval::closed() ); $iterator->rewind(); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $iterator->current()); $this->assertTrue($iterator->hasNext()); $iterator->next(); $this->assertEquals(DateTime::fromString('2020-01-04 00:00:00 UTC'), $iterator->current()); $this->assertFalse($iterator->hasNext()); $array = \iterator_to_array($iterator); $this->assertCount(2, $iterator); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $array[0]); $this->assertEquals(DateTime::fromString('2020-01-04 00:00:00 UTC'), $array[1]); } public function test_backward_even_iteration_right_open() : void { $iterator = new DateTimeIntervalIterator( DateTime::fromString('2020-01-04 00:00:00 UTC'), DateTime::fromString('2020-01-01 00:00:00 UTC'), TimeUnit::day()->toNegative(), Interval::rightOpen() ); $this->assertFalse($iterator->isForward()); $iterator->rewind(); $this->assertEquals(DateTime::fromString('2020-01-03 00:00:00 UTC'), $iterator->current()); $this->assertTrue($iterator->hasNext()); $iterator->next(); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $iterator->current()); $this->assertTrue($iterator->hasNext()); $iterator->next(); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $iterator->current()); $this->assertFalse($iterator->hasNext()); $array = \iterator_to_array($iterator); $this->assertCount(3, $iterator); $this->assertEquals(DateTime::fromString('2020-01-03 00:00:00 UTC'), $array[0]); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $array[2]); } public function test_backward_even_iteration_left_open() : void { $iterator = new DateTimeIntervalIterator( DateTime::fromString('2020-01-04 00:00:00 UTC'), DateTime::fromString('2020-01-01 00:00:00 UTC'), TimeUnit::day()->toNegative(), Interval::leftOpen() ); $iterator->rewind(); $this->assertEquals(DateTime::fromString('2020-01-04 00:00:00 UTC'), $iterator->current()); $this->assertTrue($iterator->hasNext()); $iterator->next(); $this->assertEquals(DateTime::fromString('2020-01-03 00:00:00 UTC'), $iterator->current()); $this->assertTrue($iterator->hasNext()); $iterator->next(); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $iterator->current()); $this->assertFalse($iterator->hasNext()); $array = \iterator_to_array($iterator); $this->assertCount(3, $iterator); $this->assertEquals(DateTime::fromString('2020-01-04 00:00:00 UTC'), $array[0]); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $array[2]); } public function test_backward_even_iteration_open() : void { $iterator = new DateTimeIntervalIterator( DateTime::fromString('2020-01-04 00:00:00 UTC'), DateTime::fromString('2020-01-01 00:00:00 UTC'), TimeUnit::day()->toNegative(), Interval::open() ); $iterator->rewind(); $this->assertEquals(DateTime::fromString('2020-01-03 00:00:00 UTC'), $iterator->current()); $this->assertTrue($iterator->hasNext()); $iterator->next(); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $iterator->current()); $this->assertFalse($iterator->hasNext()); $array = \iterator_to_array($iterator); $this->assertCount(2, $iterator); $this->assertEquals(DateTime::fromString('2020-01-03 00:00:00 UTC'), $array[0]); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $array[1]); } public function test_backward_even_iteration_closed() : void { $iterator = new DateTimeIntervalIterator( DateTime::fromString('2020-01-04 00:00:00 UTC'), DateTime::fromString('2020-01-01 00:00:00 UTC'), TimeUnit::day()->toNegative(), Interval::closed() ); $iterator->rewind(); $this->assertEquals(DateTime::fromString('2020-01-04 00:00:00 UTC'), $iterator->current()); $this->assertTrue($iterator->hasNext()); $iterator->next(); $this->assertEquals(DateTime::fromString('2020-01-03 00:00:00 UTC'), $iterator->current()); $this->assertTrue($iterator->hasNext()); $iterator->next(); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $iterator->current()); $this->assertTrue($iterator->hasNext()); $iterator->next(); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $iterator->current()); $this->assertFalse($iterator->hasNext()); $array = \iterator_to_array($iterator); $this->assertCount(4, $iterator); $this->assertEquals(DateTime::fromString('2020-01-04 00:00:00 UTC'), $array[0]); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $array[3]); } public function test_backward_exceeded_iteration_right_open() : void { $iterator = new DateTimeIntervalIterator( DateTime::fromString('2020-01-02 00:00:00 UTC'), DateTime::fromString('2020-01-01 00:00:00 UTC'), TimeUnit::days(2)->toNegative(), Interval::rightOpen() ); $iterator->rewind(); $this->assertEquals(null, $iterator->current()); $this->assertFalse($iterator->hasNext()); $this->assertCount(0, $iterator); } public function test_backward_exceeded_iteration_left_open() : void { $iterator = new DateTimeIntervalIterator( DateTime::fromString('2020-01-02 00:00:00 UTC'), DateTime::fromString('2020-01-01 00:00:00 UTC'), TimeUnit::days(2)->toNegative(), Interval::leftOpen() ); $iterator->rewind(); $this->assertEquals(null, $iterator->current()); $this->assertFalse($iterator->hasNext()); $this->assertCount(0, $iterator); } public function test_backward_exceeded_iteration_open() : void { $iterator = new DateTimeIntervalIterator( DateTime::fromString('2020-01-02 00:00:00 UTC'), DateTime::fromString('2020-01-01 00:00:00 UTC'), TimeUnit::days(2)->toNegative(), Interval::open() ); $iterator->rewind(); $this->assertEquals(null, $iterator->current()); $this->assertFalse($iterator->hasNext()); $this->assertCount(0, $iterator); } public function test_backward_exceeded_iteration_closed() : void { $iterator = new DateTimeIntervalIterator( DateTime::fromString('2020-01-02 00:00:00 UTC'), DateTime::fromString('2020-01-01 00:00:00 UTC'), TimeUnit::days(2)->toNegative(), Interval::closed() ); $iterator->rewind(); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $iterator->current()); $this->assertFalse($iterator->hasNext()); $array = \iterator_to_array($iterator); $this->assertCount(1, $iterator); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $array[0]); } public function test_backward_odd_iteration_right_open() : void { $iterator = new DateTimeIntervalIterator( DateTime::fromString('2020-01-04 00:00:00 UTC'), DateTime::fromString('2020-01-01 00:00:00 UTC'), TimeUnit::days(3)->toNegative(), Interval::rightOpen() ); $iterator->rewind(); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $iterator->current()); $this->assertFalse($iterator->hasNext()); $array = \iterator_to_array($iterator); $this->assertCount(1, $iterator); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $array[0]); } public function test_backward_odd_iteration_left_open() : void { $iterator = new DateTimeIntervalIterator( DateTime::fromString('2020-01-04 00:00:00 UTC'), DateTime::fromString('2020-01-01 00:00:00 UTC'), TimeUnit::days(3)->toNegative(), Interval::leftOpen() ); $iterator->rewind(); $this->assertEquals(DateTime::fromString('2020-01-04 00:00:00 UTC'), $iterator->current()); $this->assertFalse($iterator->hasNext()); $array = \iterator_to_array($iterator); $this->assertCount(1, $iterator); $this->assertEquals(DateTime::fromString('2020-01-04 00:00:00 UTC'), $array[0]); } public function test_backward_odd_iteration_open() : void { $iterator = new DateTimeIntervalIterator( DateTime::fromString('2020-01-04 00:00:00 UTC'), DateTime::fromString('2020-01-01 00:00:00 UTC'), TimeUnit::days(3)->toNegative(), Interval::open() ); $iterator->rewind(); $this->assertEquals(null, $iterator->current()); $this->assertFalse($iterator->hasNext()); $this->assertCount(0, $iterator); } public function test_backward_odd_iteration_closed() : void { $iterator = new DateTimeIntervalIterator( DateTime::fromString('2020-01-04 00:00:00 UTC'), DateTime::fromString('2020-01-01 00:00:00 UTC'), TimeUnit::days(3)->toNegative(), Interval::closed() ); $iterator->rewind(); $this->assertEquals(DateTime::fromString('2020-01-04 00:00:00 UTC'), $iterator->current()); $this->assertTrue($iterator->hasNext()); $iterator->next(); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $iterator->current()); $this->assertFalse($iterator->hasNext()); $array = \iterator_to_array($iterator); $this->assertCount(2, $iterator); $this->assertEquals(DateTime::fromString('2020-01-04 00:00:00 UTC'), $array[0]); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $array[1]); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/Gregorian/DateTimeIteratorTest.php ================================================ assertSame(0, $iterator->key()); $iterator->rewind(); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $iterator->current()); $this->assertTrue($iterator->hasNext()); $iterator->next(); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $iterator->current()); $this->assertTrue($iterator->hasNext()); $iterator->next(); $this->assertEquals(DateTime::fromString('2020-01-03 00:00:00 UTC'), $iterator->current()); $this->assertFalse($iterator->hasNext()); $array = \iterator_to_array($iterator); $this->assertCount(3, $iterator); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $array[0]); $this->assertEquals(DateTime::fromString('2020-01-03 00:00:00 UTC'), $array[2]); } public function test_forward_exceeded_iteration() : void { $iterator = new DateTimeIterator( DateTime::fromString('2020-01-01 00:00:00 UTC'), DateTime::fromString('2020-01-02 00:00:00 UTC'), TimeUnit::days(2) ); $iterator->rewind(); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $iterator->current()); $this->assertFalse($iterator->hasNext()); $array = \iterator_to_array($iterator); $this->assertCount(1, $iterator); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $array[0]); } public function test_forward_odd_iteration() : void { $iterator = new DateTimeIterator( DateTime::fromString('2020-01-01 00:00:00 UTC'), DateTime::fromString('2020-01-04 00:00:00 UTC'), TimeUnit::days(3) ); $iterator->rewind(); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $iterator->current()); $this->assertTrue($iterator->hasNext()); $iterator->next(); $this->assertEquals(DateTime::fromString('2020-01-04 00:00:00 UTC'), $iterator->current()); $this->assertFalse($iterator->hasNext()); $array = \iterator_to_array($iterator); $this->assertCount(2, $iterator); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $array[0]); $this->assertEquals(DateTime::fromString('2020-01-04 00:00:00 UTC'), $array[1]); } public function test_backward_even_iteration() : void { $iterator = new DateTimeIterator( DateTime::fromString('2020-01-04 00:00:00 UTC'), DateTime::fromString('2020-01-01 00:00:00 UTC'), TimeUnit::day()->toNegative() ); $iterator->rewind(); $this->assertEquals(DateTime::fromString('2020-01-04 00:00:00 UTC'), $iterator->current()); $this->assertTrue($iterator->hasNext()); $iterator->next(); $this->assertEquals(DateTime::fromString('2020-01-03 00:00:00 UTC'), $iterator->current()); $this->assertTrue($iterator->hasNext()); $iterator->next(); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $iterator->current()); $this->assertTrue($iterator->hasNext()); $iterator->next(); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $iterator->current()); $this->assertFalse($iterator->hasNext()); $array = \iterator_to_array($iterator); $this->assertCount(4, $iterator); $this->assertEquals(DateTime::fromString('2020-01-04 00:00:00 UTC'), $array[0]); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $array[3]); } public function test_backward_exceeded_iteration() : void { $iterator = new DateTimeIterator( DateTime::fromString('2020-01-02 00:00:00 UTC'), DateTime::fromString('2020-01-01 00:00:00 UTC'), TimeUnit::days(2)->toNegative() ); $iterator->rewind(); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $iterator->current()); $this->assertFalse($iterator->hasNext()); $array = \iterator_to_array($iterator); $this->assertCount(1, $iterator); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $array[0]); } public function test_backward_odd_iteration() : void { $iterator = new DateTimeIterator( DateTime::fromString('2020-01-04 00:00:00 UTC'), DateTime::fromString('2020-01-01 00:00:00 UTC'), TimeUnit::days(3)->toNegative() ); $iterator->rewind(); $this->assertEquals(DateTime::fromString('2020-01-04 00:00:00 UTC'), $iterator->current()); $this->assertTrue($iterator->hasNext()); $iterator->next(); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $iterator->current()); $this->assertFalse($iterator->hasNext()); $array = \iterator_to_array($iterator); $this->assertCount(2, $iterator); $this->assertEquals(DateTime::fromString('2020-01-04 00:00:00 UTC'), $array[0]); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $array[1]); } public function test_forward_with_negative_time_unit() : void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Forward DateTimeIterator 2020-01-01 00:00:00+00:00...2020-01-04 00:00:00+00:00 requires positive TimeUnit'); new DateTimeIterator( DateTime::fromString('2020-01-01 00:00:00 UTC'), DateTime::fromString('2020-01-04 00:00:00 UTC'), TimeUnit::days(3)->toNegative() ); } public function test_backward_with_positive_time_unit() : void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Backward DateTimeIterator 2020-01-04 00:00:00+00:00...2020-01-01 00:00:00+00:00 requires negative TimeUnit'); new DateTimeIterator( DateTime::fromString('2020-01-04 00:00:00 UTC'), DateTime::fromString('2020-01-01 00:00:00 UTC'), TimeUnit::days(3) ); } public function test_empty() : void { $iterator = new DateTimeIterator( DateTime::fromString('2020-01-01 00:00:00 UTC'), DateTime::fromString('2020-01-01 00:00:00 UTC'), TimeUnit::day() ); $this->assertFalse($iterator->valid()); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/Gregorian/DateTimeTest.php ================================================ */ public static function creating_datetime_data_provider() : \Generator { yield ['2020-01-01 00:00:00+00:00', DateTime::fromString('2020-01-01 00:00:00+00:00'), 'Y-m-d H:i:sP']; yield ['2020-01-01 00:00:00+00:00', DateTime::create(2020, 01, 01, 00, 00, 00), 'Y-m-d H:i:sP']; yield ['2020-01-01 00:00:00+00:00', DateTime::fromDateTime(new \DateTimeImmutable('2020-01-01 00:00:00+00:00')), 'Y-m-d H:i:sP']; yield ['2020-01-01 00:00:00+00:00', new DateTime(new Day(new Month(new Year(2020), 01), 01), new Time(0, 0, 0), TimeZone::UTC()), 'Y-m-d H:i:sP']; yield ['2020-11-01 02:00:00-08:00', DateTime::fromDateTime(new \DateTimeImmutable('2020-11-01 02:00 America/Los_Angeles')), 'Y-m-d H:i:sP']; yield ['2020-01-01 00:00:00+01:00', DateTime::fromString('2020-01-01 00:00:00+01:00'), 'Y-m-d H:i:sP']; yield ['2020-01-01 00:00:00+01:00', DateTime::create(2020, 01, 01, 00, 00, 00, 0, 'Europe/Warsaw'), 'Y-m-d H:i:sP']; yield ['2020-01-01 00:00:00+01:00', DateTime::fromDateTime(new \DateTimeImmutable('2020-01-01 00:00:00+01:00')), 'Y-m-d H:i:sP']; yield ['2020-01-01 00:00:00+01:00', new DateTime(new Day(new Month(new Year(2020), 01), 01), new Time(0, 0, 0), TimeZone::europeWarsaw()), 'Y-m-d H:i:sP']; } /** * @return \Generator */ public static function creating_datetime_data_provider_from_string() : \Generator { yield ['2020-01-01 00:00:00+00:00', '2020-01 00:00:00', 'Y-m-d H:i:sP']; yield ['2020-01-02 00:00:00+00:00', '2020-01 00:00:00 +1 day', 'Y-m-d H:i:sP']; yield ['2020-01-01 00:00:00+00:00', '2020-01 00:00', 'Y-m-d H:i:sP']; yield ['2020-01-01 00:00:00+00:00.000000', '2020-01 00:00', 'Y-m-d H:i:sP.u']; yield ['2020-01-01 00:00:00+00:00.000500', '2020-01 00:00:00.0005', 'Y-m-d H:i:sP.u']; yield ['1999-10-13 00:00:00', '1999-10-13 00:00:00', 'Y-m-d H:i:s']; yield ['1999-10-13 00:00:00', 'Oct 13 1999 00:00:00', 'Y-m-d H:i:s']; yield ['2000-01-19 00:00:00', '2000-01-19 00:00:00', 'Y-m-d H:i:s']; yield ['2000-01-19 00:00:00', 'Jan 19 2000 00:00:00', 'Y-m-d H:i:s']; yield ['2001-12-21 00:00:00', '2001-12-21 00:00:00', 'Y-m-d H:i:s']; yield ['2001-12-21 00:00:00', 'Dec 21 2001 00:00:00', 'Y-m-d H:i:s']; yield ['2001-12-21 12:16:00', '2001-12-21 12:16', 'Y-m-d H:i:s']; yield ['2001-12-21 12:16:00', 'Dec 21 2001 12:16', 'Y-m-d H:i:s']; yield ['2001-10-22 21:19:58', '2001-10-22 21:19:58', 'Y-m-d H:i:s']; yield ['2001-10-22 21:19:58', '2001-10-22 21:19:58-02', 'Y-m-d H:i:s']; yield ['2001-10-22 21:19:58', '2001-10-22 21:19:58-0213', 'Y-m-d H:i:s']; yield ['2001-10-22 21:19:58', '2001-10-22 21:19:58+02', 'Y-m-d H:i:s']; yield ['2001-10-22 21:19:58', '2001-10-22 21:19:58+0213', 'Y-m-d H:i:s']; yield ['2001-10-22 21:20:58', '2001-10-22T21:20:58-03:40', 'Y-m-d H:i:s']; yield ['2001-10-22 21:19:58', '2001-10-22T211958-2', 'Y-m-d H:i:s']; yield ['2001-10-22 21:19:58', '20011022T211958+0213', 'Y-m-d H:i:s']; yield ['2001-10-22 21:20:00', '20011022T21:20+0215', 'Y-m-d H:i:s']; yield ['1996-12-30 00:00:00', '1997W011 00:00:00', 'Y-m-d H:i:s']; yield ['2004-03-01 05:00:00', '2004W101T05:00+0', 'Y-m-d H:i:s']; // DTS switch +1 hour yield ['2020-03-29 03:30:00+02:00', '2020-03-29 02:30:00 Europe/Warsaw', 'Y-m-d H:i:sP']; // DTS switch -1 hour if (PHP_VERSION_ID >= 81000) { yield ['2020-10-25 02:30:00+02:00', '2020-10-25 02:30:00 Europe/Warsaw', 'Y-m-d H:i:sP']; yield ['2020-10-25 01:30:00+02:00', '2020-10-25 01:30:00 Europe/Warsaw', 'Y-m-d H:i:sP']; } // now yield [(new \DateTimeImmutable('now'))->format('Y-m-d'), 'now', 'Y-m-d']; yield [(new \DateTimeImmutable('now'))->format('Y-m-d'), 'noW', 'Y-m-d']; yield [(new \DateTimeImmutable('now GMT'))->format('Y-m-d'), 'noW GMT', 'Y-m-d']; yield [(new \DateTimeImmutable('now America/Los_Angeles'))->format('Y-m-d'), 'noW America/Los_Angeles', 'Y-m-d']; yield [(new \DateTimeImmutable('now +01:00'))->format('Y-m-d'), 'noW +01:00', 'Y-m-d']; // today yield [(new \DateTimeImmutable('today'))->format('Y-m-d'), 'today ', 'Y-m-d']; yield [(new \DateTimeImmutable('today America/Los_Angeles'))->format('Y-m-d'), 'today America/Los_Angeles', 'Y-m-d']; yield [(new \DateTimeImmutable('today +01:00'))->format('Y-m-d'), 'today +01:00', 'Y-m-d']; yield [(new \DateTimeImmutable('today GMT'))->format('Y-m-d'), 'today GMT', 'Y-m-d']; // noon yield [(new \DateTimeImmutable('noon'))->format('Y-m-d'), ' noON ', 'Y-m-d']; yield [(new \DateTimeImmutable('yesterday noon'))->format('Y-m-d'), 'yesterday noon', 'Y-m-d']; yield [(new \DateTimeImmutable('noon Europe/Warsaw'))->format('Y-m-d'), ' noON Europe/Warsaw', 'Y-m-d']; // tomorrow yield [(new \DateTimeImmutable('tomorrow'))->format('Y-m-d'), 'tomorrow', 'Y-m-d']; yield [(new \DateTimeImmutable('tomorrow midnight'))->format('Y-m-d'), 'tomorrow midnight', 'Y-m-d']; yield [(new \DateTimeImmutable('tomorrow +01:00'))->format('Y-m-d'), 'tomorrow +01:00', 'Y-m-d']; // yesterday yield [(new \DateTimeImmutable('yesterday'))->format('Y-m-d'), 'yesterday', 'Y-m-d']; yield [(new \DateTimeImmutable('yesterday GMT'))->format('Y-m-d'), 'yesterday GMT', 'Y-m-d']; // midnight yield [(new \DateTimeImmutable('midnight'))->format('Y-m-d'), 'midnight', 'Y-m-d']; yield [(new \DateTimeImmutable('midnight PST'))->format('Y-m-d'), 'midnight PST', 'Y-m-d']; yield [(new \DateTimeImmutable('24 week'))->format('Y-m-d'), '24 week', 'Y-m-d']; yield [(new \DateTimeImmutable('today +1 hour'))->format('Y-m-d'), 'today +1 hour', 'Y-m-d']; yield [(new \DateTimeImmutable('tomorrow +1 hour'))->format('Y-m-d'), 'tomorrow +1 hour', 'Y-m-d']; yield [(new \DateTimeImmutable('-2 days'))->format('Y-m-d'), '-2 days', 'Y-m-d']; yield [(new \DateTimeImmutable('Monday'))->format('Y-m-d'), 'Monday', 'Y-m-d']; yield [(new \DateTimeImmutable('Monday next week'))->format('Y-m-d'), 'Monday next week', 'Y-m-d']; yield [(new \DateTimeImmutable('next year'))->format('Y-m-d'), 'next year', 'Y-m-d']; yield [(new \DateTimeImmutable('fifth day'))->format('Y-m-d'), 'fifth day', 'Y-m-d']; yield [(new \DateTimeImmutable('last day'))->format('Y-m-d'), 'last day', 'Y-m-d']; yield [(new \DateTimeImmutable('first day of January 2019'))->format('Y-m-d'), 'first day of January 2019', 'Y-m-d']; } /** * @return \Generator */ public static function invalid_date_time_string() : \Generator { yield ['2020-31-01']; yield ['2020-01-32']; yield ['something']; } /** * @return \Generator */ public static function modify_datetime() : \Generator { yield ['2021-01-31 00:00:00 UTC', '+1 second', '2021-01-31 00:00:01 UTC']; yield ['2021-01-31 00:00:00 UTC', '+1 minute', '2021-01-31 00:01:00 UTC']; yield ['2021-01-31 00:00:00 UTC', '+1 hour', '2021-01-31 01:00:00 UTC']; yield ['2021-01-31 00:00:00 UTC', '+1 day', '2021-02-01 00:00:00 UTC']; yield ['2021-01-31 00:00:00 UTC', '+1 week', '2021-02-07 00:00:00 UTC']; yield ['2021-01-31 00:00:00 UTC', '+1 fortnight', '2021-02-14 00:00:00 UTC']; yield ['2020-01-01 00:00:00 UTC', '+1 hour', '2020-01-01 01:00:00 UTC']; yield ['2020-01-01 00:00:00 UTC', '1 hour', '2020-01-01 01:00:00 UTC']; yield ['2020-01-01 00:00:00 UTC', '-1 hour', '2019-12-31 23:00:00 UTC']; yield ['2021-03-30 00:00:00 UTC', '-1 month', '2021-02-28 00:00:00 UTC']; yield ['2021-02-28 00:00:00 UTC', '+1 month', '2021-03-28 00:00:00 UTC']; yield ['2021-01-31 00:00:00 UTC', '+1 month', '2021-02-28 00:00:00 UTC']; yield ['2021-01-31 00:00:00 UTC', '+2 month', '2021-03-31 00:00:00 UTC']; yield ['2021-01-31 00:00:00 UTC', '+1 year', '2022-01-31 00:00:00 UTC']; yield ['2021-01-31 00:00:00 UTC', '-1 year', '2020-01-31 00:00:00 UTC']; yield ['2022-10-25 15:00:00 UTC', 'next Saturday', '2022-10-29 15:00:00 UTC']; yield ['2022-10-25 15:00:00 UTC', 'previous Saturday', '2022-10-22 15:00:00 UTC']; } /** * @return \Generator */ public static function add_relative_timeunit_months() : \Generator { yield ['2020-01-01', 1, '2020-02-01']; yield ['2020-01-01', 6, '2020-07-01']; yield ['2020-01-01', 12, '2021-01-01']; yield ['2020-01-01', 16, '2021-05-01']; } /** * @return \Generator */ public static function sub_relative_timeunit_months() : \Generator { yield ['2020-01-01', 1, '2019-12-01']; yield ['2020-01-01', 6, '2019-07-01']; yield ['2020-01-01', 12, '2019-01-01']; yield ['2020-01-01', 16, '2018-09-01']; } /** * @return \Generator */ public static function ambiguous_time_data_provider() : \Generator { // yield [DateTime::fromString('2020-10-25 02:00:00 Europe/Warsaw')]; TODO: verify why this is failing only at CI, at PHP 8.1.8 yield [DateTime::fromString('2020-10-25 02:30:30 Europe/Warsaw')]; yield [DateTime::fromString('2020-10-25 02:59:59 Europe/Warsaw')]; yield [DateTime::fromString('2020-10-25 03:00:00 Europe/Warsaw')]; } /** * @return \Generator */ public static function not_ambiguous_time_data_provider() : \Generator { yield [new DateTime(Day::fromString('2020-01-01'), Time::fromString('00:00:00'), TimeZone::UTC())]; yield [DateTime::fromString('2020-10-25 01:59:59 UTC')]; yield [DateTime::fromString('2020-10-25 00:00:00 Europe/Warsaw')]; yield [DateTime::fromString('2020-10-25 01:00:00 Europe/Warsaw')]; yield [DateTime::fromString('2020-10-25 01:59:59 Europe/Warsaw')]; yield [DateTime::fromString('2020-03-29 01:59:58 Europe/Warsaw')]; yield [DateTime::fromString('2020-03-29 01:59:59 Europe/Warsaw')]; yield [DateTime::fromString('2020-03-29 02:00:00 Europe/Warsaw')]; yield [DateTime::fromString('2020-03-29 02:59:59 Europe/Warsaw')]; yield [DateTime::fromString('2020-03-29 03:00:00 Europe/Warsaw')]; yield [DateTime::fromString('2020-03-29 03:01:00 Europe/Warsaw')]; yield [DateTime::fromString('2020-03-29 04:00:00 Europe/Warsaw')]; yield [DateTime::fromString('2020-03-29 05:00:00 Europe/Warsaw')]; } /** * @return \Generator */ public static function timezone_abbreviation_provider() : \Generator { yield ['PST', '2021-01-01 00:00:00 America/Los_Angeles']; yield ['PDT', '2021-07-01 00:00:00 America/Los_Angeles']; yield ['CEST', '2021-07-01 00:00:00 Europe/Warsaw']; yield ['CET', '2021-01-01 00:00:00 Europe/Warsaw']; yield ['CEST', '2021-01-01 00:00:00 CEST']; } /** * @return \Generator */ public static function compare_to_provider() : \Generator { yield [DateTime::fromString('2022-10-26 11:53:12'), DateTime::fromString('2022-10-26 11:53:12'), 0]; yield [DateTime::fromString('2022-10-26'), DateTime::fromString('2022-10-26'), 0]; yield [DateTime::fromString('2022-10-25 11:53:12'), DateTime::fromString('2022-10-26 11:53:12'), -1]; yield [DateTime::fromString('2022-10-25 11:53:12'), DateTime::fromString('2022-11-26 11:53:12'), -1]; yield [DateTime::fromString('2022-10-25 11:53:12'), DateTime::fromString('2022-10-25 00:53:12'), 1]; yield [DateTime::fromString('2022-11-26 11:53:12'), DateTime::fromString('2022-10-26 11:53:12'), 1]; } public function setUp() : void { \date_default_timezone_set('UTC'); } #[DataProvider('creating_datetime_data_provider')] public function test_creating_datetime(string $dateTimeString, DateTime $dateTime, string $format) : void { try { $this->assertSame($dateTimeString, $dateTime->format($format)); } catch (InvalidArgumentException $exception) { $this->fail($exception->getMessage()); } } #[DataProvider('creating_datetime_data_provider_from_string')] public function test_creating_datetime_from_string(string $dateTimeString, string $dateTime, string $format) : void { try { $this->assertSame($dateTimeString, DateTime::fromString($dateTime)->format($format)); } catch (InvalidArgumentException $exception) { $this->fail($exception->getMessage()); } } #[DataProvider('invalid_date_time_string')] public function test_creating_datetime_from_invalid_string(string $dateTimeInvalidString) : void { $this->expectExceptionMessage("Value \"{$dateTimeInvalidString}\" is not valid date time format."); $this->expectException(InvalidArgumentException::class); DateTime::fromString($dateTimeInvalidString); } public function test_creating_datetime_from_string_relative_with_system_default_timezone_different_from_UTC() : void { \date_default_timezone_set('Europe/Warsaw'); $dateTime = DateTime::fromString('tomorrow'); $this->assertSame('UTC', $dateTime->timeZone()->name()); $this->assertSame('Europe/Warsaw', \date_default_timezone_get()); } public function test_creating_datetime_from_string_relative_with_timezone_and_with_system_default_timezone_different_from_UTC() : void { \date_default_timezone_set('Europe/Warsaw'); $dateTime = DateTime::fromString('tomorrow PST'); $this->assertSame('PST', $dateTime->timeZone()->name()); $this->assertSame('Europe/Warsaw', \date_default_timezone_get()); } public function test_debug_info() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00 America/Los_Angeles'); $this->assertSame( [ 'datetime' => $dateTime->toISO8601(), 'day' => $dateTime->day(), 'time' => $dateTime->time(), 'timeZone' => $dateTime->timeZone(), ], $dateTime->__debugInfo() ); } public function test_create_with_timezone_and_without_time_offset() : void { $this->assertSame( '2020-01-01 00:00:00.000000-0800', (new DateTime(new Day(new Month(new Year(2020), 1), 1), new Time(0, 0, 0), TimeZone::americaLosAngeles()))->format('Y-m-d H:i:s.uO') ); } public function test_create_from_string_without_offset_and_timezone() : void { $this->assertSame( 'UTC', DateTime::fromString('2020-01-01 00:00:00')->timeZone()->name() ); } public function test_create_from_string_with_default_system_timezone() : void { \date_default_timezone_set('Europe/Warsaw'); $this->assertSame( 'UTC', DateTime::fromString('2020-01-01 00:00:00')->timeZone()->name() ); $this->assertSame('Europe/Warsaw', \date_default_timezone_get()); } public function test_create_from_timestamp_with_default_system_timezone() : void { \date_default_timezone_set('Europe/Warsaw'); $this->assertSame( 'UTC', DateTime::fromTimestampUnix(1577836800)->timeZone()->name() ); $this->assertSame('Europe/Warsaw', \date_default_timezone_get()); } public function test_compare_objects_create_through_different_constructors() : void { \date_default_timezone_set('Europe/Warsaw'); $dateTimeFromString = DateTime::fromString('2020-01-01 00:00:00'); $dateTimeFromTimestamp = DateTime::fromTimestampUnix(1577836800); $dateTimeCreate = DateTime::create(2020, 01, 01, 00, 00, 00); $dateTime = new DateTime(new Day(new Month(new Year(2020), 01), 01), new Time(00, 00, 00), TimeZone::UTC()); $this->assertTrue($dateTime->isEqualTo($dateTimeFromString)); $this->assertTrue($dateTime->isEqualTo($dateTimeFromTimestamp)); $this->assertTrue($dateTime->isEqualTo($dateTimeCreate)); $this->assertTrue($dateTimeFromString->isEqualTo($dateTimeCreate)); $this->assertTrue($dateTimeFromString->isEqualTo($dateTimeFromTimestamp)); $this->assertTrue($dateTimeFromTimestamp->isEqualTo($dateTimeCreate)); } public function test_compare_source_datetime_immutable_with_converted_one() : void { $dateTimeImmutable = new \DateTimeImmutable('2020-01-01 00:00:00'); $dateTime = DateTime::fromDateTime($dateTimeImmutable); $this->assertEquals($dateTimeImmutable, $dateTime->toDateTimeImmutable()); } public function test_compare_source_datetime_immutable_with_timezone_with_converted_one() : void { $dateTimeImmutable = new \DateTimeImmutable('2020-01-01 00:00:00 America/Los_Angeles'); $dateTime = DateTime::fromDateTime($dateTimeImmutable); $this->assertEquals($dateTimeImmutable, $dateTime->toDateTimeImmutable()); } public function test_compare_source_from_string_with_timezone_with_converted_one() : void { $dateTimeImmutable = new \DateTimeImmutable('2020-01-01 00:00:00 America/Los_Angeles'); $dateTime = DateTime::fromString('2020-01-01 00:00:00 America/Los_Angeles'); $this->assertEquals($dateTimeImmutable, $dateTime->toDateTimeImmutable()); } public function test_create_from_string_with_timezone() : void { $this->assertSame( '2020-01-01 00:00:00.000000-0800', DateTime::fromString('2020-01-01 00:00:00 America/Los_Angeles')->format('Y-m-d H:i:s.uO') ); } public function test_to_string() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00+00'); $this->assertSame($dateTime->toISO8601(), $dateTime->__toString()); } public function test_to_iso8601_extended_format() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00+00'); $this->assertSame('2020-01-01T00:00:00+00:00', $dateTime->toISO8601()); } public function test_to_iso8601_basic_format() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00+00'); $this->assertSame('20200101T000000+0000', $dateTime->toISO8601($extended = false)); } public function test_create() : void { $this->assertTrue( DateTime::create(2020, 01, 01, 00, 00, 00, 0, 'America/Los_Angeles') ->isEqualTo(DateTime::fromString('2020-01-01 00:00:00 America/Los_Angeles')) ); } public function test_create_with_default_values() : void { $this->assertTrue( DateTime::create(2020, 01, 01, 00, 00, 00) ->isEqualTo(DateTime::fromString('2020-01-01 00:00:00.000000 UTC')) ); } public function test_from_timestamp() : void { $this->assertTrue( DateTime::fromString('2020-01-01 00:00:00')->isEqualTo(DateTime::fromTimestampUnix(1577836800)) ); } public function test_year() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00'); $this->assertSame(2020, $dateTime->year()->number()); } public function test_month() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00'); $this->assertSame(1, $dateTime->month()->number()); } public function test_day() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00'); $this->assertSame(1, $dateTime->day()->number()); } public function test_midnight() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00 UTC'); $this->assertSame('2020-01-01 00:00:00.000000+00:00', $dateTime->midnight()->format('Y-m-d H:i:s.uP')); } public function test_noon() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00 UTC'); $this->assertSame('2020-01-01 12:00:00.000000+00:00', $dateTime->noon()->format('Y-m-d H:i:s.uP')); } public function test_end_of_day() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00 UTC'); $this->assertSame('2020-01-01 23:59:59.999999+00:00', $dateTime->endOfDay()->format('Y-m-d H:i:s.uP')); } public function test_modify_with_non_relative_value() : void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Value "2020-02-03 00:00:00 UTC" is not valid relative time format.'); DateTime::fromString('2020-01-01 00:00:00 UTC')->modify('2020-02-03 00:00:00 UTC'); } #[DataProvider('modify_datetime')] public function test_modify(string $date, string $modifier, string $expectedDate) : void { $this->assertSame( $expectedDate, DateTime::fromString($date)->modify($modifier)->format('Y-m-d H:i:s T') ); } public function test_time() : void { $dateTime = DateTime::fromString('2020-01-01 12:54:23.001000'); $this->assertSame(12, $dateTime->time()->hour()); $this->assertSame(54, $dateTime->time()->minute()); $this->assertSame(23, $dateTime->time()->second()); $this->assertSame(1, $dateTime->time()->millisecond()); $this->assertSame(1000, $dateTime->time()->microsecond()); } public function test_timezone_conversion() : void { $dateTimeString = '2020-01-01 00:00:00.000000+0000'; $dateTime = DateTime::fromString($dateTimeString); $timeZone = 'Europe/Warsaw'; $this->assertSame( (new \DateTimeImmutable($dateTimeString))->setTimezone(new \DateTimeZone($timeZone))->format('Y-m-d H:i:s.uO'), $dateTime->toTimeZone(TimeZone::fromString($timeZone))->format('Y-m-d H:i:s.uO') ); } public function test_daylight() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00') ->toTimeZone(TimeZone::fromString('Europe/Warsaw')); $this->assertFalse($dateTime->isDaylightSaving()); $this->assertTrue($dateTime->isDaylight()); } public function test_saving_time() : void { $dateTime = DateTime::fromString('2020-08-01 00:00:00') ->toTimeZone(TimeZone::fromString('Europe/Warsaw')); $this->assertTrue($dateTime->isDaylightSaving()); $this->assertFalse($dateTime->isDaylight()); } public function test_unix_timestamp() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00'); $this->assertSame(1577836800, $dateTime->timestampUNIX()->inSeconds()); } public function test_unix_zero_timestamp() : void { $dateTime = DateTime::fromString('1970-01-01 00:00:00'); $this->assertSame(0, $dateTime->timestampUNIX()->inSeconds()); $this->assertTrue($dateTime->timestampUNIX()->isPositive()); } public function test_timestamp() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00 UTC'); $this->assertSame(1577836800, $dateTime->timestampUNIX()->inSeconds()); $this->assertSame(1577836800, $dateTime->timestamp(TimeEpoch::UNIX())->inSeconds()); $this->assertSame(1577836800, $dateTime->timestamp(TimeEpoch::POSIX())->inSeconds()); $this->assertSame(1514764837, $dateTime->timestamp(TimeEpoch::UTC())->inSeconds()); $this->assertSame(1261872018, $dateTime->timestamp(TimeEpoch::GPS())->inSeconds()); $this->assertSame(1956528037, $dateTime->timestamp(TimeEpoch::TAI())->inSeconds()); } public function test_to_atomic_time() : void { $now = DateTime::fromString('2020-06-17 20:57:07 UTC'); $this->assertSame('2020-06-17T20:57:44+00:00', $now->toAtomicTime()->toISO8601()); } public function test_to_gps_time() : void { $now = DateTime::fromString('2020-06-17 20:57:07 UTC'); $this->assertSame('2020-06-17T20:57:25+00:00', $now->toGPSTime()->toISO8601()); } public function test_timestamp_before_epoch_start() : void { $this->expectException(Exception::class); $this->expectExceptionMessage('Given epoch started at 1970-01-01T00:00:00+00:00 which was after 1969-01-01T00:00:00+00:00'); $dateTime = DateTime::fromString('1969-01-01 00:00:00 UTC'); $this->assertSame(1577836800, $dateTime->timestamp(TimeEpoch::UNIX())->inSeconds()); } public function test_add_hour() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00'); $this->assertSame('2020-01-01T01:00:00+00:00', $dateTime->addHour()->format('c')); } public function test_add_hour_during_ambiguous_time() : void { $dateTime = DateTime::fromString('2020-11-01 01:30:00 America/Los_Angeles'); $dateTime = DateTime::fromString( $dateTime->add(TimeUnit::minutes(30))->format('Y-m-d H:i:s.u') . ' America/Los_Angeles' ); $this->assertSame('2020-11-01T01:00:00-07:00', $dateTime->toISO8601()); } public function test_sub_hour() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00'); $this->assertSame('2019-12-31T23:00:00+00:00', $dateTime->subHour()->format('c')); } public function test_add_hours() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00'); $this->assertSame('2020-01-01T05:00:00+00:00', $dateTime->addHours(5)->format('c')); } public function test_sub_hours() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00'); $this->assertSame('2019-12-31T19:00:00+00:00', $dateTime->subHours(5)->format('c')); } public function test_iterating_until_forward() : void { $timePeriods = DateTime::fromString('2020-01-01 00:00:00') ->iterate( DateTime::fromString('2020-01-02 00:00:00'), TimeUnit::hour() ); $this->assertInstanceOf(TimePeriod::class, $timePeriods->all()[0]); $this->assertSame('2020-01-01 00:00:00', $timePeriods->all()[0]->start()->format('Y-m-d H:i:s')); $this->assertFalse($timePeriods->all()[0]->distance()->isNegative()); $this->assertSame('2020-01-01 01:00:00', $timePeriods->all()[0]->end()->format('Y-m-d H:i:s')); } public function test_iterating_until_backward() : void { $timePeriods = DateTime::fromString('2020-01-02 00:00:00') ->iterate( DateTime::fromString('2020-01-01 00:00:00'), TimeUnit::hour() ); $this->assertInstanceOf(TimePeriod::class, $timePeriods->all()[0]); $this->assertSame('2020-01-02 00:00:00', $timePeriods->all()[0]->start()->format('Y-m-d H:i:s')); $this->assertTrue($timePeriods->all()[0]->distance()->isNegative()); $this->assertSame('2020-01-01 23:00:00', $timePeriods->all()[0]->end()->format('Y-m-d H:i:s')); } public function test_equal_dates_in_different_timezones() : void { $this->assertTrue( DateTime::fromString('2020-01-01 00:00:00.100001') ->toTimeZone(TimeZone::australiaSydney()) ->isEqualTo( DateTime::fromString('2020-01-01 00:00:00.100001')->toTimeZone(TimeZone::europeWarsaw()) ) ); } public function test_add_second() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00+00'); $this->assertSame(1, $dateTime->addSecond()->time()->second()); } public function test_add_seconds() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00+00'); $this->assertSame(5, $dateTime->addSeconds(5)->time()->second()); } public function test_sub_second() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00+00'); $this->assertSame(59, $dateTime->subSecond()->time()->second()); } public function test_sub_seconds() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00+00'); $this->assertSame(55, $dateTime->subSeconds(5)->time()->second()); } public function test_add_minute() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00+00'); $this->assertSame(1, $dateTime->addMinute()->time()->minute()); } public function test_add_minutes() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00+00'); $this->assertSame(5, $dateTime->addMinutes(5)->time()->minute()); } public function test_sub_minute() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00+00'); $this->assertSame(59, $dateTime->subMinute()->time()->minute()); } public function test_sub_minutes() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00+00'); $this->assertSame(55, $dateTime->subMinutes(5)->time()->minute()); } public function test_add_day() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00+00'); $this->assertSame('2020-01-02 00:00:00+0000', $dateTime->addDay()->format('Y-m-d H:i:sO')); } public function test_add_days() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00+00'); $this->assertSame('2020-01-03 00:00:00+0000', $dateTime->addDays(2)->format('Y-m-d H:i:sO')); } public function test_sub_day() : void { $dateTime = DateTime::fromString('2020-01-02 00:00:00+00'); $this->assertSame('2020-01-01 00:00:00+0000', $dateTime->subDay()->format('Y-m-d H:i:sO')); } public function test_sub_days() : void { $dateTime = DateTime::fromString('2020-01-05 00:00:00+00'); $this->assertSame('2020-01-01 00:00:00+0000', $dateTime->subDays(4)->format('Y-m-d H:i:sO')); } public function test_add_month() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00+00'); $this->assertSame('2020-02-01 00:00:00+0000', $dateTime->addMonth()->format('Y-m-d H:i:sO')); } public function test_add_months() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00+00'); $this->assertSame('2020-03-01 00:00:00+0000', $dateTime->addMonths(2)->format('Y-m-d H:i:sO')); } public function test_add_2_months_to_the_last_day_of_march() : void { $dateTime = DateTime::fromString('2020-03-31 00:00:00+00'); $this->assertSame('2020-04-30 00:00:00+0000', $dateTime->addMonths(1)->format('Y-m-d H:i:sO')); } public function test_sub_month() : void { $dateTime = DateTime::fromString('2020-02-01 00:00:00+00'); $this->assertSame('2020-01-01 00:00:00+0000', $dateTime->subMonth()->format('Y-m-d H:i:sO')); } public function test_sub_month_from_day_before_last_day_of_march() : void { $dateTime = DateTime::fromString('2021-03-30 00:00:00+00'); $this->assertSame('2021-02-28 00:00:00+0000', $dateTime->subMonth()->format('Y-m-d H:i:sO')); } public function test_sub_months() : void { $dateTime = DateTime::fromString('2020-03-01 00:00:00+00'); $this->assertSame('2020-01-01 00:00:00+0000', $dateTime->subMonths(2)->format('Y-m-d H:i:sO')); } public function test_add_year() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00+00'); $this->assertSame('2021-01-01 00:00:00+0000', $dateTime->addYear()->format('Y-m-d H:i:sO')); } public function test_add_years() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00+00'); $this->assertSame('2022-01-01 00:00:00+0000', $dateTime->addYears(2)->format('Y-m-d H:i:sO')); } public function test_sub_year() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00+00'); $this->assertSame('2019-01-01 00:00:00+0000', $dateTime->subYear()->format('Y-m-d H:i:sO')); } public function test_sub_years() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00+00'); $this->assertSame('2018-01-01 00:00:00+0000', $dateTime->subYears(2)->format('Y-m-d H:i:sO')); } public function test_add_timeunit_hour() : void { $this->assertSame( '2020-01-01 01:00:00+0000', DateTime::fromString('2020-01-01 00:00:00+00')->add(TimeUnit::hour())->format('Y-m-d H:i:sO') ); } #[DataProvider('add_relative_timeunit_months')] public function test_add_relative_timeunit_months(string $date, int $addMonths, string $expectedDate) : void { $this->assertSame( $expectedDate, DateTime::fromString($date)->add(RelativeTimeUnit::months($addMonths))->format('Y-m-d') ); } #[DataProvider('sub_relative_timeunit_months')] public function test_sub_relative_timeunit_months(string $date, int $addMonths, string $expectedDate) : void { $this->assertSame( $expectedDate, DateTime::fromString($date)->sub(RelativeTimeUnit::months($addMonths))->format('Y-m-d') ); } public function test_add_precse_timeunit() : void { $this->assertSame( '2020-01-01 00:00:02.500000+0000', DateTime::fromString('2020-01-01 00:00:00.000000+00')->add(TimeUnit::precise(2.500000))->format('Y-m-d H:i:s.uO') ); } public function test_sub_timeunit() : void { $this->assertSame( '2020-01-01 00:00:00+0000', DateTime::fromString('2020-01-01 01:00:00+00')->sub(TimeUnit::hour())->format('Y-m-d H:i:sO') ); } public function test_sub_precse_timeunit() : void { $this->assertSame( '2020-01-01 00:59:57.500000+0000', DateTime::fromString('2020-01-01 01:00:00.000000+00')->sub(TimeUnit::precise(2.500000))->format('Y-m-d H:i:s.uO') ); } public function test_is_after() : void { $this->assertTrue( DateTime::fromString('2020-01-01 01:00:00+00') ->isAfter(DateTime::fromString('2020-01-01 00:00:00+00')) ); } public function test_is_before() : void { $this->assertTrue( DateTime::fromString('2020-01-01 00:00:00+00') ->isBefore(DateTime::fromString('2020-01-01 01:00:00+00')) ); $this->assertFalse( DateTime::fromString('2020-01-01 00:00:00+00') ->isBefore(DateTime::fromString('2020-01-01 00:00:00+00')) ); } public function test_is_after_or_equal() : void { $this->assertTrue( DateTime::fromString('2020-01-01 00:00:00+00') ->isAfterOrEqualTo(DateTime::fromString('2020-01-01 00:00:00+00')) ); } public function test_is_before_or_equal() : void { $this->assertTrue( DateTime::fromString('2020-01-01 00:00:00+00') ->isBeforeOrEqualTo(DateTime::fromString('2020-01-01 00:00:00+00')) ); } public function test_until() : void { $this->assertSame( 1, DateTime::fromString('2020-01-01 00:00:00+00') ->until(DateTime::fromString('2020-01-01 01:00:00+00')) ->distance() ->inHours() ); } public function test_until_with_date_before_datetime() : void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Backward DateTimeIterator 2020-01-01 00:00:00+00:00...2019-12-01 01:00:00+00:00 requires negative TimeUnit'); DateTime::fromString('2020-01-01 00:00:00+00') ->until(DateTime::fromString('2019-12-01 01:00:00+00')) ->iterate(TimeUnit::day(), Interval::rightOpen()); } public function test_since_with_date_after_datetime() : void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Backward DateTimeIterator 2020-01-01 01:00:00+00:00...2019-12-01 00:00:00+00:00 requires negative TimeUnit'); DateTime::fromString('2019-12-01 00:00:00+00') ->since(DateTime::fromString('2020-01-01 01:00:00+00')) ->iterate(TimeUnit::day(), Interval::rightOpen()); } public function test_distance_until() : void { $this->assertSame( 1, DateTime::fromString('2020-01-01 00:00:00+00') ->distanceUntil(DateTime::fromString('2020-01-01 01:00:00+00')) ->inHours() ); } public function test_since() : void { $this->assertSame( 1, DateTime::fromString('2020-01-01 01:00:00+00') ->since(DateTime::fromString('2020-01-01 00:00:00+00')) ->distance() ->inHours() ); } public function test_distance_since() : void { $this->assertSame( 1, DateTime::fromString('2020-01-01 01:00:00+00') ->distanceSince(DateTime::fromString('2020-01-01 00:00:00+00')) ->inHours() ); } #[DataProvider('ambiguous_time_data_provider')] public function test_checking_is_ambiguous(DateTime $dateTime) : void { $this->assertTrue($dateTime->isAmbiguous(), $dateTime->toISO8601() . ' is not ambiguous, timezonedb version: ' . \timezone_version_get()); } #[DataProvider('not_ambiguous_time_data_provider')] public function test_checking_is_not_ambiguous(DateTime $dateTime) : void { $this->assertFalse($dateTime->isAmbiguous()); } public function test_using_create_constructor_during_dst_gap() : void { $this->assertSame( '02:30:00.000000', DateTime::create(2020, 03, 29, 02, 30, 00, 0, 'Europe/Warsaw')->time()->toString() ); } public function test_using_constructor_during_dst_gap() : void { $this->assertSame( '02:30:00.000000', (new DateTime( new Day(new Month(new Year(2020), 03), 29), new Time(02, 30, 00, 0), TimeZone::europeWarsaw() ))->time()->toString() ); } public function test_timezone_when_not_explicitly_provided() : void { $timeZone = DateTime::fromString('2020-03-29 00:00:00')->timeZone(); $this->assertInstanceOf(TimeZone::class, $timeZone); $this->assertSame('UTC', $timeZone->name()); } public function test_time_zone_when_only_time_offset_explicitly_provided() : void { $this->assertSame('+01:00', DateTime::fromString('2020-01-01 00:00:00+0100')->timeZone()->name()); } public function test_yesterday() : void { $this->assertSame('2019-12-31T00:00:00+00:00', DateTime::fromString('2020-01-01 01:00:00')->yesterday()->toISO8601()); } public function test_yesterday_with_tz() : void { $this->assertSame('2019-12-31T00:00:00+01:00', DateTime::fromString('2020-01-01 01:00:00 Europe/Warsaw')->yesterday()->toISO8601()); } public function test_tomorrow() : void { $this->assertSame('2020-01-02T00:00:00+00:00', DateTime::fromString('2020-01-01 01:00:00')->tomorrow()->toISO8601()); } public function test_tomorrow_with_tz() : void { $this->assertSame('2020-01-02T00:00:00+01:00', DateTime::fromString('2020-01-01 01:00:00 Europe/Warsaw')->tomorrow()->toISO8601()); } public function test_set_time() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00.00000')->toTimeZone(TimeZone::europeWarsaw()); $newDateTime = $dateTime->setTime(new Time(1, 1, 1, 1)); $this->assertSame( '2020-01-01 01:01:01.000001+01:00', $newDateTime->format('Y-m-d H:i:s.uP') ); } public function test_set_time_in() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00.00000 UTC'); $newDateTime = $dateTime->setTimeIn(new Time(15, 0, 0, 0), TimeZone::americaNewYork()); $this->assertSame( '2020-01-01 15:00:00.000000-05:00', $newDateTime->format('Y-m-d H:i:s.uP') ); } public function test_set_day() : void { $dateTime = DateTime::fromString('2020-01-01 00:00:00.00000')->toTimeZone(TimeZone::europeWarsaw()); $newDateTime = $dateTime->setDay(Day::fromString('2020-01-05')); $this->assertSame( '2020-01-05 01:00:00.000000+01:00', $newDateTime->format('Y-m-d H:i:s.uP') ); } public function test_distance_to() : void { $this->assertSame(58, DateTime::fromString('2020-01-01 01:00:00 UTC')->distance(DateTime::fromString('2020-01-03 12:00:00 Europe/Warsaw'))->inHours()); } public function test_quarter() : void { $this->assertSame(1, DateTime::fromString('2020-01-01 00:00:00')->quarter()->number()); $this->assertSame(2, DateTime::fromString('2020-04-01 00:00:00')->quarter()->number()); $this->assertSame(3, DateTime::fromString('2020-07-01 00:00:00')->quarter()->number()); $this->assertSame(4, DateTime::fromString('2020-10-01 00:00:00')->quarter()->number()); } public function test_serialization() : void { $dateTime = DateTime::create(2020, 03, 29, 02, 30, 00, 0, 'Europe/Warsaw'); $this->assertObjectEquals( $dateTime, \unserialize(\serialize($dateTime)), 'isEqual' ); } #[DataProvider('timezone_abbreviation_provider')] public function test_timezone_abbreviation(string $abbreviation, string $date) : void { $this->assertSame($abbreviation, DateTime::fromString($date)->timeZoneAbbreviation()->name()); } public function test_timezone_abbreviation_from_time_offset() : void { $this->expectException(Exception::class); DateTime::fromString('2020-01-01 00:00:00+01:00')->timeZoneAbbreviation(); } public function test_from_date_time_immutable() : void { $this->assertSame('2020-01-01 00:00:00', DateTime::fromDateTime(new \DateTimeImmutable('2020-01-01 00:00:00'))->format('Y-m-d H:i:s')); } public function test_from_date_time() : void { $this->assertSame('2020-01-01 00:00:00', DateTime::fromDateTime(new \DateTime('2020-01-01 00:00:00'))->format('Y-m-d H:i:s')); } public function test_equals_works() : void { $this->assertEquals( DateTime::fromDateTime(new \DateTimeImmutable('2022-01-01 15:00:00')), DateTime::fromString('2022-01-01 15:00:00') ); $this->assertEquals( DateTime::fromDateTime(new \DateTimeImmutable('2022-01-01 15:00:00')), new DateTime( new Day( new Month( new Year(2022), 1 ), 1 ), new Time(15, 0, 0), TimeZone::UTC() ) ); } #[DataProvider('compare_to_provider')] public function test_compare_to(DateTime $dateTime, DateTime $comparable, int $compareResult) : void { $this->assertSame($compareResult, $dateTime->compareTo($comparable)); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/Gregorian/Day/WeekDayTest.php ================================================ assertTrue(WeekDay::monday()->isEqualTo(WeekDay::monday())); $this->assertTrue(WeekDay::tuesday()->isEqualTo(WeekDay::tuesday())); $this->assertTrue(WeekDay::wednesday()->isEqualTo(WeekDay::wednesday())); $this->assertTrue(WeekDay::thursday()->isEqualTo(WeekDay::thursday())); $this->assertTrue(WeekDay::friday()->isEqualTo(WeekDay::friday())); $this->assertTrue(WeekDay::saturday()->isEqualTo(WeekDay::saturday())); $this->assertTrue(WeekDay::sunday()->isEqualTo(WeekDay::sunday())); } public function test_create_for_week_day_less_than_0() : void { $this->expectException(InvalidArgumentException::class); $this->assertTrue(new WeekDay(0)); } public function test_create_for_week_day_greater_than_12() : void { $this->expectException(InvalidArgumentException::class); $this->assertTrue(new WeekDay(8)); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/Gregorian/DayTest.php ================================================ */ public static function create_day_with_invalid_number_provider() : \Generator { yield [0]; yield [32]; yield [40]; } /** * @return \Generator */ public static function creating_day_data_provider_from_string() : \Generator { yield [(new \DateTimeImmutable('now'))->format('Y-m-d 00:00:00+00:00'), 'now', 'Y-m-d H:i:sP']; yield [(new \DateTimeImmutable('now'))->format('Y-m-d 00:00:00+00:00'), 'now ', 'Y-m-d H:i:sP']; yield [(new \DateTimeImmutable('today'))->format('Y-m-d 00:00:00+00:00'), 'today', 'Y-m-d H:i:sP']; yield [(new \DateTimeImmutable('today'))->format('Y-m-d 00:00:00+00:00'), ' tOday', 'Y-m-d H:i:sP']; yield [(new \DateTimeImmutable('noon'))->format('Y-m-d 00:00:00+00:00'), 'noon', 'Y-m-d H:i:sP']; yield [(new \DateTimeImmutable('noon'))->format('Y-m-d 00:00:00+00:00'), 'noon ', 'Y-m-d H:i:sP']; yield [(new \DateTimeImmutable('yesterday noon'))->format('Y-m-d 00:00:00+00:00'), 'yesterday noon', 'Y-m-d H:i:sP']; yield [(new \DateTimeImmutable('tomorrow'))->format('Y-m-d 00:00:00+00:00'), 'tomorrow', 'Y-m-d H:i:sP']; yield [(new \DateTimeImmutable('tomorrow midnight'))->format('Y-m-d 00:00:00+00:00'), 'tomorrow midnight', 'Y-m-d H:i:sP']; yield [(new \DateTimeImmutable('yesterday'))->format('Y-m-d 00:00:00+00:00'), 'yesterday', 'Y-m-d H:i:sP']; yield [(new \DateTimeImmutable('midnight'))->format('Y-m-d 00:00:00+00:00'), 'midnight', 'Y-m-d H:i:sP']; yield [(new \DateTimeImmutable('24 week'))->format('Y-m-d 00:00:00+00:00'), '24 week', 'Y-m-d H:i:sP']; yield [(new \DateTimeImmutable('today +1 hour'))->format('Y-m-d 00:00:00+00:00'), 'today +1 hour', 'Y-m-d H:i:sP']; yield [(new \DateTimeImmutable('tomorrow +1 hour'))->format('Y-m-d 00:00:00+00:00'), 'tomorrow +1 hour', 'Y-m-d H:i:sP']; yield [(new \DateTimeImmutable('-2 days'))->format('Y-m-d 00:00:00+00:00'), '-2 days', 'Y-m-d H:i:sP']; yield [(new \DateTimeImmutable('Monday'))->format('Y-m-d 00:00:00+00:00'), 'Monday', 'Y-m-d H:i:sP']; yield [(new \DateTimeImmutable('Monday next week'))->format('Y-m-d 00:00:00+00:00'), 'Monday next week', 'Y-m-d H:i:sP']; yield [(new \DateTimeImmutable('next year'))->format('Y-m-d 00:00:00+00:00'), 'next year', 'Y-m-d H:i:sP']; yield [(new \DateTimeImmutable('fifth day'))->format('Y-m-d 00:00:00+00:00'), 'fifth day', 'Y-m-d H:i:sP']; yield [(new \DateTimeImmutable('first day of January 2019'))->format('Y-m-d 00:00:00+00:00'), 'first day of January 2019', 'Y-m-d H:i:sP']; } /** * @return \Generator */ public static function invalid_string_day_format() : \Generator { yield ['2020-32']; } /** * @return \Generator */ public static function valid_string_day_format() : \Generator { yield ['2020-01', new Day(new Month(new Year(2020), 1), 1)]; yield ['2020-01-02 +1 month', new Day(new Month(new Year(2020), 2), 2)]; } /** * @return \Generator */ public static function compare_to_provider() : \Generator { yield [Day::fromString('2022-10-26'), Day::fromString('2022-10-26'), 0]; yield [Day::fromString('2022-10'), Day::fromString('2022-10'), 0]; yield [Day::fromString('2022-10-25'), Day::fromString('2022-10-26'), -1]; yield [Day::fromString('2022-10-25'), Day::fromString('2022-11-25'), -1]; yield [Day::fromString('2022-11-26'), Day::fromString('2022-10-26'), 1]; yield [Day::fromString('2022-10-26'), Day::fromString('2022-10-25'), 1]; } #[DataProvider('create_day_with_invalid_number_provider')] public function test_create_day_with_invalid_number(int $number) : void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Day number must be greater or equal 1 and less or equal than 31'); new Day(new Month(new Year(2020), 01), $number); } public function test_debug_info() : void { $this->assertSame( [ 'year' => 2020, 'month' => 1, 'day' => 1, ], Day::fromString('2020-01-01')->__debugInfo() ); } #[DataProvider('creating_day_data_provider_from_string')] public function test_creating_day_from_string(string $dateTimeString, string $dateTime, string $format) : void { try { $this->assertSame($dateTimeString, Day::fromString($dateTime)->format($format)); } catch (InvalidArgumentException $exception) { $this->fail($exception->getMessage()); } } public function test_to_string() : void { $this->assertSame( '2020-01-02', Day::fromString('2020-01-01 +1 day')->toString() ); } #[DataProvider('invalid_string_day_format')] public function test_from_invalid_string(string $invalidValue) : void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("Value \"{$invalidValue}\" is not valid day format."); Day::fromString($invalidValue); } #[DataProvider('valid_string_day_format')] public function test_from_string(string $invalidValue, Day $month) : void { $this->assertObjectEquals($month, Day::fromString($invalidValue), 'isEqual'); } public function test_midnight() : void { $day = Day::fromString('2020-01-01'); $this->assertSame('2020-01-01 00:00:00.000000+00:00', $day->midnight(TimeZone::UTC())->format('Y-m-d H:i:s.uP')); $this->assertSame(2020, $day->year()->number()); } public function test_noon() : void { $day = Day::fromString('2020-01-01'); $this->assertSame('2020-01-01 12:00:00.000000+00:00', $day->noon(TimeZone::UTC())->format('Y-m-d H:i:s.uP')); } public function test_end_of_day() : void { $day = Day::fromString('2020-01-01'); $this->assertSame('2020-01-01 23:59:59.999999+0000', $day->endOfDay(TimeZone::UTC())->format('Y-m-d H:i:s.uO')); } public function test_set_time() : void { $day = Day::fromString('2020-01-01'); $this->assertSame( '2020-01-01 11:00:00.000000+0100', $day->setTime(Time::fromString('11:00:00'), TimeZone::europeWarsaw())->format('Y-m-d H:i:s.uO') ); } public function test_next() : void { $day = Day::fromString('2020-01-01')->next(); $this->assertSame('2020-01-02 00:00:00', $day->format('Y-m-d H:i:s')); } public function test_previous() : void { $day = Day::fromString('2020-01-02')->previous(); $this->assertSame('2020-01-01 00:00:00', $day->format('Y-m-d H:i:s')); } public function test_week_of_year() : void { $day = Day::fromString('2020-02-01'); $this->assertSame(5, $day->weekOfYear()); } public function test_week_of_month() : void { $this->assertSame(1, Day::fromString('2020-01-05')->weekOfMonth()); $this->assertSame(2, Day::fromString('2020-01-12')->weekOfMonth()); $this->assertSame(3, Day::fromString('2020-01-19')->weekOfMonth()); $this->assertSame(4, Day::fromString('2020-01-26')->weekOfMonth()); $this->assertSame(5, Day::fromString('2020-01-31')->weekOfMonth()); $this->assertSame(1, Day::fromString('2020-02-1')->weekOfMonth()); $this->assertSame(2, Day::fromString('2020-02-9')->weekOfMonth()); $this->assertSame(3, Day::fromString('2020-02-16')->weekOfMonth()); $this->assertSame(4, Day::fromString('2020-02-23')->weekOfMonth()); $this->assertSame(5, Day::fromString('2020-02-29')->weekOfMonth()); } public function test_day_of_year() : void { $day = Day::fromString('2020-02-01'); $this->assertSame(32, $day->dayOfYear()); } public function test_week_day() : void { $this->assertSame(5, Day::fromString('2020-01-03')->weekDay()->number()); $this->assertSame('Friday', Day::fromString('2020-01-03')->weekDay()->name()); $this->assertSame('Fri', Day::fromString('2020-01-03')->weekDay()->shortName()); } public function test_format() : void { $this->assertSame('2020-01-03', Day::fromString('2020-01-03')->format('Y-m-d')); } public function test_is_weekend() : void { $this->assertFalse(Day::fromString('2020-01-03')->isWeekend()); $this->assertTrue(Day::fromString('2020-01-04')->isWeekend()); $this->assertTrue(Day::fromString('2020-01-05')->isWeekend()); $this->assertFalse(Day::fromString('2020-01-06')->isWeekend()); } public function test_is_equal() : void { $this->assertTrue(Day::fromString('2020-01-01')->isEqualTo(Day::fromString('2020-01-01'))); $this->assertFalse(Day::fromString('2020-01-02')->isEqualTo(Day::fromString('2020-01-01'))); } public function test_is_before() : void { $this->assertTrue(Day::fromString('2019-01-01')->isBefore(Day::fromString('2020-01-01'))); $this->assertFalse(Day::fromString('2019-01-01')->isBefore(Day::fromString('2019-01-01'))); $this->assertTrue(Day::fromString('2020-01-01')->isBeforeOrEqualTo(Day::fromString('2020-01-01'))); $this->assertFalse(Day::fromString('2021-01-01')->isBefore(Day::fromString('2020-01-01'))); $this->assertFalse(Day::fromString('2021-01-01')->isBeforeOrEqualTo(Day::fromString('2020-01-01'))); $this->assertTrue(Day::fromString('2020-01-01')->isBeforeOrEqualTo(Day::fromString('2020-05-01'))); $this->assertTrue(Day::fromString('2020-05-01')->isAfterOrEqualTo(Day::fromString('2020-01-01'))); } public function test_is_after() : void { $this->assertTrue(Day::fromString('2022-01-01')->isAfter(Day::fromString('2020-02-01'))); $this->assertFalse(Day::fromString('2020-01-01')->isAfter(Day::fromString('2020-01-01'))); $this->assertTrue(Day::fromString('2020-01-01')->isAfterOrEqualTo(Day::fromString('2020-01-01'))); $this->assertFalse(Day::fromString('2019-01-01')->isAfter(Day::fromString('2020-02-01'))); $this->assertFalse(Day::fromString('2019-01-01')->isAfterOrEqualTo(Day::fromString('2020-02-01'))); } public function test_reset_time_in_to_datetime_immutable() : void { $day = new Day(new Month(new Year(2020), 1), 1); $dateTimeImmutable1 = $day->toDateTimeImmutable(); \sleep(1); $dateTimeImmutable2 = $day->toDateTimeImmutable(); $this->assertTrue($dateTimeImmutable1 == $dateTimeImmutable2); } public function test_modify_months() : void { $this->assertSame('2020-05-02', Day::fromString('2020-06-01')->subDays(30)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2020-05-31', Day::fromString('2020-06-01')->subDays(1)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2020-07-01', Day::fromString('2020-06-01')->addDays(30)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2017-12-01', Day::fromString('2020-06-01')->subMonths(30)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2020-05-01', Day::fromString('2020-06-01')->subMonths(1)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2022-12-01', Day::fromString('2020-06-01')->addMonths(30)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('1990-06-01', Day::fromString('2020-06-01')->subYears(30)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2019-06-01', Day::fromString('2020-06-01')->subYears(1)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2050-06-01', Day::fromString('2020-06-01')->addYears(30)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2020-01-01', Day::fromString('2020-01-01')->add(0, 0, 0)->format('Y-m-d')); $this->assertSame('2021-02-02', Day::fromString('2020-01-01')->add(1, 1, 1)->format('Y-m-d')); $this->assertSame('2018-11-30', Day::fromString('2020-01-01')->add(-1, -1, -1)->format('Y-m-d')); $this->assertSame('2021-02-02', Day::fromString('2020-01-01')->sub(-1, -1, -1)->format('Y-m-d')); $this->assertSame('2018-11-30', Day::fromString('2020-01-01')->sub(1, 1, 1)->format('Y-m-d')); } public function test_until_with_wrong_destination_month() : void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('1 January 2020 is after 1 January 2019'); Day::fromString('2020-01-01')->until(Day::fromString('2019-01-01'), Interval::rightOpen()); } public function test_since_with_wrong_destination_month() : void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('1 January 2019 is before 1 January 2020'); Day::fromString('2019-01-01')->since(Day::fromString('2020-01-01'), Interval::rightOpen()); } public function test_until() : void { $this->assertCount(5, $days = Day::fromString('2020-01-01')->until(Day::fromString('2020-01-06'), Interval::rightOpen())); $this->assertInstanceOf(Day::class, $days->all()[0]); $this->assertInstanceOf(Day::class, $days->all()[4]); $this->assertSame(1, $days->all()[0]->number()); $this->assertSame(5, $days->all()[4]->number()); } public function test_since() : void { $this->assertCount(5, $days = Day::fromString('2020-01-06')->since(Day::fromString('2020-01-01'), Interval::leftOpen())); $this->assertInstanceOf(Day::class, $days->all()[0]); $this->assertInstanceOf(Day::class, $days->all()[4]); $this->assertSame(6, $days->all()[0]->number()); $this->assertSame(2, $days->all()[4]->number()); } public function test_iterate_until() : void { $this->assertCount(5, $days = Day::fromString('2020-01-01')->iterate(Day::fromString('2020-01-06'), Interval::rightOpen())); $this->assertInstanceOf(Day::class, $days->all()[0]); $this->assertInstanceOf(Day::class, $days->all()[4]); $this->assertSame(1, $days->all()[0]->number()); $this->assertSame(5, $days->all()[4]->number()); } public function test_iterate_since() : void { $this->assertCount(5, $days = Day::fromString('2020-01-06')->iterate(Day::fromString('2020-01-01'), Interval::leftOpen())); $this->assertInstanceOf(Day::class, $days->all()[0]); $this->assertInstanceOf(Day::class, $days->all()[4]); $this->assertSame(6, $days->all()[0]->number()); $this->assertSame(2, $days->all()[4]->number()); } public function test_days_between() : void { $day1 = Day::fromString('2020-01-02'); $day2 = Day::fromString('2020-01-01'); $this->assertInstanceOf(TimeUnit::class, $day1->timeBetween($day2)); $this->assertSame(1, $day1->timeBetween($day2)->inDays()); $this->assertSame(1, $day2->timeBetween($day1)->inDays()); } public function test_day_static_create() : void { $day = Day::create(2020, 12, 24); $this->assertTrue(Day::fromString('2020-12-24')->isEqualTo($day)); } public function test_distance_to() : void { $this->assertSame(9, Day::create(2020, 01, 01)->distance(Day::create(2020, 01, 10))->inDays()); } public function test_quarter() : void { $this->assertSame(1, Day::fromString('2020-01-01')->quarter()->number()); $this->assertSame(2, Day::fromString('2020-04-01')->quarter()->number()); $this->assertSame(3, Day::fromString('2020-07-01')->quarter()->number()); $this->assertSame(4, Day::fromString('2020-10-01')->quarter()->number()); } public function test_serialization() : void { $day = Day::create(2020, 01, 01); $this->assertObjectEquals( $day, \unserialize(\serialize($day)), 'isEqual' ); } #[DataProvider('compare_to_provider')] public function test_compare_to(Day $time, Day $comparable, int $compareResult) : void { $this->assertSame($compareResult, $time->compareTo($comparable)); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/Gregorian/DaysIteratorTest.php ================================================ reverse() ); $this->assertEquals($array[0], Day::fromString('2020-01-10 00:00:00 UTC')); $this->assertEquals($array[9], Day::fromString('2020-01-01 00:00:00 UTC')); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/Gregorian/DaysTest.php ================================================ assertTrue(isset($days->all()[0])); $this->assertInstanceOf(Day::class, $days->all()[0]); $this->assertSame(3, \iterator_count($days->getIterator())); $this->assertSame(3, $days->count()); } public function test_map() : void { $days = Days::fromArray( Day::fromString('2002-01-01'), Day::fromString('2002-01-02'), Day::fromString('2002-01-03') ); $this->assertSame( [1, 2, 3], $days->map(function (Day $day) { return $day->number(); }) ); } public function test_filter() : void { $days = Days::fromArray( Day::fromString('2002-01-01'), Day::fromString('2002-01-02'), Day::fromString('2002-01-03') ); $this->assertObjectEquals( Day::fromString('2002-01-01'), $days->filter(function (Day $day) { return $day->number() === 1; })->all()[0], 'isEqual' ); } public function test_foreach() : void { $days = Days::fromArray( Day::fromString('2002-01-01'), Day::fromString('2002-01-02'), Day::fromString('2002-01-03') ); foreach ($days as $day) { $this->assertInstanceOf(Day::class, $day); } } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/Gregorian/LeapSecondTest.php ================================================ all()[0]; $this->assertObjectEquals( $leapSecond, \unserialize(\serialize($leapSecond)), 'isEqual' ); } public function test_is_equal() : void { $leapSeconds = LeapSeconds::load()->all(); $this->assertFalse($leapSeconds[0]->isEqualTo($leapSeconds[1])); $this->assertFalse($leapSeconds[0]->isEqualTo(new LeapSecond(DateTime::fromString('1972-01-01 00:00:00 UTC'), TimeUnit::seconds(12)), )); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/Gregorian/LeapSecondsTest.php ================================================ assertFalse( $leapSeconds->expirationDate()->isBefore( GregorianCalendar::UTC()->now()->add(TimeUnit::days(5)) ) ); $this->assertSame( 37, $leapSeconds->offsetTAI()->inSeconds() ); } /** * @runInSeparateProcess */ public function test_finding_leap_seconds_between_1970_jan_1_and_1980_jan_1() : void { $leapSeconds = LeapSeconds::load(); $this->assertEquals( 19, $leapSeconds->findAllBetween( DateTime::fromString('1970-01-01 00:00:00 UTC')->until(DateTime::fromString('1980-01-06 00:00:00 UTC')) )->offsetTAI()->inSeconds() ); $this->assertSame( 9, $leapSeconds->findAllBetween( DateTime::fromString('1970-01-01 00:00:00 UTC')->until(DateTime::fromString('1980-01-01 00:00:00 UTC')) )->count() ->inSeconds() ); } /** * @runInSeparateProcess */ public function test_finding_leap_seconds_since_date() : void { $leapSeconds = LeapSeconds::load(); $this->assertSame( 28, $leapSeconds->since( DateTime::fromString('1970-01-01 00:00:00 UTC') )->count() ->inSeconds() ); } public function test_creating_leap_second_with_invalid_offset() : void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Leap second TAI offset must be greater or equal 10'); new LeapSecond(DateTime::fromString('1970-01-01 00:00:00 UTC'), TimeUnit::seconds(5)); } /** * @runInSeparateProcess */ public function test_all_leap_seconds() : void { $leapSeconds = LeapSeconds::load(); $this->assertSame( [ 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 36, 37, ], \array_map( function (LeapSecond $leapSecond) : int { return $leapSecond->offsetTAI()->inSeconds(); }, $leapSeconds->all() ) ); } /** * @runInSeparateProcess */ public function test_filter_leap_seconds() : void { $leapSeconds = LeapSeconds::load(); $this->assertSame( 10, $leapSeconds->filter(fn (LeapSecond $leapSecond) => $leapSecond->offsetTAI()->inSeconds() === 10)->offsetTAI()->inSeconds() ); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/Gregorian/MonthDaysTest.php ================================================ assertSame( 31, (new Month(new Year(2020), 01))->days()->count() ); } public function test_first_day() : void { $this->assertSame( 1, (new Month(new Year(2020), 01))->days()->first()->number() ); } public function test_last_day() : void { $this->assertSame( 31, (new Month(new Year(2020), 01))->days()->last()->number() ); } public function test_map_days() : void { $this->assertSame( \range(1, 31), (new Month(new Year(2020), 01))->days()->map(fn (Day $day) => $day->number()) ); } public function test_filter_days() : void { $this->assertSame( [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30], \array_map( function (Day $day) : int { return $day->number(); }, \array_values((new Month(new Year(2020), 01))->days()->filter(fn (Day $day) => $day->number() % 2 === 0)->all()) ) ); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/Gregorian/MonthTest.php ================================================ */ public static function creating_month_data_provider_from_string() : \Generator { yield [(new \DateTimeImmutable('now'))->format('Y-m'), 'now']; yield [(new \DateTimeImmutable('now'))->format('Y-m'), 'NoW']; yield [(new \DateTimeImmutable('today'))->format('Y-m'), 'today']; yield [(new \DateTimeImmutable('today'))->format('Y-m'), 'today ']; yield [(new \DateTimeImmutable('noon'))->format('Y-m'), 'noon']; yield [(new \DateTimeImmutable('noon'))->format('Y-m'), ' noon']; yield [(new \DateTimeImmutable('yesterday noon'))->format('Y-m'), 'yesterday noon']; yield [(new \DateTimeImmutable('tomorrow'))->format('Y-m'), 'tomorrow']; yield [(new \DateTimeImmutable('tomorrow midnight'))->format('Y-m'), 'tomorrow midnight']; yield [(new \DateTimeImmutable('yesterday'))->format('Y-m'), 'yesterday']; yield [(new \DateTimeImmutable('midnight'))->format('Y-m'), 'midnight']; yield [(new \DateTimeImmutable('24 week'))->format('Y-m'), '24 week']; yield [(new \DateTimeImmutable('today +1 hour'))->format('Y-m'), 'today +1 hour']; yield [(new \DateTimeImmutable('tomorrow +1 hour'))->format('Y-m'), 'tomorrow +1 hour']; yield [(new \DateTimeImmutable('-2 days'))->format('Y-m'), '-2 days']; yield [(new \DateTimeImmutable('Monday'))->format('Y-m'), 'Monday']; yield [(new \DateTimeImmutable('Monday next week'))->format('Y-m'), 'Monday next week']; yield [(new \DateTimeImmutable('next year'))->format('Y-m'), 'next year']; yield [(new \DateTimeImmutable('fifth day'))->format('Y-m'), 'fifth day']; yield [(new \DateTimeImmutable('first day of January 2019'))->format('Y-m'), 'first day of January 2019']; } /** * @return \Generator */ public static function invalid_string_day_format() : \Generator { yield ['test']; } /** * @return \Generator */ public static function valid_string_day_format() : \Generator { yield ['2020-01', new Month(new Year(2020), 1)]; yield ['2020-01 +1 month', new Month(new Year(2020), 2)]; yield ['2020-01 +1 year', new Month(new Year(2021), 1)]; yield ['2020-01-01', new Month(new Year(2020), 1)]; } /** * @return \Generator */ public static function compare_to_provider() : \Generator { yield [Month::fromString('2022-10'), Month::fromString('2022-10'), 0]; yield [Month::fromString('2022'), Month::fromString('2022'), 0]; yield [Month::fromString('2022-09'), Month::fromString('2022-10'), -1]; yield [Month::fromString('2021-10'), Month::fromString('2022-10'), -1]; yield [Month::fromString('2022-11'), Month::fromString('2022-10'), 1]; yield [Month::fromString('2022-10'), Month::fromString('2021-10'), 1]; } public function test_create_with_month_number_lower_than_0() : void { $this->expectException(InvalidArgumentException::class); new Month(new Year(2020), 0); } public function test_create_with_month_number_greater_than_12() : void { $this->expectException(InvalidArgumentException::class); new Month(new Year(2020), 13); } public function test_first_day_of_month() : void { $month = Month::fromString('2020-01-01'); $this->assertSame(1, $month->firstDay()->number()); } public function test_create_from_datetime() : void { $month = Month::fromDateTime(new \DateTimeImmutable('2020-02-01')); $this->assertSame(2, $month->number()); } #[DataProvider('creating_month_data_provider_from_string')] public function test_creating_month_from_string(string $dateTimeString, string $dateTime) : void { try { $this->assertSame($dateTimeString, Month::fromString($dateTime)->toString()); } catch (InvalidArgumentException $exception) { $this->fail($exception->getMessage()); } } #[DataProvider('invalid_string_day_format')] public function test_from_invalid_string(string $invalidValue) : void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("Value \"{$invalidValue}\" is not valid month format."); Month::fromString($invalidValue); } #[DataProvider('valid_string_day_format')] public function test_from_string(string $invalidValue, Month $month) : void { $this->assertObjectEquals($month, Month::fromString($invalidValue), 'isEqual'); } public function test_debug_info() : void { $this->assertSame( [ 'year' => 2020, 'month' => 1, ], Month::fromString('2020-01-01')->__debugInfo() ); } public function test_to_string() : void { $this->assertSame( '2020-01', Month::fromString('2020-01-01')->toString() ); $this->assertSame( '2020-01', (string) Month::fromString('2020-01-01') ); } public function test_last_day_of_month() : void { $month = Month::fromString('2020-01-01'); $this->assertSame(31, $month->lastDay()->number()); } public function test_next_month() : void { $this->assertSame(2, Month::fromString('2020-01-01')->next()->number()); } public function test_previous_month() : void { $this->assertSame(1, Month::fromString('2020-02-01')->previous()->number()); } public function test_name() : void { $this->assertSame('January', Month::fromString('2020-01-01')->name()); } public function test_short_name() : void { $this->assertSame('Jan', Month::fromString('2020-01-01')->shortName()); } public function test_reset_time_in_to_datetime_immutable() : void { $month = new Month(new Year(2020), 1); $dateTimeImmutable1 = $month->toDateTimeImmutable(); \sleep(1); $dateTimeImmutable2 = $month->toDateTimeImmutable(); $this->assertTrue($dateTimeImmutable1 == $dateTimeImmutable2); } public function test_is_equal() : void { $this->assertTrue(Month::fromString('2020-01-01')->isEqualTo(Month::fromString('2020-01-01'))); $this->assertFalse(Month::fromString('2020-01-02')->isEqualTo(Month::fromString('2020-02-01'))); } public function test_is_before() : void { $this->assertTrue(Month::fromString('2019-01-01')->isBefore(Month::fromString('2020-01-01'))); $this->assertTrue(Month::fromString('2020-01-01')->isBeforeOrEqualTo(Month::fromString('2020-01-01'))); $this->assertFalse(Month::fromString('2021-01-01')->isBefore(Month::fromString('2020-01-01'))); $this->assertFalse(Month::fromString('2021-01-01')->isBeforeOrEqualTo(Month::fromString('2020-01-01'))); $this->assertTrue(Month::fromString('2019-01-01')->isBeforeOrEqualTo(Month::fromString('2020-01-01'))); $this->assertTrue(Month::fromString('2021-01-01')->isAfterOrEqualTo(Month::fromString('2020-01-01'))); } public function test_is_after() : void { $this->assertTrue(Month::fromString('2022-01-01')->isAfter(Month::fromString('2020-02-01'))); $this->assertTrue(Month::fromString('2020-01-01')->isAfterOrEqualTo(Month::fromString('2020-01-01'))); $this->assertFalse(Month::fromString('2019-01-01')->isAfter(Month::fromString('2020-02-01'))); $this->assertFalse(Month::fromString('2019-01-01')->isAfterOrEqualTo(Month::fromString('2020-02-01'))); } public function test_until_with_wrong_destination_month() : void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('January 2020 is after January 2019'); Month::fromString('2020-01-01')->until(Month::fromString('2019-01-01'), Interval::rightOpen()); } public function test_since_with_wrong_destination_month() : void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('January 2019 is before January 2020'); Month::fromString('2019-01-01')->since(Month::fromString('2020-01-01'), Interval::leftOpen()); } public function test_until() : void { $this->assertCount(12, $months = Month::fromString('2020-01-01')->until(Month::fromString('2021-01-01'), Interval::rightOpen())); $this->assertInstanceOf(Month::class, $months->all()[0]); $this->assertInstanceOf(Month::class, $months->all()[11]); $this->assertSame('January', $months->all()[0]->name()); $this->assertSame('December', $months->all()[11]->name()); } public function test_since() : void { $this->assertCount(12, $months = Month::fromString('2022-01-01')->since(Month::fromString('2021-01-01'), Interval::leftOpen())); $this->assertInstanceOf(Month::class, $months->all()[0]); $this->assertInstanceOf(Month::class, $months->all()[11]); $this->assertSame('February', $months->all()[0]->name()); $this->assertSame('January', $months->all()[11]->name()); } public function test_iterate_until() : void { $this->assertCount(12, $months = Month::fromString('2020-01-01')->iterate(Month::fromString('2021-01-01'), Interval::rightOpen())); $this->assertInstanceOf(Month::class, $months->all()[0]); $this->assertInstanceOf(Month::class, $months->all()[11]); $this->assertSame('January', $months->all()[0]->name()); $this->assertSame('December', $months->all()[11]->name()); } public function test_iterate_since() : void { $this->assertCount(12, $months = Month::fromString('2022-01-01')->iterate(Month::fromString('2021-01-01'), Interval::leftOpen())); $this->assertInstanceOf(Month::class, $months->all()[0]); $this->assertInstanceOf(Month::class, $months->all()[11]); $this->assertSame('February', $months->all()[0]->name()); $this->assertSame('January', $months->all()[11]->name()); } public function test_modify_months() : void { $this->assertSame('2020-01-01', Month::fromString('2020-06-01')->subMonths(5)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2020-05-01', Month::fromString('2020-06-01')->subMonths(1)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2019-12-01', Month::fromString('2020-01-01')->subMonths(1)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2021-12-01', Month::fromString('2020-01-01')->addMonths(23)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2020-12-01', Month::fromString('2020-06-01')->addMonths(6)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2021-10-01', Month::fromString('2020-06-01')->addMonths(16)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2021-07-01', Month::fromString('2020-06-01')->addMonths(13)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2021-12-01', Month::fromString('2020-06-01')->addMonths(18)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2022-01-01', Month::fromString('2020-06-01')->addMonths(19)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2022-07-01', Month::fromString('2020-12-01')->addMonths(19)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2026-08-01', Month::fromString('2020-12-01')->addMonths(68)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2020-12-01', Month::fromString('2020-08-01')->addMonths(4)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2019-12-01', Month::fromString('2020-06-01')->subMonths(6)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2019-02-01', Month::fromString('2020-06-01')->subMonths(16)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2019-05-01', Month::fromString('2020-06-01')->subMonths(13)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2018-12-01', Month::fromString('2020-06-01')->subMonths(18)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2018-11-01', Month::fromString('2020-06-01')->subMonths(19)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2019-05-01', Month::fromString('2020-12-01')->subMonths(19)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2015-04-01', Month::fromString('2020-12-01')->subMonths(68)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2020-04-01', Month::fromString('2020-08-01')->subMonths(4)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2015-06-01', Month::fromString('2020-06-01')->subYears(5)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2019-06-01', Month::fromString('2020-06-01')->subYears(1)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2026-06-01', Month::fromString('2020-06-01')->addYears(6)->toDateTimeImmutable()->format('Y-m-d')); $this->asserTsame('2021-02-01', Month::fromString('2020-01-01')->add(1, 1)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2019-12-01', Month::fromString('2020-01-01')->add(-1, -1)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2018-12-01', Month::fromString('2020-01-01')->sub(1, 1)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2021-02-01', Month::fromString('2020-01-01')->sub(-1, -1)->toDateTimeImmutable()->format('Y-m-d')); } public function test_day_static_create() : void { $month = Month::create(2020, 12); $this->assertTrue(Month::fromString('2020-12-24')->isEqualTo($month)); } public function test_distance_to() : void { $this->assertSame(31, Month::create(2020, 01)->distance(Month::create(2020, 02))->inDays()); } public function test_quarter() : void { $this->assertSame(1, Month::fromString('2020-01-01')->quarter()->number()); $this->assertSame(2, Month::fromString('2020-04-01')->quarter()->number()); $this->assertSame(3, Month::fromString('2020-07-01')->quarter()->number()); $this->assertSame(4, Month::fromString('2020-10-01')->quarter()->number()); } public function test_serialization() : void { $month = Month::create(2020, 1); $this->assertObjectEquals( $month, \unserialize(\serialize($month)), 'isEqual' ); } public function test_number_of_days() : void { $this->assertSame(31, (new Month(new Year(2020), 1))->numberOfDays()); $this->assertSame(29, (new Month(new Year(2020), 2))->numberOfDays()); $this->assertSame(31, (new Month(new Year(2020), 3))->numberOfDays()); $this->assertSame(30, (new Month(new Year(2020), 4))->numberOfDays()); $this->assertSame(31, (new Month(new Year(2020), 5))->numberOfDays()); $this->assertSame(30, (new Month(new Year(2020), 6))->numberOfDays()); $this->assertSame(31, (new Month(new Year(2020), 7))->numberOfDays()); $this->assertSame(31, (new Month(new Year(2020), 8))->numberOfDays()); $this->assertSame(30, (new Month(new Year(2020), 9))->numberOfDays()); $this->assertSame(31, (new Month(new Year(2020), 10))->numberOfDays()); $this->assertSame(30, (new Month(new Year(2020), 11))->numberOfDays()); $this->assertSame(31, (new Month(new Year(2020), 12))->numberOfDays()); $this->assertSame(28, (new Month(new Year(2021), 2))->numberOfDays()); } #[DataProvider('compare_to_provider')] public function test_compare_to(Month $time, Month $comparable, int $compareResult) : void { $this->assertSame($compareResult, $time->compareTo($comparable)); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/Gregorian/MonthsIteratorTest.php ================================================ reverse()); $this->assertObjectEquals($array[0], Month::fromString('2021-01-01 00:00:00 UTC'), 'isEqual'); $this->assertObjectEquals($array[12], Month::fromString('2020-01-01 00:00:00 UTC'), 'isEqual'); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/Gregorian/MonthsTest.php ================================================ assertTrue(isset($months->all()[0])); $this->assertInstanceOf(Month::class, $months->all()[0]); $this->assertSame(3, $months->count()); } public function test_map() : void { $days = Months::fromArray( Month::fromString('2002-01-01'), Month::fromString('2002-02-02'), Month::fromString('2002-03-03') ); $this->assertSame( [1, 2, 3], $days->map(function (Month $day) { return $day->number(); }) ); } public function test_filter() : void { $months = Months::fromArray( Month::fromString('2002-01-01'), Month::fromString('2002-02-02'), Month::fromString('2002-03-03') ); $this->assertEquals( Month::fromString('2002-01-01'), $months->filter(function (Month $day) { return $day->number() === 1; })->all()[0] ); } public function test_slice_below_lower_limit() : void { $this->expectException(InvalidArgumentException::class); (new Year(2020))->months()->slice(-1, 5); } public function test_slice_above_upper_limit() : void { $this->expectException(InvalidArgumentException::class); (new Year(2020))->months()->slice(12, 1); } public function test_slice() : void { $this->assertSame(12, (new Year(2020))->months()->slice(11, 1)->all()[0]->number()); $this->assertSame(2, (new Year(2020))->months()->slice(1, 1)->all()[0]->number()); $this->assertSame(1, (new Year(2020))->months()->slice(0, 1)->all()[0]->number()); $this->assertCount(5, (new Year(2020))->months()->slice(0, 5)->all()); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/Gregorian/Psr20ClockAdapterTest.php ================================================ assertEquals($date->toDateTimeImmutable(), $sut->now()); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/Gregorian/QuarterTest.php ================================================ expectException(InvalidArgumentException::class); new Quarter(0, Months::fromArray(Month::fromString('2020-01'), Month::fromString('2020-02'), Month::fromString('2020-03'))); } public function test_number_above_range() : void { $this->expectException(InvalidArgumentException::class); new Quarter(5, Months::fromArray(Month::fromString('2020-01'), Month::fromString('2020-02'), Month::fromString('2020-03'))); } public function test_invalid_number_of_months() : void { $this->expectException(InvalidArgumentException::class); new Quarter(1, Months::fromArray(Month::fromString('2020-01'))); } public function test_invalid_months_quarter_1() : void { $this->expectException(InvalidArgumentException::class); new Quarter(1, Months::fromArray(Month::fromString('2020-05'), Month::fromString('2020-02'), Month::fromString('2020-03'))); } public function test_invalid_months_quarter_2() : void { $this->expectException(InvalidArgumentException::class); new Quarter(2, Months::fromArray(Month::fromString('2020-01'), Month::fromString('2020-05'), Month::fromString('2020-06'))); } public function test_invalid_months_quarter_3() : void { $this->expectException(InvalidArgumentException::class); new Quarter(3, Months::fromArray(Month::fromString('2020-01'), Month::fromString('2020-08'), Month::fromString('2020-09'))); } public function test_invalid_months_quarter_4() : void { $this->expectException(InvalidArgumentException::class); new Quarter(4, Months::fromArray(Month::fromString('2020-01'), Month::fromString('2020-11'), Month::fromString('2020-12'))); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/Gregorian/TimeEpochTest.php ================================================ */ public static function seconds_since_data_provider() : \Generator { yield [TimeEpoch::UTC(), TimeEpoch::GPS(), DateTime::fromString('1972-01-01 00:00:00 UTC'), DateTime::fromString('1980-01-06 00:00:00 UTC')]; yield [TimeEpoch::UTC(), TimeEpoch::TAI(), DateTime::fromString('1972-01-01 00:00:00 UTC'), DateTime::fromString('1958-01-01 00:00:00 UTC')]; yield [TimeEpoch::UTC(), TimeEpoch::UNIX(), DateTime::fromString('1972-01-01 00:00:00 UTC'), DateTime::fromString('1970-01-01 00:00:00 UTC')]; yield [TimeEpoch::UTC(), TimeEpoch::UTC(), DateTime::fromString('1972-01-01 00:00:00 UTC'), DateTime::fromString('1972-01-01 00:00:00 UTC')]; yield [TimeEpoch::GPS(), TimeEpoch::UTC(), DateTime::fromString('1980-01-06 00:00:00 UTC'), DateTime::fromString('1972-01-01 00:00:00 UTC')]; yield [TimeEpoch::GPS(), TimeEpoch::TAI(), DateTime::fromString('1980-01-06 00:00:00 UTC'), DateTime::fromString('1958-01-01 00:00:00 UTC')]; yield [TimeEpoch::GPS(), TimeEpoch::UNIX(), DateTime::fromString('1980-01-06 00:00:00 UTC'), DateTime::fromString('1970-01-01 00:00:00 UTC')]; yield [TimeEpoch::GPS(), TimeEpoch::GPS(), DateTime::fromString('1980-01-06 00:00:00 UTC'), DateTime::fromString('1980-01-06 00:00:00 UTC')]; yield [TimeEpoch::UNIX(), TimeEpoch::UTC(), DateTime::fromString('1970-01-01 00:00:00 UTC'), DateTime::fromString('1972-01-01 00:00:00 UTC')]; yield [TimeEpoch::UNIX(), TimeEpoch::TAI(), DateTime::fromString('1970-01-01 00:00:00 UTC'), DateTime::fromString('1958-01-01 00:00:00 UTC')]; yield [TimeEpoch::UNIX(), TimeEpoch::GPS(), DateTime::fromString('1970-01-01 00:00:00 UTC'), DateTime::fromString('1980-01-06 00:00:00 UTC')]; yield [TimeEpoch::POSIX(), TimeEpoch::POSIX(), DateTime::fromString('1970-01-01 00:00:00 UTC'), DateTime::fromString('1970-01-01 00:00:00 UTC')]; yield [TimeEpoch::TAI(), TimeEpoch::UTC(), DateTime::fromString('1958-01-01 00:00:00 UTC'), DateTime::fromString('1972-01-01 00:00:00 UTC')]; yield [TimeEpoch::TAI(), TimeEpoch::GPS(), DateTime::fromString('1958-01-01 00:00:00 UTC'), DateTime::fromString('1980-01-06 00:00:00 UTC')]; yield [TimeEpoch::TAI(), TimeEpoch::UNIX(), DateTime::fromString('1958-01-01 00:00:00 UTC'), DateTime::fromString('1970-01-01 00:00:00 UTC')]; yield [TimeEpoch::TAI(), TimeEpoch::TAI(), DateTime::fromString('1958-01-01 00:00:00 UTC'), DateTime::fromString('1958-01-01 00:00:00 UTC')]; } #[DataProvider('seconds_since_data_provider')] public function test_distance_to_epoch(TimeEpoch $epoch, TimeEpoch $sinceEpoch, DateTime $dateTime, DateTime $sinceDateTime) : void { $this->assertSame( $epoch->distanceTo($sinceEpoch)->inSeconds(), $dateTime->until($sinceDateTime)->distance()->inSeconds() ); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/Gregorian/TimePeriodTest.php ================================================ */ public static function overlapping_time_periods_data_provider() : \Generator { yield [ false, new TimePeriod(DateTime::fromString('2020-01-01 00:00:00.0000'), DateTime::fromString('2020-01-02 00:00:00.0000')), new TimePeriod(DateTime::fromString('2020-05-02 00:00:00.0000'), DateTime::fromString('2020-05-03 00:00:00.0000')), ]; yield [ false, new TimePeriod(DateTime::fromString('2020-01-02 00:00:00.0000'), DateTime::fromString('2020-01-01 00:00:00.0000')), new TimePeriod(DateTime::fromString('2020-05-02 00:00:00.0000'), DateTime::fromString('2020-05-03 00:00:00.0000')), ]; yield [ false, new TimePeriod(DateTime::fromString('2020-01-01 00:00:00.0000'), DateTime::fromString('2020-01-02 00:00:00.0000')), new TimePeriod(DateTime::fromString('2020-05-03 00:00:00.0000'), DateTime::fromString('2020-05-02 00:00:00.0000')), ]; yield [ false, new TimePeriod(DateTime::fromString('2020-01-01 00:00:00.0000'), DateTime::fromString('2020-01-02 00:00:00.0000')), new TimePeriod(DateTime::fromString('2020-01-02 00:00:00.0000'), DateTime::fromString('2020-01-03 00:00:00.0000')), ]; yield [ false, new TimePeriod(DateTime::fromString('2020-01-02 00:00:00.0000'), DateTime::fromString('2020-01-01 00:00:00.0000')), new TimePeriod(DateTime::fromString('2020-01-03 00:00:00.0000'), DateTime::fromString('2020-01-02 00:00:00.0000')), ]; yield [ true, new TimePeriod(DateTime::fromString('2020-01-01 00:00:00.0000'), DateTime::fromString('2020-01-03 00:00:00.0000')), new TimePeriod(DateTime::fromString('2020-01-02 00:00:00.0000'), DateTime::fromString('2020-01-04 00:00:00.0000')), ]; yield [ true, new TimePeriod(DateTime::fromString('2020-01-03 00:00:00.0000'), DateTime::fromString('2020-01-01 00:00:00.0000')), new TimePeriod(DateTime::fromString('2020-01-04 00:00:00.0000'), DateTime::fromString('2020-01-02 00:00:00.0000')), ]; yield [ true, new TimePeriod(DateTime::fromString('2020-01-03 00:00:00.0000'), DateTime::fromString('2020-01-04 00:00:00.0000')), new TimePeriod(DateTime::fromString('2020-01-02 00:00:00.0000'), DateTime::fromString('2020-01-05 00:00:00.0000')), ]; yield [ true, new TimePeriod(DateTime::fromString('2020-01-04 00:00:00.0000'), DateTime::fromString('2020-01-03 00:00:00.0000')), new TimePeriod(DateTime::fromString('2020-01-05 00:00:00.0000'), DateTime::fromString('2020-01-02 00:00:00.0000')), ]; yield [ true, new TimePeriod(DateTime::fromString('2020-01-03 00:00:00.0000'), DateTime::fromString('2020-01-10 00:00:00.0000')), new TimePeriod(DateTime::fromString('2020-01-02 00:00:00.0000'), DateTime::fromString('2020-01-05 00:00:00.0000')), ]; yield [ true, new TimePeriod(DateTime::fromString('2020-01-10 00:00:00.0000'), DateTime::fromString('2020-01-03 00:00:00.0000')), new TimePeriod(DateTime::fromString('2020-01-05 00:00:00.0000'), DateTime::fromString('2020-01-02 00:00:00.0000')), ]; yield [ false, new TimePeriod(DateTime::fromString('2020-01-07 00:00:00.0000'), DateTime::fromString('2020-01-10 00:00:00.0000')), new TimePeriod(DateTime::fromString('2020-01-02 00:00:00.0000'), DateTime::fromString('2020-01-05 00:00:00.0000')), ]; yield [ false, new TimePeriod(DateTime::fromString('2020-01-10 00:00:00.0000'), DateTime::fromString('2020-01-07 00:00:00.0000')), new TimePeriod(DateTime::fromString('2020-01-05 00:00:00.0000'), DateTime::fromString('2020-01-02 00:00:00.0000')), ]; yield [ true, new TimePeriod(DateTime::fromString('2020-01-01 00:00:00.0000'), DateTime::fromString('2020-01-10 00:00:00.0000')), new TimePeriod(DateTime::fromString('2020-01-02 00:00:00.0000'), DateTime::fromString('2020-01-05 00:00:00.0000')), ]; yield [ true, new TimePeriod(DateTime::fromString('2020-01-10 00:00:00.0000'), DateTime::fromString('2020-01-01 00:00:00.0000')), new TimePeriod(DateTime::fromString('2020-01-05 00:00:00.0000'), DateTime::fromString('2020-01-02 00:00:00.0000')), ]; yield [ true, new TimePeriod(DateTime::fromString('2020-01-01 00:00:00.0000'), DateTime::fromString('2020-01-02 00:00:00.0000')), new TimePeriod(DateTime::fromString('2020-01-01 00:00:00.0000'), DateTime::fromString('2020-01-02 00:00:00.0000')), ]; yield [ false, new TimePeriod(DateTime::fromString('2020-01-01 00:00:00.0000'), DateTime::fromString('2020-01-02 00:00:00.0000')), new TimePeriod(DateTime::fromString('2020-01-02 00:00:00.0000'), DateTime::fromString('2020-01-03 00:00:00.0000')), ]; yield [ false, new TimePeriod(DateTime::fromString('2020-01-03 00:00:00.0000'), DateTime::fromString('2020-01-05 00:00:00.0000')), new TimePeriod(DateTime::fromString('2020-01-05 00:00:00.0000'), DateTime::fromString('2020-01-08 00:00:00.0000')), ]; yield [ true, new TimePeriod(DateTime::fromString('2020-01-03 00:00:00.0000'), DateTime::fromString('2020-01-05 00:00:00.0000')), new TimePeriod(DateTime::fromString('2020-01-03 00:00:00.0000'), DateTime::fromString('2020-01-08 00:00:00.0000')), ]; yield [ true, new TimePeriod(DateTime::fromString('2020-01-04 00:00:00.0000'), DateTime::fromString('2020-01-13 00:00:00.0000')), new TimePeriod(DateTime::fromString('2020-01-03 00:00:00.0000'), DateTime::fromString('2020-01-08 00:00:00.0000')), ]; } /** * @return \Generator */ public static function period_abuts_other_period_data_provider() : \Generator { yield [ true, new TimePeriod(DateTime::fromString('2020-01-01 00:00:00.0000'), DateTime::fromString('2020-01-02 00:00:00.0000')), new TimePeriod(DateTime::fromString('2020-01-02 00:00:00.0000'), DateTime::fromString('2020-01-03 00:00:00.0000')), ]; yield [ true, new TimePeriod(DateTime::fromString('2020-01-04 00:00:00.0000'), DateTime::fromString('2020-01-05 00:00:00.0000')), new TimePeriod(DateTime::fromString('2020-01-03 00:00:00.0000'), DateTime::fromString('2020-01-04 00:00:00.0000')), ]; yield [ true, new TimePeriod(DateTime::fromString('2020-01-05 00:00:00.0000'), DateTime::fromString('2020-01-04 00:00:00.0000')), new TimePeriod(DateTime::fromString('2020-01-04 00:00:00.0000'), DateTime::fromString('2020-01-03 00:00:00.0000')), ]; yield [ false, new TimePeriod(DateTime::fromString('2020-01-04 00:00:00.0000'), DateTime::fromString('2020-01-05 00:00:00.0000')), new TimePeriod(DateTime::fromString('2020-01-02 00:00:00.0000'), DateTime::fromString('2020-01-03 00:00:00.0000')), ]; yield [ false, new TimePeriod(DateTime::fromString('2020-01-02 00:00:00.0000'), DateTime::fromString('2020-01-03 00:00:00.0000')), new TimePeriod(DateTime::fromString('2020-01-04 00:00:00.0000'), DateTime::fromString('2020-01-05 00:00:00.0000')), ]; } public function test_distance_in_time_unit_from_start_to_end_date() : void { $period = new TimePeriod( DateTime::fromString('2020-01-01 00:00:00.0000'), DateTime::fromString('2020-01-02 00:00:00.0000') ); $this->assertSame(86400, $period->distance()->inSeconds()); $this->assertFalse($period->distance()->isNegative()); } public function test_distance_in_time_unit_before_and_after_unix_epoch() : void { $period = new TimePeriod( DateTime::fromString('1969-01-01 00:00:00.0000'), DateTime::fromString('2020-01-01 00:00:00.0000') ); $this->assertSame(1609372800, $period->distance()->inSeconds()); $this->assertFalse($period->distance()->isNegative()); } public function test_distance_in_time_unit_before_and_after_unix_epoch_inverse() : void { $period = new TimePeriod( DateTime::fromString('2020-01-01 00:00:00.0000'), DateTime::fromString('1969-01-01 00:00:00.0000') ); $this->assertSame(-1609372800, $period->distance()->inSeconds()); $this->assertTrue($period->distance()->isNegative()); } public function test_distance_in_time_unit_before_and_before_unix_epoch() : void { $period = new TimePeriod( DateTime::fromString('1969-01-01 00:00:00.0000'), DateTime::fromString('1969-01-01 01:00:00.0000') ); $this->assertSame(3600, $period->distance()->inSeconds()); $this->assertFalse($period->distance()->isNegative()); } public function test_distance_in_time_unit_before_and_before_unix_epoch_inverse() : void { $period = new TimePeriod( DateTime::fromString('1969-01-01 01:00:00.0000'), DateTime::fromString('1969-01-01 00:00:00.0000') ); $this->assertSame(-3600, $period->distance()->inSeconds()); $this->assertTrue($period->distance()->isNegative()); } public function test_precise_distance_in_time_unit_from_start_to_end() : void { $period = new TimePeriod( DateTime::fromString('2020-01-01 12:25:30.079635'), DateTime::fromString('2020-01-01 12:25:32.588460') ); $this->assertSame('2.508825', $period->distance()->inSecondsPrecise()); } public function test_distance_in_time_unit_from_start_to_end_date_between_years() : void { $period = new TimePeriod( DateTime::fromString('2020-01-01 00:00:00.0000'), DateTime::fromString('2021-01-01 00:00:00.0000') ); $this->assertSame(DateTime::fromString('2020-01-01 00:00:00.0000')->year()->numberOfDays(), $period->distance()->inDays()); $this->assertFalse($period->distance()->isNegative()); $this->assertTrue(DateTime::fromString('2020-01-01 00:00:00.0000')->year()->isLeap()); } #[DataProvider('iterating_through_intervals_provider')] public function test_iterating_through_intervals(string $startDate, string $endDate, bool $forward, TimeUnit $timeUnit, Interval $interval, array $periods, string $format = 'Y-m-d') : void { $period = new TimePeriod( DateTime::fromString($startDate), DateTime::fromString($endDate) ); $timePeriods = ($forward) ? $period->iterate($timeUnit, $interval) : $period->iterateBackward($timeUnit, $interval); $periodsResult = $timePeriods->map(fn (TimePeriod $timePeriod) : string => $timePeriod->start()->format($format) . '...' . $timePeriod->end()->format($format)); $this->assertSame($periods, $periodsResult); } public function test_iterating_through_day_by_hour() : void { $period = new TimePeriod( DateTime::fromString('2020-01-01 00:00:00.0000'), DateTime::fromString('2020-01-02 00:00:00.0000') ); $timePeriods = $period->iterate(TimeUnit::hour(), Interval::rightOpen()); $this->assertCount(23, $timePeriods); $this->assertInstanceOf(TimePeriod::class, $timePeriods->all()[0]); $this->assertInstanceOf(TimePeriod::class, $timePeriods->all()[1]); $this->assertInstanceOf(TimePeriod::class, $timePeriods->all()[2]); $this->assertInstanceOf(TimePeriod::class, $timePeriods->all()[22]); $this->assertSame(0, $timePeriods->all()[0]->start()->time()->hour()); $this->assertSame(1, $timePeriods->all()[0]->end()->time()->hour()); $this->assertSame(1, $timePeriods->all()[1]->start()->time()->hour()); $this->assertSame(2, $timePeriods->all()[1]->end()->time()->hour()); $this->assertSame(2, $timePeriods->all()[2]->start()->time()->hour()); $this->assertSame(3, $timePeriods->all()[2]->end()->time()->hour()); $this->assertSame(22, $timePeriods->all()[22]->start()->time()->hour()); $this->assertSame(23, $timePeriods->all()[22]->end()->time()->hour()); } public function test_iterating_through_day_backward_by_hour() : void { $period = new TimePeriod( DateTime::fromString('2020-01-01 00:00:00.0000'), DateTime::fromString('2020-01-02 00:00:00.0000') ); $timePeriods = $period->iterateBackward(TimeUnit::hour(), Interval::leftOpen()); $this->assertCount(23, $timePeriods); $this->assertInstanceOf(TimePeriod::class, $timePeriods->all()[0]); $this->assertInstanceOf(TimePeriod::class, $timePeriods->all()[1]); $this->assertInstanceOf(TimePeriod::class, $timePeriods->all()[2]); $this->assertInstanceOf(TimePeriod::class, $timePeriods->all()[22]); $this->assertSame(0, $timePeriods->all()[0]->start()->time()->hour()); $this->assertSame(23, $timePeriods->all()[0]->end()->time()->hour()); $this->assertSame(23, $timePeriods->all()[1]->start()->time()->hour()); $this->assertSame(22, $timePeriods->all()[1]->end()->time()->hour()); $this->assertSame(22, $timePeriods->all()[2]->start()->time()->hour()); $this->assertSame(21, $timePeriods->all()[2]->end()->time()->hour()); $this->assertSame(2, $timePeriods->all()[22]->start()->time()->hour()); $this->assertSame(1, $timePeriods->all()[22]->end()->time()->hour()); } public function test_iterating_by_2days_interval_closed_both_ways() : void { $period = new TimePeriod( DateTime::fromString('2020-01-01 00:00:00.0000'), DateTime::fromString('2020-01-05 00:00:00.0000') ); $timePeriods = $period->iterate(TimeUnit::days(3), $interval = Interval::closed()); $timePeriodsBackward = $period->iterateBackward(TimeUnit::days(3), Interval::closed()); $this->assertTrue($interval->isClosed()); $this->assertCount(2, $timePeriods); $this->assertCount(2, $timePeriodsBackward); $this->assertSame('2020-01-01', $timePeriods->all()[0]->start()->format('Y-m-d')); $this->assertSame('2020-01-04', $timePeriods->all()[1]->start()->format('Y-m-d')); $this->assertSame('2020-01-05', $timePeriods->all()[1]->end()->format('Y-m-d')); $this->assertSame('2020-01-05', $timePeriodsBackward->all()[0]->start()->format('Y-m-d')); $this->assertSame('2020-01-02', $timePeriodsBackward->all()[0]->end()->format('Y-m-d')); $this->assertSame('2020-01-02', $timePeriodsBackward->all()[1]->start()->format('Y-m-d')); $this->assertSame('2020-01-01', $timePeriodsBackward->all()[1]->end()->format('Y-m-d')); } public function test_iterating_by_day_interval_closed_both_ways() : void { $period = new TimePeriod( DateTime::fromString('2020-01-01 00:00:00.0000'), DateTime::fromString('2020-01-05 00:00:00.0000') ); $timePeriods = $period->iterate(TimeUnit::day(), $interval = Interval::closed()); $timePeriodsBackward = $period->iterateBackward(TimeUnit::day(), Interval::closed()); $this->assertTrue($interval->isClosed()); $this->assertCount(4, $timePeriods); $this->assertCount(4, $timePeriodsBackward); $this->assertSame('2020-01-01', $timePeriods->all()[0]->start()->format('Y-m-d')); $this->assertSame('2020-01-02', $timePeriods->all()[1]->start()->format('Y-m-d')); $this->assertSame('2020-01-03', $timePeriods->all()[2]->start()->format('Y-m-d')); $this->assertSame('2020-01-04', $timePeriods->all()[3]->start()->format('Y-m-d')); $this->assertSame('2020-01-05', $timePeriods->all()[3]->end()->format('Y-m-d')); $this->assertSame('2020-01-05', $timePeriodsBackward->all()[0]->start()->format('Y-m-d')); $this->assertSame('2020-01-04', $timePeriodsBackward->all()[1]->start()->format('Y-m-d')); $this->assertSame('2020-01-03', $timePeriodsBackward->all()[2]->start()->format('Y-m-d')); $this->assertSame('2020-01-02', $timePeriodsBackward->all()[3]->start()->format('Y-m-d')); $this->assertSame('2020-01-01', $timePeriodsBackward->all()[3]->end()->format('Y-m-d')); } public function test_iterating_by_day_interval_left_open_both_ways() : void { $period = new TimePeriod( DateTime::fromString('2020-01-01 00:00:00.0000'), DateTime::fromString('2020-01-05 00:00:00.0000') ); $timePeriods = $period->iterate(TimeUnit::day(), Interval::leftOpen()); $timePeriodsBackward = $period->iterateBackward(TimeUnit::day(), Interval::leftOpen()); $this->assertCount(3, $timePeriods); $this->assertCount(3, $timePeriodsBackward); $this->assertSame('2020-01-02', $timePeriods->all()[0]->start()->format('Y-m-d')); $this->assertSame('2020-01-03', $timePeriods->all()[0]->end()->format('Y-m-d')); $this->assertSame('2020-01-03', $timePeriods->all()[1]->start()->format('Y-m-d')); $this->assertSame('2020-01-04', $timePeriods->all()[1]->end()->format('Y-m-d')); $this->assertSame('2020-01-04', $timePeriods->all()[2]->start()->format('Y-m-d')); $this->assertSame('2020-01-05', $timePeriods->all()[2]->end()->format('Y-m-d')); $this->assertSame('2020-01-05', $timePeriodsBackward->all()[0]->start()->format('Y-m-d')); $this->assertSame('2020-01-04', $timePeriodsBackward->all()[0]->end()->format('Y-m-d')); $this->assertSame('2020-01-04', $timePeriodsBackward->all()[1]->start()->format('Y-m-d')); $this->assertSame('2020-01-03', $timePeriodsBackward->all()[1]->end()->format('Y-m-d')); $this->assertSame('2020-01-03', $timePeriodsBackward->all()[2]->start()->format('Y-m-d')); $this->assertSame('2020-01-02', $timePeriodsBackward->all()[2]->end()->format('Y-m-d')); } public function test_iterating_by_day_interval_right_open_forward() : void { $period = new TimePeriod( DateTime::fromString('2020-01-01 00:00:00.0000'), DateTime::fromString('2020-01-05 00:00:00.0000') ); $timePeriods = $period->iterate(TimeUnit::day(), Interval::rightOpen()); $this->assertCount(3, $timePeriods); $this->assertSame('2020-01-01', $timePeriods->all()[0]->start()->format('Y-m-d')); $this->assertSame('2020-01-02', $timePeriods->all()[0]->end()->format('Y-m-d')); $this->assertSame('2020-01-02', $timePeriods->all()[1]->start()->format('Y-m-d')); $this->assertSame('2020-01-03', $timePeriods->all()[1]->end()->format('Y-m-d')); $this->assertSame('2020-01-03', $timePeriods->all()[2]->start()->format('Y-m-d')); $this->assertSame('2020-01-04', $timePeriods->all()[2]->end()->format('Y-m-d')); } public function test_iterating_by_day_interval_right_open_backward() : void { $period = new TimePeriod( DateTime::fromString('2020-01-01 00:00:00.0000'), DateTime::fromString('2020-01-05 00:00:00.0000') ); $timePeriodsBackward = $period->iterateBackward(TimeUnit::day(), Interval::rightOpen()); $this->assertCount(3, $timePeriodsBackward); $this->assertSame('2020-01-04', $timePeriodsBackward->all()[0]->start()->format('Y-m-d')); $this->assertSame('2020-01-03', $timePeriodsBackward->all()[0]->end()->format('Y-m-d')); $this->assertSame('2020-01-03', $timePeriodsBackward->all()[1]->start()->format('Y-m-d')); $this->assertSame('2020-01-02', $timePeriodsBackward->all()[1]->end()->format('Y-m-d')); $this->assertSame('2020-01-02', $timePeriodsBackward->all()[2]->start()->format('Y-m-d')); $this->assertSame('2020-01-01', $timePeriodsBackward->all()[2]->end()->format('Y-m-d')); } public function test_iterating_by_month_interval_closed_both_ways() : void { $period = new TimePeriod( DateTime::fromString('2020-01-01 00:00:00.0000'), DateTime::fromString('2021-01-01 00:00:00.0000') ); $timePeriods = $period->iterate(RelativeTimeUnit::month(), Interval::closed()); $timePeriodsBackward = $period->iterateBackward(RelativeTimeUnit::month(), Interval::closed()); $this->assertCount(12, $timePeriods); $this->assertCount(12, $timePeriodsBackward); $this->assertSame('2020-01-01', $timePeriods->all()[0]->start()->format('Y-m-d')); $this->assertSame(31, $timePeriods->all()[0]->distance()->inDays()); $this->assertSame('2020-02-01', $timePeriods->all()[1]->start()->format('Y-m-d')); $this->assertSame(29, $timePeriods->all()[1]->distance()->inDays()); $this->assertSame('2020-03-01', $timePeriods->all()[2]->start()->format('Y-m-d')); $this->assertSame(31, $timePeriods->all()[2]->distance()->inDays()); $this->assertSame('2020-04-01', $timePeriods->all()[3]->start()->format('Y-m-d')); $this->assertSame(30, $timePeriods->all()[3]->distance()->inDays()); $this->assertSame('2020-05-01', $timePeriods->all()[4]->start()->format('Y-m-d')); $this->assertSame(31, $timePeriods->all()[4]->distance()->inDays()); $this->assertSame('2020-06-01', $timePeriods->all()[5]->start()->format('Y-m-d')); $this->assertSame(30, $timePeriods->all()[5]->distance()->inDays()); $this->assertSame('2020-07-01', $timePeriods->all()[6]->start()->format('Y-m-d')); $this->assertSame(31, $timePeriods->all()[6]->distance()->inDays()); $this->assertSame('2020-08-01', $timePeriods->all()[7]->start()->format('Y-m-d')); $this->assertSame(31, $timePeriods->all()[7]->distance()->inDays()); $this->assertSame('2020-09-01', $timePeriods->all()[8]->start()->format('Y-m-d')); $this->assertSame(30, $timePeriods->all()[8]->distance()->inDays()); $this->assertSame('2020-10-01', $timePeriods->all()[9]->start()->format('Y-m-d')); $this->assertSame(31, $timePeriods->all()[9]->distance()->inDays()); $this->assertSame('2020-11-01', $timePeriods->all()[10]->start()->format('Y-m-d')); $this->assertSame(30, $timePeriods->all()[10]->distance()->inDays()); $this->assertSame('2020-12-01', $timePeriods->all()[11]->start()->format('Y-m-d')); $this->assertSame('2021-01-01', $timePeriods->all()[11]->end()->format('Y-m-d')); $this->assertSame(31, $timePeriods->all()[11]->distance()->inDays()); $this->assertSame('2021-01-01', $timePeriodsBackward->all()[0]->start()->format('Y-m-d')); $this->assertSame('2020-12-01', $timePeriodsBackward->all()[1]->start()->format('Y-m-d')); $this->assertSame('2020-11-01', $timePeriodsBackward->all()[2]->start()->format('Y-m-d')); $this->assertSame('2020-10-01', $timePeriodsBackward->all()[3]->start()->format('Y-m-d')); $this->assertSame('2020-09-01', $timePeriodsBackward->all()[4]->start()->format('Y-m-d')); $this->assertSame('2020-08-01', $timePeriodsBackward->all()[5]->start()->format('Y-m-d')); $this->assertSame('2020-07-01', $timePeriodsBackward->all()[6]->start()->format('Y-m-d')); $this->assertSame('2020-06-01', $timePeriodsBackward->all()[7]->start()->format('Y-m-d')); $this->assertSame('2020-05-01', $timePeriodsBackward->all()[8]->start()->format('Y-m-d')); $this->assertSame('2020-04-01', $timePeriodsBackward->all()[9]->start()->format('Y-m-d')); $this->assertSame('2020-03-01', $timePeriodsBackward->all()[10]->start()->format('Y-m-d')); $this->assertSame('2020-02-01', $timePeriodsBackward->all()[11]->start()->format('Y-m-d')); $this->assertSame('2020-01-01', $timePeriodsBackward->all()[11]->end()->format('Y-m-d')); } public function test_iterating_by_year_interval_closed_both_ways() : void { $period = new TimePeriod( DateTime::fromString('2020-01-01 00:00:00.0000'), DateTime::fromString('2025-01-01 00:00:00.0000') ); $timePeriods = $period->iterate(RelativeTimeUnit::year(), Interval::closed()); $timePeriodsBackward = $period->iterateBackward(RelativeTimeUnit::year(), Interval::closed()); $this->assertCount(5, $timePeriods); $this->assertCount(5, $timePeriodsBackward); $this->assertSame('2020-01-01', $timePeriods->all()[0]->start()->format('Y-m-d')); $this->assertSame('2021-01-01', $timePeriods->all()[1]->start()->format('Y-m-d')); $this->assertSame('2022-01-01', $timePeriods->all()[2]->start()->format('Y-m-d')); $this->assertSame('2023-01-01', $timePeriods->all()[3]->start()->format('Y-m-d')); $this->assertSame('2024-01-01', $timePeriods->all()[4]->start()->format('Y-m-d')); $this->assertSame('2025-01-01', $timePeriods->all()[4]->end()->format('Y-m-d')); $this->assertSame('2025-01-01', $timePeriodsBackward->all()[0]->start()->format('Y-m-d')); $this->assertSame('2024-01-01', $timePeriodsBackward->all()[1]->start()->format('Y-m-d')); $this->assertSame('2023-01-01', $timePeriodsBackward->all()[2]->start()->format('Y-m-d')); $this->assertSame('2022-01-01', $timePeriodsBackward->all()[3]->start()->format('Y-m-d')); $this->assertSame('2021-01-01', $timePeriodsBackward->all()[4]->start()->format('Y-m-d')); $this->assertSame('2020-01-01', $timePeriodsBackward->all()[4]->end()->format('Y-m-d')); } public function test_iterating_through_day_backward_by_2_days() : void { $period = new TimePeriod( DateTime::fromString('2020-01-01 00:00:00.0000'), DateTime::fromString('2020-01-02 00:00:00.0000') ); $timePeriods = $period->iterateBackward(TimeUnit::days(2), Interval::closed()); $this->assertSame('2020-01-01', $timePeriods->all()[0]->end()->format('Y-m-d')); $this->assertSame('2020-01-02', $timePeriods->all()[0]->start()->format('Y-m-d')); $this->assertCount(1, $timePeriods); } #[DataProvider('overlapping_time_periods_data_provider')] public function test_overlapping_time_periods(bool $overlap, TimePeriod $firstPeriod, TimePeriod $secondPeriod) : void { $this->assertSame($overlap, $firstPeriod->overlaps($secondPeriod)); } public function test_period_is_forward() : void { $this->assertTrue( (new TimePeriod(DateTime::fromString('2020-01-01 00:00:00.0000'), DateTime::fromString('2020-01-02 00:00:00.0000'))) ->isForward() ); $this->assertFalse( (new TimePeriod(DateTime::fromString('2020-01-01 00:00:00.0000'), DateTime::fromString('2020-01-02 00:00:00.0000'))) ->isBackward() ); } public function test_period_is_backward() : void { $this->assertTrue( (new TimePeriod(DateTime::fromString('2020-01-02 00:00:00.0000'), DateTime::fromString('2020-01-01 00:00:00.0000'))) ->isBackward() ); $this->assertFalse( (new TimePeriod(DateTime::fromString('2020-01-02 00:00:00.0000'), DateTime::fromString('2020-01-01 00:00:00.0000'))) ->isForward() ); } #[DataProvider('period_abuts_other_period_data_provider')] public function test_period_abuts_other_period(bool $abuts, TimePeriod $firstPeriod, TimePeriod $secondPeriod) : void { $this->assertSame($abuts, $firstPeriod->abuts($secondPeriod)); } public function test_one_period_contains_the_same_period() : void { $this->assertTrue( (new TimePeriod(DateTime::fromString('2020-01-01 00:00:00'), DateTime::fromString('2020-01-02 00:00:00'))) ->contains(new TimePeriod(DateTime::fromString('2020-01-01 00:00:00'), DateTime::fromString('2020-01-02 00:00:00'))) ); } public function test_one_period_contains_shorted_period() : void { $this->assertTrue( (new TimePeriod(DateTime::fromString('2020-01-01 00:00:00'), DateTime::fromString('2020-01-05 00:00:00'))) ->contains(new TimePeriod(DateTime::fromString('2020-01-02 00:00:00'), DateTime::fromString('2020-01-03 00:00:00'))) ); } public function test_one_period_not_contains_other_overlapping_period() : void { $this->assertFalse( (new TimePeriod(DateTime::fromString('2020-01-05 00:00:00'), DateTime::fromString('2020-01-10 00:00:00'))) ->contains(new TimePeriod(DateTime::fromString('2020-01-02 00:00:00'), DateTime::fromString('2020-01-07 00:00:00'))) ); } public function test_merge_not_overlapping_time_periods() : void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("Can't merge not overlapping time periods"); (new TimePeriod(DateTime::fromString('2020-01-05 00:00:00'), DateTime::fromString('2020-01-10 00:00:00'))) ->merge(new TimePeriod(DateTime::fromString('2020-01-20 00:00:00'), DateTime::fromString('2020-01-25 00:00:00'))); } public function test_merge_left_overlapping_time_periods() : void { $newPeriod = (new TimePeriod(DateTime::fromString('2020-01-05 00:00:00'), DateTime::fromString('2020-01-10 00:00:00'))) ->merge(new TimePeriod(DateTime::fromString('2020-01-08 00:00:00'), DateTime::fromString('2020-01-25 00:00:00'))); $this->assertSame( '2020-01-05', $newPeriod->start()->format('Y-m-d') ); $this->assertSame( '2020-01-25', $newPeriod->end()->format('Y-m-d') ); } public function test_merge_abuts_time_periods() : void { $newPeriod = (new TimePeriod(DateTime::fromString('2020-01-01 00:00:00'), DateTime::fromString('2020-01-02 00:00:00'))) ->merge(new TimePeriod(DateTime::fromString('2020-01-02 00:00:00'), DateTime::fromString('2020-01-05 00:00:00'))); $this->assertSame( '2020-01-01', $newPeriod->start()->format('Y-m-d') ); $this->assertSame( '2020-01-05', $newPeriod->end()->format('Y-m-d') ); } public function test_merge_right_overlapping_time_periods() : void { $newPeriod = (new TimePeriod(DateTime::fromString('2020-01-10 00:00:00'), DateTime::fromString('2020-02-10 00:00:00'))) ->merge(new TimePeriod(DateTime::fromString('2020-01-08 00:00:00'), DateTime::fromString('2020-01-25 00:00:00'))); $this->assertSame( '2020-01-08', $newPeriod->start()->format('Y-m-d') ); $this->assertSame( '2020-02-10', $newPeriod->end()->format('Y-m-d') ); } public function test_serialization() : void { $timePeriod = new TimePeriod( DateTime::fromString('2020-01-01 00:00:00'), DateTime::fromString('2020-01-02 00:00:00') ); $this->assertObjectEquals( $timePeriod, \unserialize(\serialize($timePeriod)), 'isEqual' ); } public function test_is_equal() : void { $timePeriod1 = new TimePeriod( DateTime::fromString('2020-01-01 00:00:00'), DateTime::fromString('2020-01-02 00:00:00') ); $timePeriod2 = new TimePeriod( DateTime::fromString('2020-01-01 00:00:00'), DateTime::fromString('2020-01-03 00:00:00') ); $this->assertFalse($timePeriod1->isEqualTo($timePeriod2)); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/Gregorian/TimePeriodsIteratorTest.php ================================================ assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $array[0]->start()); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $array[0]->end()); $this->assertEquals(DateTime::fromString('2020-01-08 00:00:00 UTC'), $array[7]->start()); $this->assertEquals(DateTime::fromString('2020-01-09 00:00:00 UTC'), $array[7]->end()); } public function test_forward_even_iterator_left_open() : void { $iterator = new TimePeriodsIterator( DateTime::fromString('2020-01-01 00:00:00 UTC'), DateTime::fromString('2020-01-10 00:00:00 UTC'), TimeUnit::day(), Interval::leftOpen() ); $array = \iterator_to_array($iterator); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $array[0]->start()); $this->assertEquals(DateTime::fromString('2020-01-03 00:00:00 UTC'), $array[0]->end()); $this->assertEquals(DateTime::fromString('2020-01-09 00:00:00 UTC'), $array[7]->start()); $this->assertEquals(DateTime::fromString('2020-01-10 00:00:00 UTC'), $array[7]->end()); } public function test_forward_even_iterator_open() : void { $iterator = new TimePeriodsIterator( DateTime::fromString('2020-01-01 00:00:00 UTC'), DateTime::fromString('2020-01-10 00:00:00 UTC'), TimeUnit::day(), Interval::open() ); $array = \iterator_to_array($iterator); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $array[0]->start()); $this->assertEquals(DateTime::fromString('2020-01-03 00:00:00 UTC'), $array[0]->end()); $this->assertEquals(DateTime::fromString('2020-01-07 00:00:00 UTC'), $array[5]->start()); $this->assertEquals(DateTime::fromString('2020-01-08 00:00:00 UTC'), $array[5]->end()); $this->assertEquals(DateTime::fromString('2020-01-08 00:00:00 UTC'), $array[6]->start()); $this->assertEquals(DateTime::fromString('2020-01-09 00:00:00 UTC'), $array[6]->end()); } public function test_forward_even_iterator_closed() : void { $iterator = new TimePeriodsIterator( DateTime::fromString('2020-01-01 00:00:00 UTC'), DateTime::fromString('2020-01-10 00:00:00 UTC'), TimeUnit::day(), Interval::closed() ); $array = \iterator_to_array($iterator); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $array[0]->start()); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $array[0]->end()); $this->assertEquals(DateTime::fromString('2020-01-09 00:00:00 UTC'), $array[8]->start()); $this->assertEquals(DateTime::fromString('2020-01-10 00:00:00 UTC'), $array[8]->end()); } public function test_backward_even_iterator_left_open() : void { $iterator = new TimePeriodsIterator( DateTime::fromString('2020-01-10 00:00:00 UTC'), DateTime::fromString('2020-01-01 00:00:00 UTC'), TimeUnit::day()->toNegative(), Interval::leftOpen() ); $array = \iterator_to_array($iterator); $this->assertEquals(DateTime::fromString('2020-01-10 00:00:00 UTC'), $array[0]->start()); $this->assertEquals(DateTime::fromString('2020-01-09 00:00:00 UTC'), $array[0]->end()); $this->assertEquals(DateTime::fromString('2020-01-03 00:00:00 UTC'), $array[7]->start()); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $array[7]->end()); } public function test_backward_even_iterator_right_open() : void { $iterator = new TimePeriodsIterator( DateTime::fromString('2020-01-10 00:00:00 UTC'), DateTime::fromString('2020-01-01 00:00:00 UTC'), TimeUnit::day()->toNegative(), Interval::rightOpen() ); $array = \iterator_to_array($iterator); $this->assertEquals(DateTime::fromString('2020-01-09 00:00:00 UTC'), $array[0]->start()); $this->assertEquals(DateTime::fromString('2020-01-08 00:00:00 UTC'), $array[0]->end()); $this->assertEquals(DateTime::fromString('2020-01-03 00:00:00 UTC'), $array[6]->start()); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $array[6]->end()); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $array[7]->start()); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $array[7]->end()); } public function test_backward_even_iterator_open() : void { $iterator = new TimePeriodsIterator( DateTime::fromString('2020-01-10 00:00:00 UTC'), DateTime::fromString('2020-01-01 00:00:00 UTC'), TimeUnit::day()->toNegative(), Interval::open() ); $array = \iterator_to_array($iterator); $this->assertEquals(DateTime::fromString('2020-01-09 00:00:00 UTC'), $array[0]->start()); $this->assertEquals(DateTime::fromString('2020-01-08 00:00:00 UTC'), $array[0]->end()); $this->assertEquals(DateTime::fromString('2020-01-04 00:00:00 UTC'), $array[5]->start()); $this->assertEquals(DateTime::fromString('2020-01-03 00:00:00 UTC'), $array[5]->end()); $this->assertEquals(DateTime::fromString('2020-01-03 00:00:00 UTC'), $array[6]->start()); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $array[6]->end()); } public function test_backward_even_iterator_closed() : void { $iterator = new TimePeriodsIterator( DateTime::fromString('2020-01-10 00:00:00 UTC'), DateTime::fromString('2020-01-01 00:00:00 UTC'), TimeUnit::day()->toNegative(), Interval::closed() ); $array = \iterator_to_array($iterator); $this->assertEquals(DateTime::fromString('2020-01-10 00:00:00 UTC'), $array[0]->start()); $this->assertEquals(DateTime::fromString('2020-01-09 00:00:00 UTC'), $array[0]->end()); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $array[8]->start()); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $array[8]->end()); } public function test_forward_exceeded_iteration_closed() : void { $iterator = new TimePeriodsIterator( DateTime::fromString('2020-01-01 00:00:00 UTC'), DateTime::fromString('2020-01-06 00:00:00 UTC'), TimeUnit::days(2), Interval::closed() ); $this->assertCount(3, $iterator); $array = \iterator_to_array($iterator); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $array[0]->start()); $this->assertEquals(DateTime::fromString('2020-01-03 00:00:00 UTC'), $array[0]->end()); $this->assertEquals(DateTime::fromString('2020-01-03 00:00:00 UTC'), $array[1]->start()); $this->assertEquals(DateTime::fromString('2020-01-05 00:00:00 UTC'), $array[1]->end()); $this->assertEquals(DateTime::fromString('2020-01-05 00:00:00 UTC'), $array[2]->start()); $this->assertEquals(DateTime::fromString('2020-01-06 00:00:00 UTC'), $array[2]->end()); } public function test_backward_exceeded_iteration_closed() : void { $iterator = new TimePeriodsIterator( DateTime::fromString('2020-01-06 00:00:00 UTC'), DateTime::fromString('2020-01-01 00:00:00 UTC'), TimeUnit::days(2)->toNegative(), Interval::closed() ); $this->assertCount(3, $iterator); $array = \iterator_to_array($iterator); $this->assertEquals(DateTime::fromString('2020-01-06 00:00:00 UTC'), $array[0]->start()); $this->assertEquals(DateTime::fromString('2020-01-04 00:00:00 UTC'), $array[0]->end()); $this->assertEquals(DateTime::fromString('2020-01-04 00:00:00 UTC'), $array[1]->start()); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $array[1]->end()); $this->assertEquals(DateTime::fromString('2020-01-02 00:00:00 UTC'), $array[2]->start()); $this->assertEquals(DateTime::fromString('2020-01-01 00:00:00 UTC'), $array[2]->end()); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/Gregorian/TimePeriodsSortTest.php ================================================ assertEquals(TimePeriodsSort::asc(), TimePeriodsSort::startDate(true)); $this->assertEquals(TimePeriodsSort::desc(), TimePeriodsSort::startDate(false)); $this->assertTrue(TimePeriodsSort::startDate(true)->isAscending()); $this->assertTrue(TimePeriodsSort::startDate(true)->byStartDate()); $this->assertFalse(TimePeriodsSort::startDate(false)->isAscending()); } public function test_sort_by_end_date() : void { $this->assertTrue(TimePeriodsSort::endDate(true)->isAscending()); $this->assertFalse(TimePeriodsSort::endDate(true)->byStartDate()); $this->assertFalse(TimePeriodsSort::endDate(false)->isAscending()); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/Gregorian/TimePeriodsTest.php ================================================ until(DateTime::fromString('2020-01-01 01:00:00.000000')) ->iterate(TimeUnit::minute(), Interval::rightOpen()) ->each(function (TimePeriod $timePeriod) use (&$counter) : void { /** @psalm-suppress MixedOperand */ $counter += 1; }); $this->assertSame(59, $counter); } public function test_get_iterator() : void { $timePeriods = DateTime::fromString('2020-01-01 00:00:00.000000') ->until(DateTime::fromString('2020-01-01 01:00:00.000000')) ->iterate(TimeUnit::minute(), Interval::closed()); $this->assertEquals($timePeriods->all(), \iterator_to_array($timePeriods->getIterator())); } public function test_gap_for_empty_periods() : void { $this->assertCount(0, (TimePeriods::fromArray())->gaps()); } public function test_gap_for_periods_with_one_period() : void { $this->assertCount( 0, (TimePeriods::fromArray( new TimePeriod(DateTime::fromString('2020-01-01 01:00:00.000000'), DateTime::fromString('2020-01-02 01:00:00.000000')) ))->gaps() ); } public function test_gap_for_periods_with_equal_time_periods() : void { $this->assertCount( 0, (TimePeriods::fromArray( new TimePeriod(DateTime::fromString('2020-01-01 01:00:00.000000'), DateTime::fromString('2020-01-02 01:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-01 01:00:00.000000'), DateTime::fromString('2020-01-02 01:00:00.000000')) ))->gaps() ); } public function test_gap_periods() : void { $this->assertObjectEquals( (TimePeriods::fromArray( new TimePeriod(DateTime::fromString('2020-01-02 00:00:00.000000'), DateTime::fromString('2020-01-03 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-07 00:00:00.000000'), DateTime::fromString('2020-01-08 00:00:00.000000')), )), (TimePeriods::fromArray( new TimePeriod(DateTime::fromString('2020-01-10 00:00:00.000000'), DateTime::fromString('2020-01-08 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-01 00:00:00.000000'), DateTime::fromString('2020-01-02 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-05 00:00:00.000000'), DateTime::fromString('2020-01-07 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-03 00:00:00.000000'), DateTime::fromString('2020-01-06 00:00:00.000000')), ))->gaps(), 'isEqual' ); } public function test_compare_different_periods() : void { $this->assertFalse( (TimePeriods::fromArray( new TimePeriod(DateTime::fromString('2020-01-02 00:00:00.000000'), DateTime::fromString('2020-01-03 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-07 00:00:00.000000'), DateTime::fromString('2020-01-08 00:00:00.000000')), ))->isEqualTo((TimePeriods::fromArray( new TimePeriod(DateTime::fromString('2020-01-10 00:00:00.000000'), DateTime::fromString('2020-01-08 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-01 00:00:00.000000'), DateTime::fromString('2020-01-02 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-05 00:00:00.000000'), DateTime::fromString('2020-01-07 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-03 00:00:00.000000'), DateTime::fromString('2020-01-06 00:00:00.000000')), ))) ); $this->assertFalse( (TimePeriods::fromArray( new TimePeriod(DateTime::fromString('2020-01-02 00:00:00.000000'), DateTime::fromString('2020-01-03 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-07 00:00:00.000000'), DateTime::fromString('2020-01-08 00:00:00.000000')), ))->isEqualTo((TimePeriods::fromArray( new TimePeriod(DateTime::fromString('2020-01-02 00:00:00.000000'), DateTime::fromString('2020-01-03 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-07 00:00:00.000000'), DateTime::fromString('2020-01-08 00:00:00.000001')), ))) ); } public function test_compare_identical_periods() : void { $this->assertTrue( (TimePeriods::fromArray( new TimePeriod(DateTime::fromString('2020-01-02 00:00:00.000000'), DateTime::fromString('2020-01-03 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-07 00:00:00.000000'), DateTime::fromString('2020-01-08 00:00:00.000000')), ))->isEqualTo((TimePeriods::fromArray( new TimePeriod(DateTime::fromString('2020-01-02 00:00:00.000000'), DateTime::fromString('2020-01-03 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-07 00:00:00.000000'), DateTime::fromString('2020-01-08 00:00:00.000000')), ))) ); } public function test_no_gaps_in_overlapping_periods() : void { $this->assertEquals( (TimePeriods::fromArray()), (TimePeriods::fromArray( new TimePeriod(DateTime::fromString('2021-01-15T00:00:00+00:00'), DateTime::fromString('2021-01-29T00:00:00+00:00')), new TimePeriod(DateTime::fromString('2021-01-06T14:32:01+00:00'), DateTime::fromString('2021-01-20T15:24:53+00:00')), new TimePeriod(DateTime::fromString('2021-01-10T13:03:08+00:00'), DateTime::fromString('2021-01-13T14:24:54+00:00')), new TimePeriod(DateTime::fromString('2020-12-11T13:03:08+00:00'), DateTime::fromString('2021-01-10T13:03:08+00:00')), ))->gaps() ); } public function test_no_gaps_in_abuts_periods() : void { $this->assertEquals( (TimePeriods::fromArray()), (TimePeriods::fromArray( new TimePeriod(DateTime::fromString('2019-09-09T12:53:30+00:00'), DateTime::fromString('2019-10-09T12:53:30+00:00')), new TimePeriod(DateTime::fromString('2019-08-10T12:53:30+00:00'), DateTime::fromString('2019-09-09T12:53:30+00:00')), new TimePeriod(DateTime::fromString('2019-07-11T12:53:30+00:00'), DateTime::fromString('2019-08-10T12:53:30+00:00')), ))->gaps() ); } public function test_map_periods() : void { $timePeriods = TimePeriods::fromArray( new TimePeriod(DateTime::fromString('2020-01-10 00:00:00.000000'), DateTime::fromString('2020-01-08 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-01 00:00:00.000000'), DateTime::fromString('2020-01-02 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-05 00:00:00.000000'), DateTime::fromString('2020-01-07 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-03 00:00:00.000000'), DateTime::fromString('2020-01-06 00:00:00.000000')) ); $this->assertCount( 4, $distances = $timePeriods->map(fn (TimePeriod $timePeriod) => $timePeriod->distance()) ); $this->assertInstanceOf(TimeUnit::class, $distances[0]); } public function test_filter_periods() : void { $timePeriods = TimePeriods::fromArray( new TimePeriod(DateTime::fromString('2020-01-10 00:00:00.000000'), DateTime::fromString('2020-01-08 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-01 00:00:00.000000'), DateTime::fromString('2020-01-02 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-05 00:00:00.000000'), DateTime::fromString('2020-01-07 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-03 00:00:00.000000'), DateTime::fromString('2020-01-06 00:00:00.000000')) ); $this->assertCount( 1, $distances = $timePeriods->filter(fn (TimePeriod $timePeriod) => $timePeriod->start()->isEqualTo(DateTime::fromString('2020-01-03 00:00:00.000000'))) ); $this->assertInstanceOf(TimePeriod::class, \array_values($distances->all())[0]); } public function test_sort_by_start_date_asc() : void { $timePeriods = TimePeriods::fromArray( new TimePeriod(DateTime::fromString('2020-01-10 00:00:00.000000'), DateTime::fromString('2020-01-08 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-01 00:00:00.000000'), DateTime::fromString('2020-01-02 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-05 00:00:00.000000'), DateTime::fromString('2020-01-07 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-03 00:00:00.000000'), DateTime::fromString('2020-01-06 00:00:00.000000')), ); $this->assertEquals( TimePeriods::fromArray( new TimePeriod(DateTime::fromString('2020-01-01 00:00:00.000000'), DateTime::fromString('2020-01-02 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-03 00:00:00.000000'), DateTime::fromString('2020-01-06 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-05 00:00:00.000000'), DateTime::fromString('2020-01-07 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-10 00:00:00.000000'), DateTime::fromString('2020-01-08 00:00:00.000000')), ), $timePeriods->sort() ); } public function test_sort_by_start_date_desc() : void { $timePeriods = TimePeriods::fromArray( new TimePeriod(DateTime::fromString('2020-01-10 00:00:00.000000'), DateTime::fromString('2020-01-08 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-01 00:00:00.000000'), DateTime::fromString('2020-01-02 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-05 00:00:00.000000'), DateTime::fromString('2020-01-07 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-03 00:00:00.000000'), DateTime::fromString('2020-01-06 00:00:00.000000')), ); $this->assertEquals( TimePeriods::fromArray( new TimePeriod(DateTime::fromString('2020-01-10 00:00:00.000000'), DateTime::fromString('2020-01-08 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-05 00:00:00.000000'), DateTime::fromString('2020-01-07 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-03 00:00:00.000000'), DateTime::fromString('2020-01-06 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-01 00:00:00.000000'), DateTime::fromString('2020-01-02 00:00:00.000000')), ), $timePeriods->sortBy(TimePeriodsSort::desc()) ); } public function test_sort_by_end_date_asc() : void { $timePeriods = TimePeriods::fromArray( new TimePeriod(DateTime::fromString('2020-01-10 00:00:00.000000'), DateTime::fromString('2020-01-08 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-01 00:00:00.000000'), DateTime::fromString('2020-01-02 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-05 00:00:00.000000'), DateTime::fromString('2020-01-07 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-03 00:00:00.000000'), DateTime::fromString('2020-01-06 00:00:00.000000')), ); $this->assertEquals( TimePeriods::fromArray( new TimePeriod(DateTime::fromString('2020-01-01 00:00:00.000000'), DateTime::fromString('2020-01-02 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-03 00:00:00.000000'), DateTime::fromString('2020-01-06 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-05 00:00:00.000000'), DateTime::fromString('2020-01-07 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-10 00:00:00.000000'), DateTime::fromString('2020-01-08 00:00:00.000000')), ), $timePeriods->sortBy(TimePeriodsSort::endDate()) ); } public function test_sort_by_end_date_desc() : void { $timePeriods = TimePeriods::fromArray( new TimePeriod(DateTime::fromString('2020-01-10 00:00:00.000000'), DateTime::fromString('2020-01-08 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-01 00:00:00.000000'), DateTime::fromString('2020-01-02 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-05 00:00:00.000000'), DateTime::fromString('2020-01-07 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-03 00:00:00.000000'), DateTime::fromString('2020-01-06 00:00:00.000000')), ); $this->assertEquals( TimePeriods::fromArray( new TimePeriod(DateTime::fromString('2020-01-10 00:00:00.000000'), DateTime::fromString('2020-01-08 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-05 00:00:00.000000'), DateTime::fromString('2020-01-07 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-03 00:00:00.000000'), DateTime::fromString('2020-01-06 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-01 00:00:00.000000'), DateTime::fromString('2020-01-02 00:00:00.000000')), ), $timePeriods->sortBy(TimePeriodsSort::endDate(false)) ); } public function test_first() : void { $timePeriods = TimePeriods::fromArray( new TimePeriod(DateTime::fromString('2020-01-10 00:00:00.000000'), DateTime::fromString('2020-01-08 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-01 00:00:00.000000'), DateTime::fromString('2020-01-02 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-05 00:00:00.000000'), DateTime::fromString('2020-01-07 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-03 00:00:00.000000'), DateTime::fromString('2020-01-06 00:00:00.000000')), ); $this->assertEquals( $timePeriods->first(), new TimePeriod(DateTime::fromString('2020-01-10 00:00:00.000000'), DateTime::fromString('2020-01-08 00:00:00.000000')), ); } public function test_first_empty() : void { $this->assertNull( (TimePeriods::fromArray())->first(), ); } public function test_last() : void { $timePeriods = TimePeriods::fromArray( new TimePeriod(DateTime::fromString('2020-01-10 00:00:00.000000'), DateTime::fromString('2020-01-08 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-01 00:00:00.000000'), DateTime::fromString('2020-01-02 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-05 00:00:00.000000'), DateTime::fromString('2020-01-07 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-03 00:00:00.000000'), DateTime::fromString('2020-01-06 00:00:00.000000')), ); $this->assertEquals( $timePeriods->last(), new TimePeriod(DateTime::fromString('2020-01-03 00:00:00.000000'), DateTime::fromString('2020-01-06 00:00:00.000000')), ); } public function test_last_empty() : void { $this->assertNull( (TimePeriods::fromArray())->first(), ); } public function test_add() : void { $timePeriods = TimePeriods::fromArray( new TimePeriod(DateTime::fromString('2020-01-10 00:00:00.000000'), DateTime::fromString('2020-01-08 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-01 00:00:00.000000'), DateTime::fromString('2020-01-02 00:00:00.000000')), ); $timePeriods = $timePeriods->add( new TimePeriod(DateTime::fromString('2020-01-05 00:00:00.000000'), DateTime::fromString('2020-01-07 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-03 00:00:00.000000'), DateTime::fromString('2020-01-06 00:00:00.000000')), ); $this->assertEquals( $timePeriods, TimePeriods::fromArray( new TimePeriod(DateTime::fromString('2020-01-10 00:00:00.000000'), DateTime::fromString('2020-01-08 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-01 00:00:00.000000'), DateTime::fromString('2020-01-02 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-05 00:00:00.000000'), DateTime::fromString('2020-01-07 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-03 00:00:00.000000'), DateTime::fromString('2020-01-06 00:00:00.000000')), ) ); } public function test_merge() : void { $timePeriods = TimePeriods::fromArray( new TimePeriod(DateTime::fromString('2020-01-10 00:00:00.000000'), DateTime::fromString('2020-01-08 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-01 00:00:00.000000'), DateTime::fromString('2020-01-02 00:00:00.000000')), ); $timePeriods = $timePeriods->merge( TimePeriods::fromArray( new TimePeriod(DateTime::fromString('2020-01-05 00:00:00.000000'), DateTime::fromString('2020-01-07 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-03 00:00:00.000000'), DateTime::fromString('2020-01-06 00:00:00.000000')), ) ); $this->assertEquals( $timePeriods, TimePeriods::fromArray( new TimePeriod(DateTime::fromString('2020-01-10 00:00:00.000000'), DateTime::fromString('2020-01-08 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-01 00:00:00.000000'), DateTime::fromString('2020-01-02 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-05 00:00:00.000000'), DateTime::fromString('2020-01-07 00:00:00.000000')), new TimePeriod(DateTime::fromString('2020-01-03 00:00:00.000000'), DateTime::fromString('2020-01-06 00:00:00.000000')), ) ); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/Gregorian/TimeTest.php ================================================ */ public static function invalid_string_day_format() : \Generator { yield ['2020-32']; } /** * @return \Generator */ public static function valid_string_day_format() : \Generator { yield ['01:00:01.5', new Time(01, 00, 01, 500000)]; yield ['01:00:01.005', new Time(01, 00, 01, 5000)]; yield ['01:00:01.00001', new Time(01, 00, 01, 10)]; yield ['01:12', new Time(01, 12, 00)]; yield ['01:12 +1 minute + 10 seconds', new Time(01, 13, 10)]; } /** * @return \Generator */ public static function creating_time_data_provider_from_string() : \Generator { yield ['noW', 'H:i:s']; yield ['now ', 'H:i:s']; yield ['today', 'H:i:s']; yield [' tOday', 'H:i:s']; yield ['noon', 'H:i:s']; yield ['noon ', 'H:i:s']; yield ['midnight ', 'H:i:s']; yield ['noon +1 minute', 'H:i:s']; yield ['back of 7pm', 'H:i:s']; yield ['last hour', 'H:i:s']; } /** * @return \Generator */ public static function compare_to_provider() : \Generator { yield [Time::fromString('11:53:12'), Time::fromString('11:53:12'), 0]; yield [Time::fromString('11:53'), Time::fromString('11:53'), 0]; yield [Time::fromString('11:53:00'), Time::fromString('11:53:12'), -1]; yield [Time::fromString('11:00:12'), Time::fromString('11:53:12'), -1]; yield [Time::fromString('11:53:12'), Time::fromString('00:53:12'), 1]; yield [Time::fromString('11:53:12'), Time::fromString('11:00:12'), 1]; } public function test_debug_info() : void { $this->assertSame( [ 'hour' => 0, 'minute' => 0, 'second' => 0, 'microsecond' => 0, ], (new Time(0, 0, 0, 0))->__debugInfo() ); } #[DataProvider('invalid_string_day_format')] public function test_from_invalid_string(string $invalidValue) : void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("Value \"{$invalidValue}\" is not valid time format."); Time::fromString($invalidValue); } public function test_each_part_of_time() : void { $time = Time::fromString('00:00'); $this->assertSame(0, $time->hour()); $this->assertSame(0, $time->minute()); $this->assertSame(0, $time->second()); $this->assertSame(0, $time->microsecond()); } #[DataProvider('valid_string_day_format')] public function test_from_string(string $invalidValue, Time $time) : void { $this->assertObjectEquals($time, Time::fromString($invalidValue), 'isEqual'); } #[DataProvider('creating_time_data_provider_from_string')] public function test_creating_time_from_string(string $dateTime, string $format) : void { try { $this->assertEqualsWithDelta( (new \DateTime($dateTime))->getTimestamp(), (new \DateTime(Time::fromString($dateTime)->format($format)))->getTimestamp(), 20, 'Expected ' . (new \DateTime($dateTime))->format('c') . ' got ' . (new \DateTime(Time::fromString($dateTime)->format($format)))->format('c') ); } catch (InvalidArgumentException $exception) { $this->fail($exception->getMessage()); } } public function test_time_millisecond() : void { $this->assertSame(101, (new Time(0, 0, 0, 101999))->millisecond()); $this->assertSame(0, (new Time(0, 0, 0, 0))->microsecond()); } public function test_to_string() : void { $this->assertSame('23:59:59.599999', (new Time(23, 59, 59, 599999))->toString()); $this->assertSame('00:00:00.000000', (new Time(0, 0, 0, 0))->toString()); } public function test_format() : void { $this->assertSame('23 59 59', (new Time(23, 59, 59, 599999))->format('H i s')); } public function test_is_am() : void { $this->assertTrue((new Time(0, 0, 0, 0))->isAM()); $this->assertfalse((new Time(0, 0, 0, 0))->isPM()); } public function test_is_pm() : void { $this->assertFalse((Time::fromString('13:00:00'))->isAM()); $this->assertTrue((Time::fromDateTime(new \DateTimeImmutable('13:00:00')))->isPM()); } public function test_equal() : void { $this->assertTrue((new Time(10, 0, 0, 0))->isEqualTo(new Time(10, 0, 0, 0))); $this->assertFalse((new Time(10, 0, 0, 0))->isEqualTo(new Time(0, 0, 0, 0))); $this->assertFalse((new Time(10, 0, 0, 0))->isEqualTo(new Time(15, 0, 0, 0))); } public function test_greater() : void { $this->assertFalse((new Time(10, 0, 0, 0))->isAfter(new Time(10, 0, 0, 0))); $this->assertTrue((new Time(10, 0, 0, 0))->isAfter(new Time(0, 0, 0, 0))); $this->assertFalse((new Time(10, 0, 0, 0))->isAfter(new Time(15, 0, 0, 0))); } public function test_greater_or_equal() : void { $this->assertTrue((new Time(10, 0, 0, 0))->isAfterOrEqualTo(new Time(10, 0, 0, 0))); $this->assertTrue((new Time(10, 0, 0, 0))->isAfterOrEqualTo(new Time(0, 0, 0, 0))); $this->assertFalse((new Time(10, 0, 0, 0))->isAfterOrEqualTo(new Time(15, 0, 0, 0))); } public function test_less() : void { $this->assertFalse((new Time(10, 0, 0, 0))->isBefore(new Time(10, 0, 0, 0))); $this->assertFalse((new Time(10, 0, 0, 0))->isBefore(new Time(0, 0, 0, 0))); $this->assertTrue((new Time(10, 0, 0, 0))->isBefore(new Time(15, 0, 0, 0))); } public function test_less_or_equal() : void { $this->assertTrue((new Time(10, 0, 0, 0))->isBeforeOrEqualTo(new Time(10, 0, 0, 0))); $this->assertFalse((new Time(10, 0, 0, 0))->isBeforeOrEqualTo(new Time(0, 0, 0, 0))); $this->assertTrue((new Time(10, 0, 0, 0))->isBeforeOrEqualTo(new Time(15, 0, 0, 0))); } public function test_to_time_unit() : void { $this->assertSame('36000.000000', ((new Time(10, 0, 0, 0))->toTimeUnit()->inSecondsPrecise())); } public function test_creating_using_invalid_hour() : void { $this->expectException(InvalidArgumentException::class); new Time(24, 0, 0, 0); } public function test_creating_using_invalid_minute() : void { $this->expectException(InvalidArgumentException::class); new Time(0, 60, 0, 0); } public function test_creating_using_invalid_second() : void { $this->expectException(InvalidArgumentException::class); new Time(0, 0, 60, 0); } public function test_creating_using_invalid_microsecond() : void { $this->expectException(InvalidArgumentException::class); new Time(0, 0, 0, 1_000_000); } public function test_add() : void { $this->assertSame('01:00:00.000000', Time::fromString('00:00')->add(TimeUnit::hour())->toString()); $this->assertSame('00:00:00.000000', Time::fromString('00:00')->add(TimeUnit::days(2))->toString()); $this->assertSame('03:00:00.000000', Time::fromString('00:00')->add(TimeUnit::hours(27))->toString()); } public function test_sub() : void { $this->assertSame('04:00:00.000000', Time::fromString('05:00')->sub(TimeUnit::hour())->toString()); $this->assertSame('00:00:00.000000', Time::fromString('00:00')->sub(TimeUnit::days(2))->toString()); $this->assertSame('21:00:00.000000', Time::fromString('00:00')->sub(TimeUnit::hours(27))->toString()); } public function test_serialization() : void { $time = new Time(10, 00, 00, 00); $this->assertSame( [ 'hour' => 10, 'minute' => 00, 'second' => 00, 'microsecond' => 00, ], $serializedTime = $time->__serialize() ); $this->assertSame( 'O:28:"' . Time::class . '":4:{s:4:"hour";i:10;s:6:"minute";i:0;s:6:"second";i:0;s:11:"microsecond";i:0;}', $serializedTimeString = \serialize($time) ); $this->assertEquals( \unserialize($serializedTimeString), $time ); } public function test_is_midnight() : void { $this->assertTrue(Time::fromString('00:00')->isMidnight()); $this->assertTrue(Time::fromString('00:00:00')->isMidnight()); $this->assertFalse(Time::fromString('15:00:00')->isMidnight()); $this->assertFalse(Time::fromString('15:27:00')->isMidnight()); $this->assertFalse(Time::fromString('15:27:28')->isMidnight()); $this->assertFalse(Time::fromString('15:27:28.000001')->isMidnight()); } public function test_is_not_midnight() : void { $this->assertTrue(Time::fromString('15:00:00')->isNotMidnight()); $this->assertTrue(Time::fromString('15:27:00')->isNotMidnight()); $this->assertTrue(Time::fromString('15:27:28')->isNotMidnight()); $this->assertTrue(Time::fromString('15:27:28.000001')->isNotMidnight()); $this->assertFalse(Time::fromString('00:00')->isNotMidnight()); $this->assertFalse(Time::fromString('00:00:00')->isNotMidnight()); } #[DataProvider('compare_to_provider')] public function test_compare_to(Time $time, Time $comparable, int $compareResult) : void { $this->assertSame($compareResult, $time->compareTo($comparable)); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/Gregorian/TimeZone/TimeOffsetTest.php ================================================ */ public static function valid_time_offset_data_provider() : \Generator { yield ['00:00']; yield ['+00:00']; yield ['+0000']; yield ['-00:00']; yield ['-0000']; yield ['-10:00']; yield ['-1000']; yield ['-10:30']; yield ['-1030']; yield ['-10:15']; yield ['10:30']; yield ['10:15']; yield ['1015']; yield ['+14:00']; yield ['+1400']; } /** * @return \Generator */ public static function invalid_time_offset_data_provider() : \Generator { yield ['abcd']; yield ['9999']; yield ['45:45']; } public function test_create_from_invalid_string() : void { $this->expectException(InvalidArgumentException::class); TimeOffset::fromString('invalid_string'); } public function test_create_from_time_unit_zero() : void { $this->assertSame( '+00:00', TimeOffset::fromTimeUnit(TimeUnit::seconds(0))->toString() ); } public function test_create_UTC() : void { $this->assertSame( '+00:00', TimeOffset::UTC()->toString() ); $this->assertTrue(TimeOffset::UTC()->isUTC()); } public function test_create_from_time_unit_one_and_half_hours__positive() : void { $this->assertSame( '+01:30', TimeOffset::fromTimeUnit(TimeUnit::minutes(90))->toString() ); } public function test_create_from_time_unit_one_and_half_hours_negative() : void { $this->assertSame( '-01:30', TimeOffset::fromTimeUnit(TimeUnit::minutes(90)->toNegative())->toString() ); } public function test_is_equal() : void { $this->assertTrue(TimeOffset::fromString('+00:00')->isEqualTo(TimeOffset::fromString('+00:00'))); $this->assertFalse(TimeOffset::fromString('+00:00')->isEqualTo(TimeOffset::fromString('+01:00'))); } public function test_create_from_time_unit_zero_negative() : void { $this->assertSame( '-01:30', TimeOffset::fromTimeUnit(TimeUnit::minutes(90)->invert())->toString() ); } #[DataProvider('valid_time_offset_data_provider')] public function test_valid_time_offset(string $offset) : void { $this->assertTrue(TimeOffset::isValid($offset)); } #[DataProvider('invalid_time_offset_data_provider')] public function test_invalid_time_offset(string $offset) : void { $this->assertFalse(TimeOffset::isValid($offset)); } public function test_to_date_time_zone() : void { $this->assertInstanceOf(\DateTimeZone::class, TimeOffset::UTC()->toDateTimeZone()); } public function test_serialization() : void { $timeOffset = TimeOffset::fromString('+01:00'); $this->assertSame( [ 'hours' => 1, 'minutes' => 0, 'negative' => false, ], $serializedTimeZone = $timeOffset->__serialize() ); $this->assertSame( 'O:43:"' . TimeOffset::class . '":3:{s:5:"hours";i:1;s:7:"minutes";i:0;s:8:"negative";b:0;}', $serializedTimeOffsetString = \serialize($timeOffset) ); $this->assertEquals( \unserialize($serializedTimeOffsetString), $timeOffset ); } public function test_creat_timezone_abbreviation() : void { $this->assertTrue(TimeZone::PDDT()->isAbbreviation()); } public function test_creat_timezone_id() : void { $this->assertTrue(TimeZone::americaLosAngeles()->isIdentifier()); } public function test_creat_timezone_offset() : void { $this->assertTrue(TimeZone::offset('01:00')->isOffset()); } public function test_creat_timezone_offset_from_date_time_zone() : void { $this->assertTrue(TimeZone::fromDateTimeZone(new \DateTimeZone('+01:00'))->isOffset()); } public function test_creat_timezone_id_from_date_time_zone() : void { $this->assertTrue(TimeZone::fromDateTimeZone(new \DateTimeZone('America/Los_Angeles'))->isIdentifier()); } public function test_creat_timezone_abbreviation_from_date_Time_zone() : void { $this->assertTrue(TimeZone::fromDateTimeZone(new \DateTimeZone('PDDT'))->isAbbreviation()); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/Gregorian/TimeZoneTest.php ================================================ expectExceptionMessage('"not_a_timezone" is not a valid time zone.'); TimeZone::fromString('not_a_timezone'); } public function test_creating_from_invalid_abbreviation() : void { $this->expectExceptionMessage('"not_a_timezone" is not a valid timezone abbreviation.'); TimeZone::abbreviation('not_a_timezone'); } public function test_creating_from_invalid_identifier() : void { $this->expectExceptionMessage('"not_a_timezone" is not a valid timezone identifier.'); TimeZone::id('not_a_timezone'); } public function test_creating_UTC_as_identifier() : void { $this->expectExceptionMessage('"UTC" is timezone abbreviation, not identifier.'); TimeZone::id('UTC'); } public function test_creating_from_invalid_offset() : void { $this->expectExceptionMessage('"not_a_timezone" is not a valid time offset.'); TimeZone::offset('not_a_timezone'); } public function test_creating_from_invalid_static_name() : void { $this->expectExceptionMessage('"blabla" is not a valid time zone identifier or abbreviation.'); $tz = TimeZone::blabla(); } public function test_creating_from_offset() : void { $tz = TimeZone::fromString('+0100'); $this->assertSame('+01:00', $tz->name()); $this->assertTrue($tz->isOffset()); $this->assertFalse($tz->isAbbreviation()); $this->assertFalse($tz->isIdentifier()); } public function test_creating_from_abbreviation() : void { $tz = TimeZone::fromString('gmt'); $this->assertSame('GMT', $tz->name()); $this->assertTrue($tz->isAbbreviation()); $this->assertFalse($tz->isOffset()); $this->assertFalse($tz->isIdentifier()); } public function test_creating_from_id() : void { $tz = TimeZone::fromString('Europe/Warsaw'); $this->assertSame('Europe/Warsaw', $tz->name()); $this->assertTrue($tz->isIdentifier()); $this->assertFalse($tz->isAbbreviation()); $this->assertFalse($tz->isOffset()); } public function test_creating_from_static_id() : void { $tz = TimeZone::europeWarsaw(); $this->assertSame('Europe/Warsaw', $tz->name()); } public function test_creating_from_static_abbreviation() : void { $tz = TimeZone::GMT(); $this->assertSame('GMT', $tz->name()); } public function test_calculating_offset_for_date() : void { $tz = TimeZone::europeWarsaw(); $this->assertSame('+01:00', $tz->timeOffset(DateTime::fromString('2020-01-01 12:00:00'))->toString()); $this->assertSame(3600, $tz->timeOffset(DateTime::fromString('2020-01-01 12:00:00'))->toTimeUnit()->inSeconds()); $this->assertSame('+02:00', $tz->timeOffset(DateTime::fromString('2020-06-01 12:00:00'))->toString()); $this->assertSame(7200, $tz->timeOffset(DateTime::fromString('2020-06-01 12:00:00'))->toTimeUnit()->inSeconds()); } public function test_is_valid() : void { $this->assertFalse(TimeZone::isValid('invalid_time_zone')); $this->assertTrue(TimeZone::isValid('Europe/Warsaw')); } public function test_all_timezone_identifiers() : void { // \DateTimeZone returns UTC as timezone ID but it's an timezone abbreviation $this->assertCount(\count(\DateTimeZone::listIdentifiers()) - 1, TimeZone::allIdentifiers()); $this->assertContainsOnlyInstancesOf(TimeZone::class, TimeZone::allIdentifiers()); } public function test_all_timezones() : void { // \DateTimeZone::listAbbreviations() additionally returns all 25 alphabet letters as list abbreviations keys. $this->assertCount(\count(\array_keys(\DateTimeZone::listAbbreviations())) - 25, TimeZone::allAbbreviations()); $this->assertContainsOnlyInstancesOf(TimeZone::class, TimeZone::allAbbreviations()); } public function test_serialization() : void { $timeZone = TimeZone::fromString('America/Los_Angeles'); $this->assertSame( [ 'name' => 'America/Los_Angeles', 'type' => 3, ], $serializedTimeZone = $timeZone->__serialize() ); $this->assertSame( 'O:32:"' . TimeZone::class . '":2:{s:4:"name";s:19:"America/Los_Angeles";s:4:"type";i:3;}', $serializedTimeZoneString = \serialize($timeZone) ); $this->assertEquals( \unserialize($serializedTimeZoneString), $timeZone ); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/Gregorian/YearMonthsTest.php ================================================ assertSame(12, (new Year(2020))->months()->count()); $this->assertCount(12, (new Year(2020))->months()->all()); } public function test_map_months() : void { $this->assertSame( \range(1, 12), (new Year(2020))->months()->map(fn (Month $month) => $month->number()) ); } public function test_filter_months() : void { $this->assertSame( [2, 4, 6, 8, 10, 12], \array_map( fn (Month $month) : int => $month->number(), \array_values((new Year(2020))->months()->filter(fn (Month $month) => $month->number() % 2 === 0)) ) ); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/Gregorian/YearTest.php ================================================ */ public static function month_number_of_days_data_provider() : \Generator { yield [2020, 1, 31]; yield [2020, 2, 29]; yield [2020, 3, 31]; yield [2020, 4, 30]; yield [2020, 5, 31]; yield [2020, 6, 30]; yield [2020, 7, 31]; yield [2020, 8, 31]; yield [2020, 9, 30]; yield [2020, 10, 31]; yield [2020, 11, 30]; yield [2020, 12, 31]; yield [2021, 1, 31]; yield [2021, 2, 28]; } /** * @return \Generator */ public static function leap_years() : \Generator { yield [2000, true]; yield [2100, false]; yield [2400, true]; yield [2404, true]; yield [2403, false]; } /** * @return \Generator */ public static function compare_to_provider() : \Generator { yield [Year::fromString('2022'), Year::fromString('2022'), 0]; yield [Year::fromString('2021'), Year::fromString('2022'), -1]; yield [Year::fromString('2022'), Year::fromString('2021'), 1]; } public function test_months() : void { $this->assertSame(1, Year::fromString('2020-01-01')->january()->number()); $this->assertSame(2, Year::fromString('2020-01-01')->february()->number()); $this->assertSame(3, Year::fromString('2020-01-01')->march()->number()); $this->assertSame(4, Year::fromString('2020-01-01')->april()->number()); $this->assertSame(5, Year::fromString('2020-01-01')->may()->number()); $this->assertSame(6, Year::fromString('2020-01-01')->june()->number()); $this->assertSame(7, Year::fromString('2020-01-01')->july()->number()); $this->assertSame(8, Year::fromString('2020-01-01')->august()->number()); $this->assertSame(9, Year::fromString('2020-01-01')->september()->number()); $this->assertSame(10, Year::fromString('2020-01-01')->october()->number()); $this->assertSame(11, Year::fromString('2020-01-01')->november()->number()); $this->assertSame(12, Year::fromString('2020-01-01')->december()->number()); } public function test_from_string() : void { $this->assertSame(2018, Year::fromString('2018')->number()); $this->assertSame(2020, Year::fromString('2020')->number()); $this->assertSame((int) (new \DateTimeImmutable('now'))->format('Y'), Year::fromString('now')->number()); $this->assertSame((int) (new \DateTimeImmutable('midnight'))->format('Y'), Year::fromString('midnight')->number()); $this->assertSame((int) (new \DateTimeImmutable('yesterday'))->format('Y'), Year::fromString('yesterday')->number()); $this->assertSame((int) (new \DateTimeImmutable('yesterday'))->format('Y'), Year::fromString('yesterday ')->number()); $this->assertSame((int) (new \DateTimeImmutable('noon'))->format('Y'), Year::fromString('NooN ')->number()); $this->assertSame((int) (new \DateTimeImmutable('noon'))->format('Y'), Year::fromString('noon')->number()); $this->assertSame((int) (new \DateTimeImmutable('tomorrow'))->format('Y'), Year::fromString('tomorrow')->number()); } public function test_from_string_that_is_not_valid_number() : void { $this->expectException(InvalidArgumentException::class); Year::fromString('not a number'); } public function test_from_date_time_immutable() : void { $this->assertSame(2020, Year::fromDateTime(new \DateTimeImmutable('2020-05-01'))->number()); } #[DataProvider('month_number_of_days_data_provider')] public function test_month_number_of_days(int $year, int $month, int $numberOfDays) : void { $this->assertSame($numberOfDays, (new Year($year))->months()->byNumber($month)->numberOfDays()); } public function test_debug_info() : void { $this->assertSame( [ 'year' => 2020, ], Year::fromString('2020-01-01')->__debugInfo() ); } public function test_to_string() : void { $this->assertSame( '2020', Year::fromString('2020-01-01')->toString() ); } public function test_map_days() : void { $days = (new Year(2020))->mapDays(fn (Day $day) : int => $day->number()); $this->assertCount(366, $days); $this->assertSame($days[0], 1); $this->assertSame($days[365], 31); } public function test_filter_days() : void { $this->assertSame(52, \count((new Year(2020))->filterDays(fn (Day $day) : bool => $day->isWeekend())) / 2); } public function test_next_year() : void { $this->assertSame(2021, (new Year(2020))->next()->number()); } public function test_previous_year() : void { $this->assertSame(2019, (new Year(2020))->previous()->number()); } public function test_leap_years() : void { $year = new Year(0); while ($year->number() < 9999) { if ($year->isLeap()) { $this->assertTrue( $year->number() % 4 === 0 && ($year->number() % 100 !== 0 || $year->number() % 400 === 0) ); $this->assertSame(366, $year->numberOfDays()); } else { $this->assertSame(365, $year->numberOfDays()); } $year = $year->next(); } } public function test_reset_time_in_to_datetime_immutable() : void { $year = new Year(2020); $dateTimeImmutable1 = $year->toDateTimeImmutable(); \sleep(1); $dateTimeImmutable2 = $year->toDateTimeImmutable(); $this->assertTrue($dateTimeImmutable1 == $dateTimeImmutable2); } public function test_is_equal() : void { $this->assertTrue(Year::fromString('2020-01-01')->isEqualTo(Year::fromString('2020-01-01'))); $this->assertFalse(Year::fromString('2021-01-02')->isEqualTo(Year::fromString('2020-01-01'))); } public function test_is_before() : void { $this->assertTrue(Year::fromString('2019-01-01')->isBefore(Year::fromString('2020-01-01'))); $this->assertTrue(Year::fromString('2020-01-01')->isBeforeOrEqualTo(Year::fromString('2020-01-01'))); $this->assertFalse(Year::fromString('2021-01-01')->isBefore(Year::fromString('2020-01-01'))); $this->assertFalse(Year::fromString('2021-01-01')->isBeforeOrEqualTo(Year::fromString('2020-01-01'))); } public function test_is_after() : void { $this->assertTrue(Year::fromString('2022-01-01')->isAfter(Year::fromString('2020-02-01'))); $this->assertTrue(Year::fromString('2020-01-01')->isAfterOrEqualTo(Year::fromString('2020-01-01'))); $this->assertFalse(Year::fromString('2019-01-01')->isAfter(Year::fromString('2020-02-01'))); $this->assertFalse(Year::fromString('2019-01-01')->isAfterOrEqualTo(Year::fromString('2020-02-01'))); } public function test_until_with_wrong_destination_month() : void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('2020 is after 2019'); Year::fromString('2020-01-01')->until(Year::fromString('2019-01-01'), Interval::rightOpen()); } public function test_since_with_wrong_destination_month() : void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('2019 is before 2020'); Year::fromString('2019-01-01')->since(Year::fromString('2020-01-01'), Interval::rightOpen()); } public function test_modify_months() : void { $this->assertSame('2015-01-01', Year::fromString('2020-01-01')->sub(5)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2019-01-01', Year::fromString('2020-01-01')->sub(1)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2026-01-01', Year::fromString('2020-01-01')->add(6)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2021-01-01', Year::fromString('2020-01-01')->add(1)->toDateTimeImmutable()->format('Y-m-d')); $this->assertSame('2019-01-01', Year::fromString('2020-01-01')->add(-1)->toDateTimeImmutable()->format('Y-m-d')); } public function test_until() : void { $this->assertCount(5, $years = Year::fromString('2020-01-01')->until(Year::fromString('2025-01-01'), Interval::rightOpen())); $this->assertInstanceOf(Year::class, $years[0]); $this->assertInstanceOf(Year::class, $years[4]); $this->assertSame(2020, $years[0]->number()); $this->assertSame(2024, $years[4]->number()); } public function test_since() : void { $this->assertCount(5, $years = Year::fromString('2025-01-01')->since(Year::fromString('2020-01-01'), Interval::leftOpen())); $this->assertInstanceOf(Year::class, $years[0]); $this->assertInstanceOf(Year::class, $years[4]); $this->assertSame(2021, $years[0]->number()); $this->assertSame(2025, $years[4]->number()); } public function test_iterate_until() : void { $this->assertCount(5, $years = Year::fromString('2020-01-01')->iterate(Year::fromString('2025-01-01'), Interval::rightOpen())); $this->assertInstanceOf(Year::class, $years[0]); $this->assertInstanceOf(Year::class, $years[4]); $this->assertSame(2020, $years[0]->number()); $this->assertSame(2024, $years[4]->number()); } public function test_iterate_since() : void { $this->assertCount(5, $years = Year::fromString('2025-01-01')->iterate(Year::fromString('2020-01-01'), Interval::leftOpen())); $this->assertInstanceOf(Year::class, $years[0]); $this->assertInstanceOf(Year::class, $years[4]); $this->assertSame(2021, $years[0]->number()); $this->assertSame(2025, $years[4]->number()); } public function test_quarter_below_limit() : void { $this->expectException(InvalidArgumentException::class); (new Year(2020))->quarter(0); } public function test_quarter_above_limit() : void { $this->expectException(InvalidArgumentException::class); (new Year(2020))->quarter(5); } public function test_distance_to() : void { $this->assertSame(366, (new Year(2020))->distance(new Year(2021))->inDays()); } public function test_quarters() : void { $this->assertSame(1, (new Year(2020))->quarter(1)->number()); $this->assertCount(3, (new Year(2020))->quarter(1)->months()); $this->assertSame(1, (new Year(2020))->quarter(1)->months()->all()[0]->number()); $this->assertSame(2, (new Year(2020))->quarter(1)->months()->all()[1]->number()); $this->assertSame(3, (new Year(2020))->quarter(1)->months()->all()[2]->number()); $this->assertCount(3, (new Year(2020))->quarter(2)->months()); $this->assertSame(4, (new Year(2020))->quarter(2)->months()->all()[0]->number()); $this->assertSame(5, (new Year(2020))->quarter(2)->months()->all()[1]->number()); $this->assertSame(6, (new Year(2020))->quarter(2)->months()->all()[2]->number()); $this->assertCount(3, (new Year(2020))->quarter(3)->months()); $this->assertSame(7, (new Year(2020))->quarter(3)->months()->all()[0]->number()); $this->assertSame(8, (new Year(2020))->quarter(3)->months()->all()[1]->number()); $this->assertSame(9, (new Year(2020))->quarter(3)->months()->all()[2]->number()); $this->assertCount(3, (new Year(2020))->quarter(4)->months()); $this->assertSame(10, (new Year(2020))->quarter(4)->months()->all()[0]->number()); $this->assertSame(11, (new Year(2020))->quarter(4)->months()->all()[1]->number()); $this->assertSame(12, (new Year(2020))->quarter(4)->months()->all()[2]->number()); } public function test_serialization() : void { $year = new Year(2020); $this->assertSame( [ 'year' => 2020, ], $serializedYear = $year->__serialize() ); $this->assertSame( 'O:28:"' . Year::class . '":1:{s:4:"year";i:2020;}', $serializedYearString = \serialize($year) ); $this->assertEquals( \unserialize($serializedYearString), $year ); } #[DataProvider('leap_years')] public function test_leap_year(int $year, bool $isLeap) : void { $this->assertSame($isLeap, (new Year($year))->isLeap()); } #[DataProvider('compare_to_provider')] public function test_compare_to(Year $time, Year $comparable, int $compareResult) : void { $this->assertSame($compareResult, $time->compareTo($comparable)); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/Gregorian/YearsTest.php ================================================ assertTrue(isset($years[0])); $this->assertInstanceOf(Year::class, $years[0]); $this->assertSame(3, \iterator_count($years->getIterator())); $this->assertCount(3, $years->all()); } public function test_map() : void { $yeras = new Years( Year::fromString('2000-01-01'), Year::fromString('2001-02-02'), Year::fromString('2002-03-03') ); $this->assertSame( [2000, 2001, 2002], $yeras->map(function (Year $day) { return $day->number(); }) ); } public function test_filter() : void { $years = new Years( Year::fromString('2000-01-01'), Year::fromString('2001-02-02'), Year::fromString('2002-03-03') ); $this->assertEquals( new Years(Year::fromString('2000-01-01')), $years->filter(function (Year $day) { return $day->number() === 2000; }) ); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/IntervalTest.php ================================================ assertTrue($interval->isLeftOpen()); $this->assertTrue($interval->isRightOpen()); $this->assertTrue($interval->isOpen()); $this->assertFalse($interval->isClosed()); } public function test_closed() : void { $interval = Interval::closed(); $this->assertFalse($interval->isLeftOpen()); $this->assertFalse($interval->isRightOpen()); $this->assertFalse($interval->isOpen()); $this->assertTrue($interval->isClosed()); } public function test_left_open() : void { $interval = Interval::leftOpen(); $this->assertTrue($interval->isLeftOpen()); $this->assertFalse($interval->isRightOpen()); $this->assertFalse($interval->isOpen()); $this->assertFalse($interval->isClosed()); } public function test_right_open() : void { $interval = Interval::rightOpen(); $this->assertFalse($interval->isLeftOpen()); $this->assertTrue($interval->isRightOpen()); $this->assertFalse($interval->isOpen()); $this->assertFalse($interval->isClosed()); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/RelativeTimeUnitTest.php ================================================ assertSame('02', (RelativeTimeUnit::months(2))->toDateInterval()->format('%M')); $this->assertSame('02', (RelativeTimeUnit::months(-2))->toDateInterval()->format('%M')); $this->assertSame(1, (RelativeTimeUnit::months(-2))->toDateInterval()->invert); $this->assertSame('01', (RelativeTimeUnit::month())->toDateInterval()->format('%M')); } public function test_invert() : void { $this->assertSame(2, (RelativeTimeUnit::months(-2))->invert()->inMonths()); $this->assertSame(-2, (RelativeTimeUnit::months(2))->invert()->inMonths()); $this->assertSame(-2, (RelativeTimeUnit::years(2))->invert()->inYears()); $this->assertSame(-24, (RelativeTimeUnit::years(2))->invert()->inMonths()); $this->assertSame(2, (RelativeTimeUnit::years(-2))->invert()->inYears()); $this->assertSame(24, (RelativeTimeUnit::years(-2))->invert()->inMonths()); } public function test_years() : void { $this->assertSame('02', (RelativeTimeUnit::years(2))->toDateInterval()->format('%Y')); $this->assertSame('02', (RelativeTimeUnit::years(-2))->toDateInterval()->format('%Y')); $this->assertSame('01', (RelativeTimeUnit::year())->toDateInterval()->format('%Y')); } public function test_in_years() : void { $this->assertSame(0, RelativeTimeUnit::months(11)->inYears()); $this->assertSame(1, RelativeTimeUnit::months(12)->inYears()); $this->assertSame(-1, RelativeTimeUnit::months(-12)->inYears()); $this->assertSame(0, RelativeTimeUnit::months(-1)->inYears()); $this->assertSame(2, RelativeTimeUnit::months(26)->inYears()); $this->assertSame(2, RelativeTimeUnit::years(2)->inYears()); } public function test_in_calendar_months() : void { $this->assertSame(4, RelativeTimeUnit::months(28)->inCalendarMonths()); $this->assertSame(4, RelativeTimeUnit::months(-28)->inCalendarMonths()); $this->assertSame(0, RelativeTimeUnit::years(2)->inCalendarMonths()); } public function test_in_months() : void { $this->assertSame(24, RelativeTimeUnit::years(2)->inMonths()); $this->assertSame(24, RelativeTimeUnit::months(24)->inMonths()); } public function test_to_date_interval() : void { $this->assertEquals(new \DateInterval('P0Y2M'), RelativeTimeUnit::months(2)->toDateInterval()); $this->assertEquals(new \DateInterval('P2Y0M'), RelativeTimeUnit::years(2)->toDateInterval()); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/StopwatchTest.php ================================================ expectException(Exception::class); $this->expectExceptionMessage('Stopwatch not started'); $stopwatch = new Stopwatch(); $stopwatch->stop(); } public function test_elapsed_time_on_not_stopped_stopwatch() : void { $this->expectException(Exception::class); $this->expectExceptionMessage('Stopwatch does not have any laps.'); $stopwatch = new Stopwatch(); $stopwatch->start(); $stopwatch->elapsedTime(0); } public function test_stopping_already_stopped_stopwatch() : void { $this->expectException(Exception::class); $this->expectExceptionMessage('Stopwatch already stopped'); $stopwatch = new Stopwatch(); $stopwatch->start(); $stopwatch->stop(); $stopwatch->stop(); } public function test_taking_lap_time_of_non_started_stopwatch() : void { $this->expectException(Exception::class); $this->expectExceptionMessage('Stopwatch not started'); $stopwatch = new Stopwatch(); $stopwatch->lap(); } public function test_getting_last_lap_elapsed_time_from_not_stopped_stopwatch() : void { $this->expectException(Exception::class); $this->expectExceptionMessage('Stopwatch does not have any laps'); $stopwatch = new Stopwatch(); $stopwatch->start(); $stopwatch->stop(); $stopwatch->lastLapElapsedTime(); } public function test_getting_last_lap_elapsed_time_from_not_started_stopwatch() : void { $this->expectException(Exception::class); $this->expectExceptionMessage('Stopwatch not started'); $stopwatch = new Stopwatch(); $stopwatch->lastLapElapsedTime(); } public function test_getting_first_lap_elapsed_time_from_not_stopped_stopwatch() : void { $this->expectException(Exception::class); $this->expectExceptionMessage('Stopwatch does not have any laps'); $stopwatch = new Stopwatch(); $stopwatch->start(); $stopwatch->firstLapElapsedTime(); } public function test_getting_first_lap_elapsed_time_from_not_started_stopwatch() : void { $this->expectException(Exception::class); $this->expectExceptionMessage('Stopwatch not started'); $stopwatch = new Stopwatch(); $stopwatch->firstLapElapsedTime(); } public function test_getting_total_elapsed_time_from_not_stopped_stopwatch() : void { $this->expectException(Exception::class); $this->expectExceptionMessage('Stopwatch not stopped'); $stopwatch = new Stopwatch(); $stopwatch->start(); $stopwatch->totalElapsedTime(); } public function test_getting_total_elapsed_time_from_not_started_stopwatch() : void { $this->expectException(Exception::class); $this->expectExceptionMessage('Stopwatch not started'); $stopwatch = new Stopwatch(); $stopwatch->totalElapsedTime(); } public function test_elapsed_time_from_not_stopped_stopwatch() : void { $this->expectException(Exception::class); $this->expectExceptionMessage('Stopwatch does not have any laps'); $stopwatch = new Stopwatch(); $stopwatch->start(); $stopwatch->elapsedTime(1); } public function test_elapsed_time_from_not_started_stopwatch() : void { $this->expectException(Exception::class); $this->expectExceptionMessage('Stopwatch not started'); $stopwatch = new Stopwatch(); $stopwatch->elapsedTime(1); } public function test_elapsed_time_from_not_existing_measure() : void { $this->expectException(Exception::class); $this->expectExceptionMessage('Lap 3 not exists'); $stopwatch = new Stopwatch(); $stopwatch->start(); $stopwatch->lap(); $stopwatch->stop(); $this->assertSame(2, $stopwatch->laps()); $stopwatch->elapsedTime(3); } public function test_laps_count_without_laps() : void { $stopwatch = new Stopwatch(); $stopwatch->start(); $stopwatch->stop(); $this->assertSame(0, $stopwatch->laps()); } public function test_elapsed_time_from_not_ended_measure() : void { $this->expectException(Exception::class); $this->expectExceptionMessage('Stopwatch not stopped'); $stopwatch = new Stopwatch(); $stopwatch->start(); $stopwatch->lap(); $stopwatch->lastLapElapsedTime(); } public function test_getting_elapsed_time_from_two_laps() : void { $stopwatch = new Stopwatch(); $stopwatch->start(); $stopwatch->lap(); // lap #1 $stopwatch->stop(); // lap #2 $this->assertSame(2, $stopwatch->laps()); $this->assertSame( $stopwatch->lastLapElapsedTime()->inSecondsPrecise(), $stopwatch->elapsedTime(2)->inSecondsPrecise() ); $this->assertSame( $stopwatch->firstLapElapsedTime()->inSecondsPrecise(), $stopwatch->elapsedTime(1)->inSecondsPrecise() ); } public function test_getting_total_elapsed_time_from_three_laps() : void { $stopwatch = new Stopwatch(); $stopwatch->start(); \usleep(TimeUnit::milliseconds(100)->microsecond()); // lap #1 aka first $stopwatch->lap(); \usleep(TimeUnit::milliseconds(100)->microsecond()); // lap #2 $stopwatch->lap(); \usleep(TimeUnit::milliseconds(100)->microsecond()); // lap #3 aka last $stopwatch->stop(); $this->assertSame( $stopwatch->totalElapsedTime()->inSecondsPrecise(), $stopwatch->firstLapElapsedTime() ->add($stopwatch->elapsedTime(2)) ->add($stopwatch->lastLapElapsedTime()) ->inSecondsPrecise() ); } public function test_getting_total_elapsed_time_from_one_lap_stop() : void { $stopwatch = new Stopwatch(); $stopwatch->start(); $stopwatch->lap(); \usleep(TimeUnit::milliseconds(100)->microsecond()); $stopwatch->stop(); $this->assertSame( $stopwatch->totalElapsedTime()->inSecondsPrecise(), $stopwatch->firstLapElapsedTime()->add($stopwatch->lastLapElapsedTime())->inSecondsPrecise() ); $this->assertSame(2, $stopwatch->laps()); $this->assertTrue($stopwatch->isStarted()); $this->assertTrue($stopwatch->isStopped()); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/TimeUnit/HRTimeTest.php ================================================ */ public static function converting_hr_time_to_timeunit_data_provider() : \Generator { yield ['0.000000', 0, 0]; yield ['0.100000', 0, 100_000_000]; yield ['0.100000', 0, 100_000_100]; yield ['0.100009', 0, 100_009_000]; yield ['0.100009', 0, 100_009_000_001]; yield ['0.000005', 0, 5_000]; } public function test_conversion_with_nanoseconds_smaller_than_0() : void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("Nanoseconds can't be less than 0, given -1"); HRTime::convert(0, -1); } #[DataProvider('converting_hr_time_to_timeunit_data_provider')] public function test_converting_hr_time_to_timeunit(string $expected, int $seconds, int $nanoseconds) : void { $this->assertSame($expected, HRTime::convert($seconds, $nanoseconds)->inSecondsPrecise()); } } ================================================ FILE: tests/Aeon/Calendar/Tests/Unit/TimeUnitTest.php ================================================ */ public static function greater_than_data_provider() : \Generator { yield [TimeUnit::seconds(1), TimeUnit::seconds(-5), true]; yield [TimeUnit::seconds(1), TimeUnit::seconds(5), false]; yield [TimeUnit::seconds(5), TimeUnit::seconds(5), false]; yield [TimeUnit::seconds(10), TimeUnit::seconds(5), true]; yield [TimeUnit::precise(0.000001), TimeUnit::precise(0.000000), true]; yield [TimeUnit::precise(0.000010), TimeUnit::precise(0.000000), true]; yield [TimeUnit::precise(0.000001), TimeUnit::precise(0.000010), false]; yield [TimeUnit::precise(0.000000), TimeUnit::precise(0.000000), false]; yield [TimeUnit::precise(0.000000), TimeUnit::precise(0.000001), false]; } /** * @return \Generator */ public static function greater_than_eq_data_provider() : \Generator { yield [TimeUnit::seconds(1), TimeUnit::seconds(-5), true]; yield [TimeUnit::seconds(1), TimeUnit::seconds(5), false]; yield [TimeUnit::seconds(5), TimeUnit::seconds(5), true]; yield [TimeUnit::seconds(10), TimeUnit::seconds(5), true]; yield [TimeUnit::precise(0.000001), TimeUnit::precise(0.000000), true]; yield [TimeUnit::precise(0.000000), TimeUnit::precise(0.000000), true]; yield [TimeUnit::precise(0.000000), TimeUnit::precise(0.000001), false]; } /** * @return \Generator */ public static function less_than_data_provider() : \Generator { yield [TimeUnit::seconds(1), TimeUnit::seconds(-5), false]; yield [TimeUnit::seconds(1), TimeUnit::seconds(5), true]; yield [TimeUnit::seconds(5), TimeUnit::seconds(5), false]; yield [TimeUnit::seconds(10), TimeUnit::seconds(5), false]; yield [TimeUnit::precise(0.000001), TimeUnit::precise(0.000000), false]; yield [TimeUnit::precise(0.000000), TimeUnit::precise(0.000000), false]; yield [TimeUnit::precise(0.000000), TimeUnit::precise(0.000001), true]; } /** * @return \Generator */ public static function less_than_eq_data_provider() : \Generator { yield [TimeUnit::seconds(1), TimeUnit::seconds(-5), false]; yield [TimeUnit::seconds(1), TimeUnit::seconds(5), true]; yield [TimeUnit::seconds(5), TimeUnit::seconds(5), true]; yield [TimeUnit::seconds(10), TimeUnit::seconds(5), false]; yield [TimeUnit::precise(0.000001), TimeUnit::precise(0.000000), false]; yield [TimeUnit::precise(0.000000), TimeUnit::precise(0.000000), true]; yield [TimeUnit::precise(0.000000), TimeUnit::precise(0.000001), true]; } /** * @return \Generator */ public static function equal_data_provider() : \Generator { yield [TimeUnit::seconds(1), TimeUnit::seconds(-5), false]; yield [TimeUnit::seconds(1), TimeUnit::seconds(5), false]; yield [TimeUnit::seconds(5), TimeUnit::seconds(5), true]; yield [TimeUnit::seconds(10), TimeUnit::seconds(5), false]; yield [TimeUnit::precise(0.000001), TimeUnit::precise(0.000000), false]; yield [TimeUnit::precise(0.000000), TimeUnit::precise(0.000000), true]; yield [TimeUnit::precise(0.000000), TimeUnit::precise(0.000001), false]; } /** * @return \Generator */ public static function adding_time_test_data_provider() : \Generator { yield [5, 5, 10]; yield [-20, 10, -10]; yield [-5, 10, 5]; } /** * @return \Generator */ public static function subtracting_time_test_data_provider() : \Generator { yield [5, 5, 0]; yield [5, 15, -10]; } /** * @return \Generator */ public static function adding_precise_time_test_data_provider() : \Generator { yield [0, 0, '0.000000', 0.0, 0.0]; yield [0, 500001, '0.500001', 0.0, 0.500001]; yield [0, 10001, '0.010001', 0.0, 0.010001]; yield [0, 0, '0.000000', -0.500000, 0.500000]; yield [0, 0, '0.000000', -0.500000, 0.5000001]; // 7+ decimal points are ignored yield [-1, 300000, '-1.300000', -1.500000, 0.200000]; } /** * @return \Generator */ public static function subtracting_precise_time_test_data_provider() : \Generator { yield [0, 0, '0.000000', 0.0, 0.0]; yield [0, 500000, '-0.500000', 0.0, 0.500000]; yield [0, 50000, '-0.050000', 0.0, 0.050000]; yield [2, 508825, '2.508825', 2.588460, 0.079635]; } /** * @return \Generator */ public static function creating_from_date_interval_provider() : \Generator { yield [\DateInterval::createFromDateString('5 microseconds'), TimeUnit::precise(0.000005)]; yield [\DateInterval::createFromDateString('1 second 5 microseconds'), TimeUnit::precise(1.000005)]; yield [\DateInterval::createFromDateString('1 second'), TimeUnit::second()]; yield [\DateInterval::createFromDateString('5 seconds'), TimeUnit::seconds(5)]; yield [\DateInterval::createFromDateString('1 minute'), TimeUnit::minute()]; yield [\DateInterval::createFromDateString('5 minutes'), TimeUnit::minutes(5)]; yield [\DateInterval::createFromDateString('1 hour'), TimeUnit::hour()]; yield [\DateInterval::createFromDateString('10 hours'), TimeUnit::hours(10)]; yield [\DateInterval::createFromDateString('28 hours'), TimeUnit::hours(28)]; yield [\DateInterval::createFromDateString('1 day'), TimeUnit::day()]; yield [\DateInterval::createFromDateString('365 days'), TimeUnit::days(365)]; yield [(new \DateTimeImmutable('2020-01-01'))->diff(new \DateTimeImmutable('2019-01-01')), TimeUnit::days(365)->invert()]; yield [(new \DateTimeImmutable('2020-01-01'))->diff(new \DateTimeImmutable('2020-03-01')), TimeUnit::days(60)]; } /** * @return \Generator */ public static function creating_from_date_string_provider() : \Generator { yield ['5 microseconds', TimeUnit::precise(0.000005)]; yield ['1 second 5 microsecond', TimeUnit::precise(1.000005)]; yield ['1 second', TimeUnit::second()]; yield ['5 seconds', TimeUnit::seconds(5)]; yield ['1 minute', TimeUnit::minute()]; yield ['5 minutes', TimeUnit::minutes(5)]; yield ['1 hour', TimeUnit::hour()]; yield ['10 hours', TimeUnit::hours(10)]; yield ['28 hours', TimeUnit::hours(28)]; yield ['1 day', TimeUnit::day()]; yield ['365 days', TimeUnit::days(365)]; } /** * @return \Generator */ public static function half_round_up_to_microsecond_data_provider() : \Generator { yield ['0.000000', 0.000_000_1]; yield ['0.000000', -0.000_000_1]; yield ['0.000001', 0.000_000_5]; yield ['-0.000001', -0.000_000_5]; yield ['-0.000001', -0.000_000_94]; yield ['0.000001', 0.000_000_99]; } /** * @return \Generator */ public static function multiplication_data_provider() : \Generator { yield [TimeUnit::precise(1.00), TimeUnit::precise(2.00), TimeUnit::precise(2.00)]; yield [TimeUnit::precise(1.00), TimeUnit::precise(0.50), TimeUnit::precise(0.50)]; yield [TimeUnit::seconds(1), TimeUnit::precise(0.50), TimeUnit::milliseconds(500)]; } /** * @return \Generator */ public static function division_data_provider() : \Generator { yield [TimeUnit::precise(1.00), TimeUnit::precise(2.00), TimeUnit::milliseconds(500)]; yield [TimeUnit::precise(10.00), TimeUnit::precise(10.00), TimeUnit::seconds(1)]; yield [TimeUnit::hours(1), TimeUnit::precise(60.00), TimeUnit::minutes(1)]; } /** * @return \Generator */ public static function modulo_data_provider() : \Generator { yield [TimeUnit::precise(1.00), TimeUnit::precise(2.00), TimeUnit::second()]; yield [TimeUnit::precise(10.00), TimeUnit::precise(10.00), TimeUnit::seconds(0)]; yield [TimeUnit::hours(1), TimeUnit::precise(60.00), TimeUnit::seconds(0)]; yield [TimeUnit::hours(1), TimeUnit::minutes(37), TimeUnit::minutes(23)]; } public function test_invert() : void { $timeUnit = TimeUnit::day()->invert(); $this->assertSame(-86400, $timeUnit->inSeconds()); } public function test_creating_with_negative_seconds() : void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Seconds must be greater or equal 0, got -1'); TimeUnit::negative(-1, 0); } public function test_creating_with_negative_microsecond() : void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Microsecond must be greater or equal 0 and less than 1000000, got -5'); TimeUnit::negative(0, -5); } public function test_creating_with_invalid_microsecond() : void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Microsecond must be greater or equal 0 and less than 1000000, got 1000000'); TimeUnit::negative(0, 1_000_000); } public function test_time_unit_create_from_day() : void { $unit = TimeUnit::day(); $this->assertSame(1, $unit->inDays()); } public function test_time_unit_create_from_days() : void { $unit = TimeUnit::days(5); $this->assertSame(5, $unit->inDays()); $this->assertSame(120, $unit->inHours()); $this->assertSame(7200, $unit->inMinutes()); $this->assertSame(432000, $unit->inSeconds()); $this->assertSame('432000.000000', $unit->inSecondsPrecise()); $this->assertSame(432000000, $unit->inMilliseconds()); $this->assertSame(432000, $unit->toDateInterval()->s); } public function test_time_unit_create_from_days_negative() : void { $unit = TimeUnit::days(-5); $this->assertSame(5, $unit->inDaysAbs()); $this->assertSame(-5, $unit->inDays()); $this->assertSame(-120, $unit->inHours()); $this->assertSame(120, $unit->inHoursAbs()); $this->assertSame(-7200, $unit->inMinutes()); $this->assertSame(7200, $unit->inMinutesAbs()); $this->assertSame(-432000, $unit->inSeconds()); $this->assertSame(432000, $unit->inSecondsAbs()); $this->assertSame('-432000.000000', $unit->inSecondsPrecise()); $this->assertSame(-432000000, $unit->inMilliseconds()); $this->assertSame(432000000, $unit->inMillisecondsAbs()); $this->assertSame(432000, $unit->toDateInterval()->s); $this->assertSame(1, $unit->toDateInterval()->invert); } public function test_time_unit_create_with_value_1() : void { $this->assertSame(1000, TimeUnit::millisecond()->microsecond()); $this->assertSame(0, TimeUnit::second()->microsecond()); $this->assertSame(0, TimeUnit::minute()->microsecond()); $this->assertSame(0, TimeUnit::hour()->microsecond()); $this->assertSame(0, TimeUnit::day()->microsecond()); } public function test_time_unit_create_with_0_value() : void { $this->assertTrue(TimeUnit::milliseconds(0)->isPositive()); $this->assertTrue(TimeUnit::seconds(0)->isPositive()); $this->assertTrue(TimeUnit::minutes(0)->isPositive()); $this->assertTrue(TimeUnit::hours(0)->isPositive()); $this->assertTrue(TimeUnit::days(0)->isPositive()); } public function test_time_unit_create_from_hours() : void { $unit = TimeUnit::hours(2); $this->assertSame(0, $unit->inDays()); $this->assertSame(2, $unit->inHours()); $this->assertSame(120, $unit->inMinutes()); $this->assertSame(7200, $unit->inSeconds()); } public function test_time_unit_create_from_minute() : void { $unit = TimeUnit::minute(); $this->assertSame(1, $unit->inMinutes()); } public function test_time_unit_create_from_0_minutes() : void { $unit = TimeUnit::minutes(0); $this->assertTrue($unit->isPositive()); } public function test_time_unit_create_from_minutes() : void { $unit = TimeUnit::minutes(2); $this->assertSame(0, $unit->inDays()); $this->assertSame(0, $unit->inHours()); $this->assertSame(2, $unit->inMinutes()); $this->assertSame(120, $unit->inSeconds()); } public function test_in_time_values() : void { $this->assertSame(2, TimeUnit::hours(26)->inTimeHours()); $this->assertSame(2, TimeUnit::hours(-26)->inTimeHours()); $this->assertSame(8, TimeUnit::seconds(-68)->inTimeSeconds()); $this->assertSame(8, TimeUnit::seconds(68)->inTimeSeconds()); $this->assertSame(8, TimeUnit::minutes(-68)->inTimeMinutes()); $this->assertSame(8, TimeUnit::minutes(68)->inTimeMinutes()); $this->assertSame(15, TimeUnit::minutes(135)->inTimeMinutes()); $this->assertSame(500, TimeUnit::milliseconds(1500)->inTimeMilliseconds()); $this->assertSame(500, TimeUnit::milliseconds(-1500)->inTimeMilliseconds()); } public function test_complex_time_unit_in_time_values() : void { $timeUnit = TimeUnit::days(3) ->add(TimeUnit::hours(4)) ->add(TimeUnit::minutes(85)) ->add(TimeUnit::seconds(30)) ->add(TimeUnit::milliseconds(1480)); $this->assertSame(5, $timeUnit->inTimeHours()); $this->assertSame(25, $timeUnit->inTimeMinutes()); $this->assertSame(31, $timeUnit->inTimeSeconds()); $this->assertSame(480, $timeUnit->inTimeMilliseconds()); } public function test_is_zero() : void { $this->assertFalse(TimeUnit::second()->isZero()); $this->assertTrue(TimeUnit::seconds(0)->isZero()); } public function test_second() : void { $timeUnit = TimeUnit::second(); $this->assertSame(1, $timeUnit->inSeconds()); } public function test_millisecond() : void { $timeUnit = TimeUnit::millisecond(); $this->assertSame(1000, $timeUnit->microsecond()); $this->assertSame(1, $timeUnit->inMilliseconds()); } public function test_milliseconds() : void { $timeUnit = TimeUnit::milliseconds(1500); $this->assertSame(500000, $timeUnit->microsecond()); $this->assertSame(1, $timeUnit->inSeconds()); $this->assertsame(1500, $timeUnit->inMilliseconds()); } public function test_creating_precise_timeunit() : void { $this->assertSame(500000, TimeUnit::precise(0.5)->microsecond()); $this->assertSame(0, TimeUnit::precise(0.5)->inSeconds()); $this->assertSame(500, TimeUnit::precise(0.5)->inMilliseconds()); $this->assertFalse(TimeUnit::precise(0.5)->isNegative()); $this->assertSame('0.500000', TimeUnit::precise(0.5)->inSecondsPrecise()); } public function test_creating_negative_precise_timeunit() : void { $this->assertSame(500000, TimeUnit::precise(-0.5)->microsecond()); $this->assertSame(0, TimeUnit::precise(-0.5)->inSeconds()); $this->assertSame(-500, TimeUnit::precise(-0.5)->inMilliseconds()); $this->assertTrue(TimeUnit::precise(-0.5)->isNegative()); $this->assertSame('-0.500000', TimeUnit::precise(-0.5)->inSecondsPrecise()); } #[DataProvider('greater_than_data_provider')] public function test_greater_than(TimeUnit $timeUnit, TimeUnit $nextTimeUnit, bool $expectedResult) : void { $this->assertSame($expectedResult, $timeUnit->isGreaterThan($nextTimeUnit)); } #[DataProvider('greater_than_eq_data_provider')] public function test_greater_than_eq(TimeUnit $timeUnit, TimeUnit $nextTimeUnit, bool $expectedResult) : void { $this->assertSame($expectedResult, $timeUnit->isGreaterThanOrEqualTo($nextTimeUnit)); } #[DataProvider('less_than_data_provider')] public function test_less_than(TimeUnit $timeUnit, TimeUnit $nextTimeUnit, bool $expectedResult) : void { $this->assertSame($expectedResult, $timeUnit->isLessThan($nextTimeUnit)); } #[DataProvider('less_than_eq_data_provider')] public function test_less_than_eq(TimeUnit $timeUnit, TimeUnit $nextTimeUnit, bool $expectedResult) : void { $this->assertSame($expectedResult, $timeUnit->isLessThanOrEqualTo($nextTimeUnit)); } #[DataProvider('equal_data_provider')] public function test_equal(TimeUnit $timeUnit, TimeUnit $nextTimeUnit, bool $expectedResult) : void { $this->assertSame($expectedResult, $timeUnit->isEqualTo($nextTimeUnit)); } #[DataProvider('adding_time_test_data_provider')] public function test_adding_time_units(int $seconds, int $addedSeconds, int $expectedSeconds) : void { $this->assertSame($expectedSeconds, TimeUnit::seconds($seconds)->add(TimeUnit::seconds($addedSeconds))->inSeconds()); } #[DataProvider('subtracting_time_test_data_provider')] public function test_subtracting_time_units(int $seconds, int $substractedSeconds, int $expectedSeconds) : void { $this->assertSame($expectedSeconds, TimeUnit::seconds($seconds)->sub(TimeUnit::seconds($substractedSeconds))->inSeconds()); } #[DataProvider('adding_precise_time_test_data_provider')] public function test_adding_precise_time_units(int $expectedSeconds, int $expectedMicrosecond, string $expectedPreciseString, float $seconds, float $addedSeconds) : void { $this->assertSame( $expectedSeconds, TimeUnit::precise($seconds)->add(TimeUnit::precise($addedSeconds))->inSeconds() ); $this->assertSame( $expectedMicrosecond, TimeUnit::precise($seconds)->add(TimeUnit::precise($addedSeconds))->microsecond() ); $this->assertSame( $expectedPreciseString, TimeUnit::precise($seconds)->add(TimeUnit::precise($addedSeconds))->inSecondsPrecise() ); } #[DataProvider('subtracting_precise_time_test_data_provider')] public function test_subtracting_precise_time_units(int $expectedSeconds, int $expectedMicrseond, string $expectedPreciseString, float $seconds, float $addedSeconds) : void { $this->assertSame( $expectedSeconds, TimeUnit::precise($seconds)->sub(TimeUnit::precise($addedSeconds))->inSeconds() ); $this->assertSame( $expectedMicrseond, TimeUnit::precise($seconds)->sub(TimeUnit::precise($addedSeconds))->microsecond() ); $this->assertSame( $expectedPreciseString, TimeUnit::precise($seconds)->sub(TimeUnit::precise($addedSeconds))->inSecondsPrecise() ); } #[DataProvider('creating_from_date_interval_provider')] public function test_creating_from_date_interval(\DateInterval $dateInterval, TimeUnit $timeUnit) : void { $this->assertObjectEquals(TimeUnit::fromDateInterval($dateInterval), $timeUnit, 'isEqual'); } #[DataProvider('creating_from_date_string_provider')] public function test_creating_from_date_string(string $dateString, TimeUnit $timeUnit) : void { $this->assertObjectEquals(TimeUnit::fromDateString($dateString), $timeUnit, 'isEqual'); } public function test_creating_from_inaccurate_date_interval_years() : void { $this->expectException(Exception::class); $this->expectExceptionMessage('Can\'t convert P1Y0M0DT0H0M0S precisely to time unit because year can\'t be directly converted to number of seconds.'); TimeUnit::fromDateInterval(\DateInterval::createFromDateString('1 year')); } public function test_creating_from_inaccurate_date_interval_months() : void { $this->expectException(Exception::class); $this->expectExceptionMessage('Can\'t convert P0Y4M0DT0H0M0S precisely to time unit because month can\'t be directly converted to number of seconds.'); TimeUnit::fromDateInterval(\DateInterval::createFromDateString('4 months')); } #[DataProvider('half_round_up_to_microsecond_data_provider')] public function test_half_round_up_to_microsecond(string $stringFloat, float $float) : void { $this->assertSame( $stringFloat, TimeUnit::precise($float)->inSecondsPrecise(), $stringFloat . ' is not equal ' . $float ); } #[DataProvider('multiplication_data_provider')] public function test_multiplication(TimeUnit $timeUnit, TimeUnit $multiplier, TimeUnit $expectedResult) : void { $this->assertSame($expectedResult->inSecondsPrecise(), $timeUnit->multiply($multiplier)->inSecondsPrecise()); } #[DataProvider('division_data_provider')] public function test_division(TimeUnit $timeUnit, TimeUnit $multiplier, TimeUnit $expectedResult) : void { $this->assertSame($expectedResult->inSecondsPrecise(), $timeUnit->divide($multiplier)->inSecondsPrecise()); } #[DataProvider('modulo_data_provider')] public function test_modulo(TimeUnit $timeUnit, TimeUnit $multiplier, TimeUnit $expectedResult) : void { $this->assertSame($expectedResult->inSecondsPrecise(), $timeUnit->modulo($multiplier)->inSecondsPrecise()); } public function test_to_negative() : void { $this->assertTrue(TimeUnit::negative(10, 0)->toNegative()->isNegative()); $this->assertTrue(TimeUnit::positive(10, 0)->toNegative()->isNegative()); } public function test_to_positive() : void { $this->assertTrue(TimeUnit::positive(10, 0)->toPositive()->isPositive()); $this->assertTrue(TimeUnit::negative(10, 0)->toPositive()->isPositive()); } public function test_to_date_interval() : void { $interval = new \DateInterval('PT2S'); $interval->f = 0.500000; $this->assertEquals($interval, TimeUnit::precise(2.500000)->toDateInterval()); } public function test_getting_microsecond_string() : void { $this->assertSame('500000', TimeUnit::precise(2.500000)->microsecondString()); } public function test_serialization() : void { $timeUnit = TimeUnit::positive(10, 10); $this->assertSame( [ 'seconds' => 10, 'microsecond' => 10, 'negative' => false, ], $timeUnit->__serialize() ); } }