. */ declare(strict_types=1); namespace FireflyIII\Support\Calendar; use Carbon\Carbon; use FireflyIII\Exceptions\IntervalException; use SplObjectStorage; /** * Class Calculator */ class Calculator { public const int DEFAULT_INTERVAL = 1; private static ?SplObjectStorage $intervalMap = null; // @phpstan-ignore-line private static array $intervals = []; /** * @throws IntervalException */ public function nextDateByInterval(Carbon $epoch, Periodicity $periodicity, int $skipInterval = 0): Carbon { if (!self::isAvailablePeriodicity($periodicity)) { throw IntervalException::unavailable($periodicity, self::$intervals); } /** @var Periodicity\Interval $periodicity */ $periodicity = self::$intervalMap->offsetGet($periodicity); $interval = $this->skipInterval($skipInterval); return $periodicity->nextDate($epoch->clone(), $interval); } public function isAvailablePeriodicity(Periodicity $periodicity): bool { return self::containsInterval($periodicity); } private static function containsInterval(Periodicity $periodicity): bool { return self::loadIntervalMap()->contains($periodicity); } private static function loadIntervalMap(): SplObjectStorage { if (self::$intervalMap instanceof SplObjectStorage) { return self::$intervalMap; } self::$intervalMap = new SplObjectStorage(); foreach (Periodicity::cases() as $interval) { $periodicityClass = sprintf('%s\Periodicity\%s', __NAMESPACE__, $interval->name); self::$intervals[] = $interval->name; self::$intervalMap->attach($interval, new $periodicityClass()); } return self::$intervalMap; } private function skipInterval(int $skip): int { return self::DEFAULT_INTERVAL + $skip; } }