assertFalse(WorkflowRunner::hasPassed(null, self::utc('2026-01-15 12:00:00'))); } public function testHasPassedReturnsFalseForFutureDate(): void { $now = self::utc('2026-01-15 12:00:00'); $this->assertFalse(WorkflowRunner::hasPassed($now->modify('+1 hour'), $now)); $this->assertFalse(WorkflowRunner::hasPassed($now->modify('+3 days'), $now)); } public function testHasPassedReturnsTrueForPastDate(): void { $now = self::utc('2026-01-15 12:00:00'); $this->assertTrue(WorkflowRunner::hasPassed($now->modify('-1 hour'), $now)); $this->assertTrue(WorkflowRunner::hasPassed($now->modify('-30 days'), $now)); } public function testHasPassedReturnsTrueForATimeEarlierTheSameDay(): void { // Regression: comparing whole calendar days (as Deck's own // Card::getDaysUntilDue() does) reports a card due earlier today as // "0 days until due", not overdue, until the calendar date rolls // over - holding every card back for up to 24h after it actually // fell due. hasPassed() must compare the real timestamps instead. $now = self::utc('2026-01-15 12:00:00'); $this->assertTrue(WorkflowRunner::hasPassed(self::utc('2026-01-15 06:00:00'), $now)); } public function testHasPassedReadsTheStoredWallClockAsUtc(): void { // What Entity hydration produces if PHP's default zone is not UTC: // the stored UTC wall clock "12:00", tagged with the local zone. $date = new DateTimeImmutable('2026-07-15 12:00:00', new DateTimeZone('Europe/Berlin')); // Taken at face value that would be 10:00 UTC and already passed. $this->assertFalse(WorkflowRunner::hasPassed($date, self::utc('2026-07-15 11:30:00'))); $this->assertTrue(WorkflowRunner::hasPassed($date, self::utc('2026-07-15 12:00:01'))); } public function testHasPassedComparesNowAsAnAbsoluteInstant(): void { $date = self::utc('2026-07-15 12:00:00'); $berlin = new DateTimeZone('Europe/Berlin'); // 13:30 in Berlin (CEST) is 11:30 UTC - not yet passed. $this->assertFalse(WorkflowRunner::hasPassed($date, new DateTimeImmutable('2026-07-15 13:30:00', $berlin))); // 14:30 in Berlin is 12:30 UTC - passed. $this->assertTrue(WorkflowRunner::hasPassed($date, new DateTimeImmutable('2026-07-15 14:30:00', $berlin))); } public function testHasPassedIsFalseAtTheExactInstant(): void { $now = self::utc('2026-01-15 12:00:00'); $this->assertFalse(WorkflowRunner::hasPassed($now, $now)); } }