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
- Pass an appropriate $delta (e.g. 1.0 second) to tolerate clock/precision differences.
- Normalize both values to the same timezone before comparing if wall-clock time is what matters.
- Diff the ISO-8601 strings in the exception to see the exact instant difference.
- 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
- Pass an explicit delta for 'now'-based or precision-sensitive timestamps.
- Normalize to UTC when comparing instants; only the instant matters to this comparator.
- Freeze or inject time in tests instead of relying on real clock.
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
- Failed asserting that two DateInterval objects are equal.
- Failed asserting that two arrays are equal.
- Failed asserting that closure declared at
- Failed asserting that two DOM
- Failed asserting that two values of enumeration
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)