sebastianbergmann/comparator · error · ComparisonFailure

Failed asserting that two DateTime objects are equal.

Error message

Failed asserting that two DateTime objects are equal.

What it means

ComparisonFailure thrown by DateTimeComparator::assertEquals when the actual DateTime/DateTimeInterface falls outside the window [expected - delta, expected + delta] after both are normalized to UTC. The exported strings are ISO-8601 formatted with microseconds and offset.

Solutions

  1. Pass an appropriate $delta (e.g. 1.0 second) to tolerate clock/precision differences.
  2. Normalize both values to the same timezone before comparing if wall-clock time is what matters.
  3. Diff the ISO-8601 strings in the exception to see the exact instant difference.
  4. Freeze time in tests (inject a clock or use a time-shim) instead of comparing against new DateTime('now').

Example fix

// before
$c->assertEquals(new DateTime('2024-01-01T00:00:00+00:00'), $actual); // fails for 200ms drift
// after
$c->assertEquals(new DateTime('2024-01-01T00:00:00+00:00'), $actual, 1.0); // 1s tolerance
Defensive patterns

Strategy: validation

Validate before calling

// both instants within tolerance (UTC-normalized)?
$diff = abs($expected->getTimestamp() - $actual->getTimestamp());
if ($diff > (int) $delta) { /* comparator will throw */ }

Type guard

function isDateTimeLike(mixed $v): bool { return $v instanceof DateTimeInterface; }

Try / catch

try {
    $c->assertEquals($expected, $actual, 1.0);
} catch (ComparisonFailure $e) {
    // ISO-8601 strings of both instants are in getExpectedAsString()/getActualAsString()
}

Prevention

When it happens

Trigger: assertEquals on two DateTime objects whose UTC timestamps differ by more than $delta; common when one side uses a different timezone (only the instant matters after UTC normalization), or when delta defaults to 0.0 for times generated with different precision.

Common situations: Testing timestamps with sub-second truncation; comparing 'now'-based values with clock skew; mixing timezones (e.g. '+02:00' vs 'UTC' representations of the same instant pass, but genuinely different instants fail); fixtures hard-coded in a different TZ environment.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of sebastianbergmann/comparator@00837a9d22 (2026-09-15). Data as JSON: /api/errors/7a64d5fd500bf739. Report an issue: GitHub.

Appendix: source

Thrown at src/DateTimeComparator.php:74

        $absDelta = abs($delta);

        /** @phpstan-ignore argument.type */
        $delta    = new DateInterval(sprintf('PT%dS', $absDelta));
        $delta->f = $absDelta - floor($absDelta);

        $actualClone = (clone $actual)
            ->setTimezone(new DateTimeZone('UTC'));

        $expectedLower = (clone $expected)
            ->setTimezone(new DateTimeZone('UTC'))
            ->sub($delta);

        $expectedUpper = (clone $expected)
            ->setTimezone(new DateTimeZone('UTC'))
            ->add($delta);

        if ($actualClone < $expectedLower || $actualClone > $expectedUpper) {
            throw new ComparisonFailure(
                $expected,
                $actual,
                $expected->format('Y-m-d\TH:i:s.uO'),
                $actual->format('Y-m-d\TH:i:s.uO'),
                'Failed asserting that two DateTime objects are equal.',
                $this->contextLines(),
            );
        }
    }
}

View on GitHub (pinned to 00837a9d22)