diff --git a/src/Symfony/Component/Messenger/Stamp/DelayStamp.php b/src/Symfony/Component/Messenger/Stamp/DelayStamp.php index d5a4839b1e512..99ca527e5dd34 100644 --- a/src/Symfony/Component/Messenger/Stamp/DelayStamp.php +++ b/src/Symfony/Component/Messenger/Stamp/DelayStamp.php @@ -11,11 +11,21 @@ namespace Symfony\Component\Messenger\Stamp; +use Symfony\Component\Messenger\Exception\InvalidArgumentException; + /** * Apply this stamp to delay delivery of your message on a transport. */ final class DelayStamp implements StampInterface { + public const PERIOD_SECONDS = 'seconds'; + public const PERIOD_MINUTES = 'minutes'; + public const PERIOD_HOURS = 'hours'; + public const PERIOD_DAYS = 'days'; + public const PERIOD_WEEKS = 'weeks'; + public const PERIOD_MONTHS = 'months'; + public const PERIOD_YEARS = 'years'; + private $delay; /** @@ -33,16 +43,51 @@ public function getDelay(): int public static function delayForSeconds(int $seconds): self { - return new self($seconds * 1000); + return self::delayFor($seconds, self::PERIOD_SECONDS); } public static function delayForMinutes(int $minutes): self { - return self::delayForSeconds($minutes * 60); + return self::delayFor($minutes, self::PERIOD_MINUTES); } public static function delayForHours(int $hours): self { - return self::delayForMinutes($hours * 60); + return self::delayFor($hours, self::PERIOD_HOURS); + } + + public static function delayUntil(\DateTimeInterface $executeAfter): self + { + $now = (new \DateTimeImmutable())->setTimezone($executeAfter->getTimezone()); + + if ($now >= $executeAfter) { + throw new InvalidArgumentException(sprintf('You cannot pass a date that is equal to now or is in the past. Now is "%s" and the passed date is "%s".', $now->format('Y-m-d, H:i:s'), $executeAfter->format('Y-m-d, H:i:s'))); + } + + $diff = ($executeAfter->format('U') - $now->format('U')) * 1000; + + return new self($diff); + } + + /** + * @param string $period A string representing a unit symbol valid for relative formats of DateTime objects + * + * @see https://www.php.net/manual/en/datetime.formats.relative.php#datetime.formats.relative + */ + public static function delayFor(int $units, string $period) + { + if (0 >= $units) { + throw new InvalidArgumentException(sprintf('The value of units has to be positive. You passed "%s".', $units)); + } + + $allowedPeriods = [self::PERIOD_SECONDS, self::PERIOD_MINUTES, self::PERIOD_HOURS, self::PERIOD_DAYS, self::PERIOD_WEEKS, self::PERIOD_MONTHS, self::PERIOD_YEARS]; + if (false === \in_array($period, $allowedPeriods)) { + throw new InvalidArgumentException(sprintf('The passed period "%s" is not allowed. Allowed periods are: "%s".', $period, implode(', ', $allowedPeriods))); + } + + $rescheduleIn = sprintf('+%s %s', $units, $period); + $executeAfter = (new \DateTime())->modify($rescheduleIn); + + return self::delayUntil($executeAfter); } } diff --git a/src/Symfony/Component/Messenger/Tests/Stamp/DelayStampTest.php b/src/Symfony/Component/Messenger/Tests/Stamp/DelayStampTest.php index f739c9526177d..b1c5875317e73 100644 --- a/src/Symfony/Component/Messenger/Tests/Stamp/DelayStampTest.php +++ b/src/Symfony/Component/Messenger/Tests/Stamp/DelayStampTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Messenger\Tests\Stamp; use PHPUnit\Framework\TestCase; +use Symfony\Component\Messenger\Exception\InvalidArgumentException; use Symfony\Component\Messenger\Stamp\DelayStamp; /** @@ -36,4 +37,30 @@ public function testHours() $stamp = DelayStamp::delayForHours(30); $this->assertSame(108000000, $stamp->getDelay()); } + + public function testDelayUntil() + { + $untilDate = (new \DateTime())->modify('+30 minutes'); + $stamp = DelayStamp::delayUntil($untilDate); + $this->assertSame(1800000, $stamp->getDelay()); + } + + public function testDelayUntilAcceptsOnlyFutureDates() + { + $untilDate = new \DateTime('1970-01-01T00:00:00Z'); + $this->expectException(InvalidArgumentException::class); + DelayStamp::delayUntil($untilDate); + } + + public function testDelayFor() + { + $stamp = DelayStamp::delayFor(30, DelayStamp::PERIOD_MINUTES); + $this->assertSame(1800000, $stamp->getDelay()); + } + + public function testDelayForAcceptsOnlyPositiveUnits() + { + $this->expectException(InvalidArgumentException::class); + DelayStamp::delayFor(-30, DelayStamp::PERIOD_MINUTES); + } }