Repository: assertj/assertj (Java)

Issue:
Unable to compare some timestamp types
**Describe the bug**
A clear and concise description of what the bug is.

* assertj core version: 3.25.3
* java version: 21.0.1-open
* test framework version: JUnit 5.10.2
* os (if relevant):  Linux 6.5.0-15-generic # 15-Ubuntu SMP PREEMPT_DYNAMIC Tue Jan  9 17:03:36 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux (Ubuntu 23.10)

**Test case reproducing the bug**

Some timestamp types, for example, `java.sql.Timestamp`, are not specifically handled when comparing with other temporal types, and as a result, though there are no compile-time errors caused by types, they are not effectively compared as temporals.

```java
public class Demo {
    @Test
    void compareSqlTimestampWithInstant() {
        final long now = System.currentTimeMillis();
        final Timestamp ts = new Timestamp(now);
        final Instant instant = Instant.ofEpochMilli(now);
        assertEquals(ts.getNanos(), instant.getNano()); // this succeeds
        assertThat(ts).isEqualTo(instant); // this fails.
        assertThat(ts).isAfterOrEqualTo(instant); // even if I want to relax the check, this still fails
    }
}
```

I can see there are javadocs mentioning that comparisons are done by converting these timestamp types to `java.util.Date`, but is there a way to make this less surprising, otherwise make it a compilation error, so the user can take care of nanoseconds?

**Updated**: I thought it was caused by nano-second precision being dropped along the assertion classes, but it turns out that there is no special handling of such types, and they end up calling `Object.equals()`.


Hints:
Thanks for reporting it, @IUSR.

The root cause is the usage of `Date.from(Instant)` internally, which doesn't behave correctly when `actual` is a `Timestamp`.

As a workaround, you can rely on [`Timestamp.from(Instant)`](https://docs.oracle.com/javase/8/docs/api/java/sql/Timestamp.html#from-java.time.Instant-):

```java
assertThat(ts).isEqualTo(Timestamp.from(instant));
assertThat(ts).isAfterOrEqualTo(Timestamp.from(instant));
```
Thanks for confirming, Stefano! Yes, I am currently converting the timestamp/temporal objects into the same type and comparing them as a workaround. It'd be good when things "just work", as my experience with assertj has been :)
I'm afraid we didn't fully solve the issue.

Although the original example now works, a case with non-zero nanoseconds fails:

```java
Instant instant = Instant.ofEpochSecond(0, 1); // 1970-01-01T00:00:00.000000001Z
Timestamp timestamp = Timestamp.from(instant);
assertThat(timestamp).isEqualTo(instant); // fails
```

I'll reopen it and have a look.

Patch:
diff --git a/assertj-core/src/main/java/org/assertj/core/api/AbstractDateAssert.java b/assertj-core/src/main/java/org/assertj/core/api/AbstractDateAssert.java
--- a/assertj-core/src/main/java/org/assertj/core/api/AbstractDateAssert.java
+++ b/assertj-core/src/main/java/org/assertj/core/api/AbstractDateAssert.java
@@ -27,6 +27,7 @@
 import static org.assertj.core.util.Lists.list;
 import static org.assertj.core.util.Lists.newArrayList;
 
+import java.sql.Timestamp;
 import java.text.DateFormat;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
@@ -49,9 +50,9 @@
 /**
  * Base class for all implementations of assertions for {@link Date}s.
  * <p>
- * Note that assertions with date parameter comes with two flavor, one is obviously a {@link Date} and the other is a
+ * Note that assertions with date parameter come with two flavors, one is obviously a {@link Date} and the other is a
  * {@link String} representing a Date.<br>
- * For the latter, the default format follows ISO 8901 : "yyyy-MM-dd", user can override it with a custom format by
+ * For the latter, the default format follows ISO 8901: "yyyy-MM-dd", user can override it with a custom format by
  * calling {@link #withDateFormat(DateFormat)}.<br>
  * The user custom format will then be used for all next Date assertions (i.e not limited to the current assertion) in
  * the test suite.<br>
@@ -86,7 +87,7 @@ public abstract class AbstractDateAssert<SELF extends AbstractDateAssert<SELF>>
   /**
    * Used in String based Date assertions - like {@link #isAfter(String)} - to convert input date represented as string
    * to Date.<br>
-   * It keeps the insertion order so first format added will be first format used.
+   * It keeps the insertion order, so the first format added will be the first format used.
    */
   @VisibleForTesting
   static ThreadLocal<LinkedHashSet<DateFormat>> userDateFormats = ThreadLocal.withInitial(LinkedHashSet::new);
@@ -100,10 +101,10 @@ protected AbstractDateAssert(Date actual, Class<?> selfType) {
 
   /**
    * Same assertion as {@link AbstractAssert#isEqualTo(Object) isEqualTo(Date date)} but given date is represented as
-   * a {@code String} either with one of the supported defaults date format or a user custom date format set with method
+   * a {@code String} either with one of the supported default date formats or a user custom date format set with method
    * {@link #withDateFormat(DateFormat)}.
    * <p>
-   * User custom date format take precedence over the default ones.
+   * User custom date format takes precedence over the default ones.
    * <p>
    * Unless specified otherwise, beware that the default formats are expressed in the current local timezone.
    * <p>
@@ -161,12 +162,12 @@ public SELF isEqualTo(String dateAsString) {
    * @throws AssertionError if actual {@code Date} and given {@link Instant} are not equal (after converting instant to a Date).
    */
   public SELF isEqualTo(Instant instant) {
-    return isEqualTo(Date.from(instant));
+    return isEqualTo(dateFrom(instant));
   }
 
   /**
    * Same assertion as {@link AbstractDateAssert#isEqualToIgnoringHours(Date)} but given Date is represented as String
-   * either with one of the default supported date format or user custom date format (set with method
+   * either with one of the default supported date formats or user custom date format (set with method
    * {@link #withDateFormat(DateFormat)}).
    * <p>
    * User custom date format take precedence over the default ones.
@@ -232,7 +233,7 @@ public SELF isEqualToIgnoringHours(String dateAsString) {
    */
   @Deprecated
   public SELF isEqualToIgnoringHours(Instant instant) {
-    return isEqualToIgnoringHours(Date.from(instant));
+    return isEqualToIgnoringHours(dateFrom(instant));
   }
 
   /**
@@ -332,12 +333,12 @@ public SELF isEqualToIgnoringMinutes(String dateAsString) {
    */
   @Deprecated
   public SELF isEqualToIgnoringMinutes(Instant instant) {
-    return isEqualToIgnoringMinutes(Date.from(instant));
+    return isEqualToIgnoringMinutes(dateFrom(instant));
   }
 
   /**
    * Same assertion as {@link AbstractAssert#isEqualTo(Object)} but given Date should not take care of minutes,
-   * seconds and milliseconds precision.
+   * seconds and millisecond precision.
    * <p>
    * Example:
    * <pre><code class='java'> Date date1 = parseDatetime("2003-04-26T13:01:35");
@@ -367,10 +368,10 @@ public SELF isEqualToIgnoringMinutes(Date date) {
   /**
    * Same assertion as {@link AbstractDateAssert#isEqualToIgnoringSeconds(Date)} but given Date is represented as
    * String
-   * either with one of the default supported date format or user custom date format (set with method
+   * either with one of the default supported date formats or user custom date format (set with method
    * {@link #withDateFormat(DateFormat)}).
    * <p>
-   * User custom date format take precedence over the default ones.
+   * User custom date format takes precedence over the default ones.
    * <p>
    * Unless specified otherwise, beware that the default formats are expressed in the current local timezone.
    * <p>
@@ -435,12 +436,12 @@ public SELF isEqualToIgnoringSeconds(String dateAsString) {
    */
   @Deprecated
   public SELF isEqualToIgnoringSeconds(Instant instant) {
-    return isEqualToIgnoringSeconds(Date.from(instant));
+    return isEqualToIgnoringSeconds(dateFrom(instant));
   }
 
   /**
    * Same assertion as {@link AbstractAssert#isEqualTo(Object)} but given Date should not take care of seconds and
-   * milliseconds precision.
+   * millisecond precision.
    * <p>
    * Example:
    * <pre><code class='java'> Date date1 = parseDatetime("2003-04-26T13:01:35");
@@ -469,10 +470,10 @@ public SELF isEqualToIgnoringSeconds(Date date) {
 
   /**
    * Same assertion as {@link AbstractDateAssert#isEqualToIgnoringMillis(Date)} but given Date is represented as String
-   * either with one of the default supported date format or user custom date format (set with method
+   * either with one of the default supported date formats or user custom date format (set with method
    * {@link #withDateFormat(DateFormat)}).
    * <p>
-   * User custom date format take precedence over the default ones.
+   * User custom date format takes precedence over the default ones.
    * <p>
    * Unless specified otherwise, beware that the default formats are expressed in the current local timezone.
    * <p>
@@ -536,7 +537,7 @@ public SELF isEqualToIgnoringMillis(String dateAsString) {
    */
   @Deprecated
   public SELF isEqualToIgnoringMillis(Instant instant) {
-    return isEqualToIgnoringMillis(Date.from(instant));
+    return isEqualToIgnoringMillis(dateFrom(instant));
   }
 
   /**
@@ -569,10 +570,10 @@ public SELF isEqualToIgnoringMillis(Date date) {
 
   /**
    * Same assertion as {@link AbstractAssert#isNotEqualTo(Object) isNotEqualTo(Date date)} but given date is
-   * represented as String either with one of the supported defaults date format or a user custom date format (set with
+   * represented as String either with one of the supported default date formats or a user custom date format (set with
    * method {@link #withDateFormat(DateFormat)}).
    * <p>
-   * User custom date format take precedence over the default ones.
+   * User custom date format takes precedence over the default ones.
    * <p>
    * Unless specified otherwise, beware that the default formats are expressed in the current local timezone.
    * <p>
@@ -631,14 +632,14 @@ public SELF isNotEqualTo(String dateAsString) {
    * @since 3.19.0
    */
   public SELF isNotEqualTo(Instant instant) {
-    return isNotEqualTo(Date.from(instant));
+    return isNotEqualTo(dateFrom(instant));
   }
 
   /**
    * Same assertion as {@link Assert#isIn(Object...)}but given dates are represented as String either with one of the
-   * supported defaults date format or a user custom date format (set with method {@link #withDateFormat(DateFormat)}).
+   * supported default date formats or a user custom date format (set with method {@link #withDateFormat(DateFormat)}).
    * <p>
-   * User custom date format take precedence over the default ones.
+   * User custom date format takes precedence over the default ones.
    * <p>
    * Unless specified otherwise, beware that the default formats are expressed in the current local timezone.
    * <p>
@@ -822,9 +823,9 @@ public SELF isNotIn(Instant... instants) {
 
   /**
    * Same assertion as {@link Assert#isNotIn(Iterable)} but given date is represented as String either with one of the
-   * supported defaults date format or a user custom date format (set with method {@link #withDateFormat(DateFormat)}).
+   * supported default date formats or a user custom date format (set with method {@link #withDateFormat(DateFormat)}).
    * <p>
-   * User custom date format take precedence over the default ones.
+   * User custom date format takes precedence over the default ones.
    * <p>
    * Unless specified otherwise, beware that the default formats are expressed in the current local timezone.
    * <p>
@@ -912,15 +913,15 @@ public SELF isBefore(Date other) {
    * @since 3.19.0
    */
   public SELF isBefore(Instant other) {
-    dates.assertIsBefore(info, actual, Date.from(other));
+    dates.assertIsBefore(info, actual, dateFrom(other));
     return myself;
   }
 
   /**
    * Same assertion as {@link #isBefore(Date)} but given date is represented as String either with one of the
-   * supported defaults date format or a user custom date format (set with method {@link #withDateFormat(DateFormat)}).
+   * supported default date formats or a user custom date format (set with method {@link #withDateFormat(DateFormat)}).
    * <p>
-   * User custom date format take precedence over the default ones.
+   * User custom date format takes precedence over the default ones.
    * <p>
    * Unless specified otherwise, beware that the default formats are expressed in the current local timezone.
    * <p>
@@ -1037,15 +1038,15 @@ public SELF isBeforeOrEqualTo(Date other) {
    * @since 3.19.0
    */
   public SELF isBeforeOrEqualTo(Instant other) {
-    dates.assertIsBeforeOrEqualTo(info, actual, Date.from(other));
+    dates.assertIsBeforeOrEqualTo(info, actual, dateFrom(other));
     return myself;
   }
 
   /**
    * Same assertion as {@link #isBeforeOrEqualsTo(Date)} but given date is represented as String either with one of the
-   * supported defaults date format or a user custom date format (set with method {@link #withDateFormat(DateFormat)}).
+   * supported default date formats or a user custom date format (set with method {@link #withDateFormat(DateFormat)}).
    * <p>
-   * User custom date format take precedence over the default ones.
+   * User custom date format takes precedence over the default ones.
    * <p>
    * Unless specified otherwise, beware that the default formats are expressed in the current local timezone.
    * <p>
@@ -1189,15 +1190,15 @@ public SELF isAfter(Date other) {
    * @since 3.19.0
    */
   public SELF isAfter(Instant other) {
-    dates.assertIsAfter(info, actual, Date.from(other));
+    dates.assertIsAfter(info, actual, dateFrom(other));
     return myself;
   }
 
   /**
    * Same assertion as {@link #isAfter(Date)} but given date is represented as String either with one of the
-   * supported defaults date format or a user custom date format (set with method {@link #withDateFormat(DateFormat)}).
+   * supported default date formats or a user custom date format (set with method {@link #withDateFormat(DateFormat)}).
    * <p>
-   * User custom date format take precedence over the default ones.
+   * User custom date format takes precedence over the default ones.
    * <p>
    * Unless specified otherwise, beware that the default formats are expressed in the current local timezone.
    * <p>
@@ -1313,15 +1314,15 @@ public SELF isAfterOrEqualTo(Date other) {
    * @since 3.19.0
    */
   public SELF isAfterOrEqualTo(Instant other) {
-    dates.assertIsAfterOrEqualTo(info, actual, Date.from(other));
+    dates.assertIsAfterOrEqualTo(info, actual, dateFrom(other));
     return myself;
   }
 
   /**
    * Same assertion as {@link #isAfterOrEqualsTo(Date)} but given date is represented as String either with one of the
-   * supported defaults date format or a user custom date format (set with method {@link #withDateFormat(DateFormat)}).
+   * supported default date formats or a user custom date format (set with method {@link #withDateFormat(DateFormat)}).
    * <p>
-   * User custom date format take precedence over the default ones.
+   * User custom date format takes precedence over the default ones.
    * <p>
    * Unless specified otherwise, beware that the default formats are expressed in the current local timezone.
    * <p>
@@ -1514,7 +1515,7 @@ public SELF isBetween(String start, String end) {
    * @since 3.19.0
    */
   public SELF isBetween(Instant start, Instant end) {
-    return isBetween(Date.from(start), Date.from(end));
+    return isBetween(dateFrom(start), dateFrom(end));
   }
 
   /**
@@ -1552,10 +1553,10 @@ public SELF isBetween(Date start, Date end, boolean inclusiveStart, boolean incl
 
   /**
    * Same assertion as {@link #isBetween(Date, Date, boolean, boolean)} but given date is represented as String either
-   * with one of the supported defaults date format or a user custom date format (set with method
+   * with one of the supported default date formats or a user custom date format (set with method
    * {@link #withDateFormat(DateFormat)}).
    * <p>
-   * User custom date format take precedence over the default ones.
+   * User custom date format takes precedence over the default ones.
    * <p>
    * Unless specified otherwise, beware that the default formats are expressed in the current local timezone.
    * <p>
@@ -1625,7 +1626,7 @@ public SELF isBetween(String start, String end, boolean inclusiveStart, boolean
    * @since 3.19.0
    */
   public SELF isBetween(Instant start, Instant end, boolean inclusiveStart, boolean inclusiveEnd) {
-    dates.assertIsBetween(info, actual, Date.from(start), Date.from(end), inclusiveStart, inclusiveEnd);
+    dates.assertIsBetween(info, actual, dateFrom(start), dateFrom(end), inclusiveStart, inclusiveEnd);
     return myself;
   }
 
@@ -1688,16 +1689,16 @@ public SELF isNotBetween(Date start, Date end, boolean inclusiveStart, boolean i
    * @since 3.19.0
    */
   public SELF isNotBetween(Instant start, Instant end, boolean inclusiveStart, boolean inclusiveEnd) {
-    dates.assertIsNotBetween(info, actual, Date.from(start), Date.from(end), inclusiveStart, inclusiveEnd);
+    dates.assertIsNotBetween(info, actual, dateFrom(start), dateFrom(end), inclusiveStart, inclusiveEnd);
     return myself;
   }
 
   /**
    * Same assertion as {@link #isNotBetween(Date, Date, boolean, boolean)} but given date is represented as String
-   * either with one of the supported defaults date format or a user custom date format (set with method
+   * either with one of the supported default date formats or a user custom date format (set with method
    * {@link #withDateFormat(DateFormat)}).
    * <p>
-   * User custom date format take precedence over the default ones.
+   * User custom date format takes precedence over the default ones.
    * <p>
    * Unless specified otherwise, beware that the default formats are expressed in the current local timezone.
    * <p>
@@ -1798,14 +1799,14 @@ public SELF isNotBetween(Date start, Date end) {
    * @since 3.19.0
    */
   public SELF isNotBetween(Instant start, Instant end) {
-    return isNotBetween(Date.from(start), Date.from(end), true, false);
+    return isNotBetween(dateFrom(start), dateFrom(end), true, false);
   }
 
   /**
    * Same assertion as {@link #isNotBetween(Date, Date)} but given date is represented as String either with one of the
-   * supported defaults date format or a user custom date format (set with method {@link #withDateFormat(DateFormat)}).
+   * supported default date formats or a user custom date format (set with method {@link #withDateFormat(DateFormat)}).
    * <p>
-   * User custom date format take precedence over the default ones.
+   * User custom date format takes precedence over the default ones.
    * <p>
    * Unless specified otherwise, beware that the default formats are expressed in the current local timezone.
    * <p>
@@ -2253,7 +2254,7 @@ public SELF isInSameYearAs(Date other) {
    * @since 3.19.0
    */
   public SELF isInSameYearAs(Instant other) {
-    dates.assertIsInSameYearAs(info, actual, Date.from(other));
+    dates.assertIsInSameYearAs(info, actual, dateFrom(other));
     return myself;
   }
 
@@ -2344,15 +2345,15 @@ public SELF isInSameMonthAs(Date other) {
    * @since 3.19.0
    */
   public SELF isInSameMonthAs(Instant other) {
-    dates.assertIsInSameMonthAs(info, actual, Date.from(other));
+    dates.assertIsInSameMonthAs(info, actual, dateFrom(other));
     return myself;
   }
 
   /**
    * Same assertion as {@link #isInSameMonthAs(Date)}but given date is represented as String either with one of the
-   * supported defaults date format or a user custom date format (set with method {@link #withDateFormat(DateFormat)}).
+   * supported default date formats or a user custom date format (set with method {@link #withDateFormat(DateFormat)}).
    * <p>
-   * User custom date format take precedence over the default ones.
+   * User custom date format takes precedence over the default ones.
    * <p>
    * Unless specified otherwise, beware that the default formats are expressed in the current local timezone.
    * <p>
@@ -2438,15 +2439,15 @@ public SELF isInSameDayAs(Date other) {
    * @since 3.19.0
    */
   public SELF isInSameDayAs(Instant other) {
-    dates.assertIsInSameDayAs(info, actual, Date.from(other));
+    dates.assertIsInSameDayAs(info, actual, dateFrom(other));
     return myself;
   }
 
   /**
    * Same assertion as {@link #isInSameDayAs(Date)} but given date is represented as String either with one of the
-   * supported defaults date format or a user custom date format (set with method {@link #withDateFormat(DateFormat)}).
+   * supported default date formats or a user custom date format (set with method {@link #withDateFormat(DateFormat)}).
    * <p>
-   * User custom date format take precedence over the default ones.
+   * User custom date format takes precedence over the default ones.
    * <p>
    * Unless specified otherwise, beware that the default formats are expressed in the current local timezone.
    * <p>
@@ -2555,16 +2556,16 @@ public SELF isInSameHourWindowAs(Date other) {
    * @since 3.19.0
    */
   public SELF isInSameHourWindowAs(Instant other) {
-    dates.assertIsInSameHourWindowAs(info, actual, Date.from(other));
+    dates.assertIsInSameHourWindowAs(info, actual, dateFrom(other));
     return myself;
   }
 
   /**
    * Same assertion as {@link #isInSameHourWindowAs(java.util.Date)} but given date is represented as String either
-   * with one of the supported defaults date format or a user custom date format (set with method
+   * with one of the supported default date formats or a user custom date format (set with method
    * {@link #withDateFormat(DateFormat)}).
    * <p>
-   * User custom date format take precedence over the default ones.
+   * User custom date format takes precedence over the default ones.
    * <p>
    * Unless specified otherwise, beware that the default formats are expressed in the current local timezone.
    * <p>
@@ -2642,9 +2643,9 @@ public SELF isInSameHourAs(Date other) {
 
   /**
    * Same assertion as {@link #isInSameHourAs(Date)} but given date is represented as String either with one of the
-   * supported defaults date format or a user custom date format (set with method {@link #withDateFormat(DateFormat)}).
+   * supported default date formats or a user custom date format (set with method {@link #withDateFormat(DateFormat)}).
    * <p>
-   * User custom date format take precedence over the default ones.
+   * User custom date format takes precedence over the default ones.
    * <p>
    * Unless specified otherwise, beware that the default formats are expressed in the current local timezone.
    * <p>
@@ -2755,16 +2756,16 @@ public SELF isInSameMinuteWindowAs(Date other) {
    * @since 3.19.0
    */
   public SELF isInSameMinuteWindowAs(Instant other) {
-    dates.assertIsInSameMinuteWindowAs(info, actual, Date.from(other));
+    dates.assertIsInSameMinuteWindowAs(info, actual, dateFrom(other));
     return myself;
   }
 
   /**
    * Same assertion as {@link #isInSameMinuteWindowAs(Date)} but given date is represented as String either with one of
-   * the supported defaults date format or a user custom date format (set with method
+   * the supported default date formats or a user custom date format (set with method
    * {@link #withDateFormat(DateFormat)}).
    * <p>
-   * User custom date format take precedence over the default ones.
+   * User custom date format takes precedence over the default ones.
    * <p>
    * Unless specified otherwise, beware that the default formats are expressed in the current local timezone.
    * <p>
@@ -2844,13 +2845,13 @@ public SELF isInSameMinuteAs(Date other) {
 
   /**
    * Same assertion as {@link #isInSameMinuteAs(Date)} but given date is represented as String either with one of the
-   * supported defaults date format or a user custom date format (set with method {@link #withDateFormat(DateFormat)}).
+   * supported default date formats or a user custom date format (set with method {@link #withDateFormat(DateFormat)}).
    * <p>
-   * User custom date format take precedence over the default ones.
+   * User custom date format takes precedence over the default ones.
    * <p>
    * Unless specified otherwise, beware that the default formats are expressed in the current local timezone.
    * <p>
-   * Defaults date format (expressed in the local time zone unless specified otherwise) are:
+   * Default date formats (expressed in the local time zone unless specified otherwise) are:
    * <ul>
    * <li><code>yyyy-MM-dd'T'HH:mm:ss.SSSX</code> (in ISO Time zone)</li>
    * <li><code>yyyy-MM-dd'T'HH:mm:ss.SSS</code></li>
@@ -2969,16 +2970,16 @@ public SELF isInSameSecondWindowAs(Date other) {
    * @since 3.19.0
    */
   public SELF isInSameSecondWindowAs(Instant other) {
-    dates.assertIsInSameSecondWindowAs(info, actual, Date.from(other));
+    dates.assertIsInSameSecondWindowAs(info, actual, dateFrom(other));
     return myself;
   }
 
   /**
    * Same assertion as {@link #isInSameSecondWindowAs(Date)} but given date is represented as String either with one of
-   * the supported defaults date format or a user custom date format (set with method
+   * the supported default date formats or a user custom date format (set with method
    * {@link #withDateFormat(DateFormat)}).
    * <p>
-   * User custom date format take precedence over the default ones.
+   * User custom date format takes precedence over the default ones.
    * <p>
    * Unless specified otherwise, beware that the default formats are expressed in the current local timezone.
    * <p>
@@ -3162,7 +3163,7 @@ public SELF isCloseTo(Date other, long deltaInMilliseconds) {
    * @since 3.19.0
    */
   public SELF isCloseTo(Instant other, long deltaInMilliseconds) {
-    dates.assertIsCloseTo(info, actual, Date.from(other), deltaInMilliseconds);
+    dates.assertIsCloseTo(info, actual, dateFrom(other), deltaInMilliseconds);
     return myself;
   }
 
@@ -3174,7 +3175,7 @@ public SELF isCloseTo(Instant other, long deltaInMilliseconds) {
    * <p>
    * Unless specified otherwise, beware that the default formats are expressed in the current local timezone.
    * <p>
-   * Defaults date format (expressed in the local time zone unless specified otherwise) are:
+   * Default date formats (expressed in the local time zone unless specified otherwise) are:
    * <ul>
    * <li><code>yyyy-MM-dd'T'HH:mm:ss.SSSX</code> (in ISO Time zone)</li>
    * <li><code>yyyy-MM-dd'T'HH:mm:ss.SSS</code></li>
@@ -3312,7 +3313,7 @@ public SELF hasSameTimeAs(String dateAsString) {
 
   /**
    * Instead of using default date formats for the date String based Date assertions like {@link #isEqualTo(String)},
-   * AssertJ is gonna use any date formats registered with one of these methods :
+   * AssertJ is going to use any date formats registered with one of these methods :
    * <ul>
    * <li>{@link #withDateFormat(String)}</li>
    * <li>this method</li>
@@ -3339,7 +3340,7 @@ public SELF withDateFormat(DateFormat userCustomDateFormat) {
 
   /**
    * Instead of using default date formats for the date String based Date assertions like {@link #isEqualTo(String)},
-   * AssertJ is gonna use any date formats registered with one of these methods :
+   * AssertJ is going to use any date formats registered with one of these methods :
    * <ul>
    * <li>this method</li>
    * <li>{@link #withDateFormat(java.text.DateFormat)}</li>
@@ -3368,7 +3369,7 @@ public SELF withDateFormat(String userCustomDateFormatPattern) {
    * Instead of using default strict date/time parsing, it is possible to use lenient parsing mode for default date
    * formats parser to interpret inputs that do not precisely match supported date formats (lenient parsing).
    * <p>
-   * With strict parsing, inputs must match exactly date/time format.
+   * With strict parsing, inputs must match exactly date/time formats.
    * <p>
    * Example:
    * <pre><code class='java'> final Date date = Dates.parse("2001-02-03");
@@ -3528,9 +3529,9 @@ public static void useDefaultDateFormatsOnly() {
   }
 
   /**
-   * Remove all registered custom date formats =&gt; use only the defaults date formats to parse string as date.
+   * Remove all registered custom date formats =&gt; use only the default date formats to parse string as date.
    * <p>
-   * User custom date format take precedence over the default ones.
+   * User custom date format takes precedence over the default ones.
    * <p>
    * Unless specified otherwise, beware that the default formats are expressed in the current local timezone.
    * <p>
@@ -3637,4 +3638,8 @@ public SELF usingDefaultComparator() {
     return super.usingDefaultComparator();
   }
 
+  private Date dateFrom(Instant instant) {
+    return actual instanceof Timestamp ? Timestamp.from(instant) : Date.from(instant);
+  }
+
 }
diff --git a/assertj-core/src/main/java/org/assertj/core/internal/StandardComparisonStrategy.java b/assertj-core/src/main/java/org/assertj/core/internal/StandardComparisonStrategy.java
--- a/assertj-core/src/main/java/org/assertj/core/internal/StandardComparisonStrategy.java
+++ b/assertj-core/src/main/java/org/assertj/core/internal/StandardComparisonStrategy.java
@@ -15,7 +15,6 @@
 import static org.assertj.core.util.Preconditions.checkArgument;
 
 import java.util.Collection;
-import java.util.Date;
 import java.util.Iterator;
 import java.util.Set;
 import java.util.TreeSet;
@@ -106,9 +105,6 @@ public boolean areEqual(Object actual, Object other) {
           return java.util.Arrays.deepEquals((Object[]) actual, (Object[]) other);
       }
     }
-
-    // use compareTo over equals as it deals correctly with java.sql.Timestamp
-    if (actual instanceof Date && other instanceof Date) return ((Date) actual).compareTo((Date) other) == 0;
     return actual.equals(other);
   }
 


Test patch:
diff --git a/assertj-core/src/test/java/org/assertj/core/api/date/DateAssert_isEqualTo_Test.java b/assertj-core/src/test/java/org/assertj/core/api/date/DateAssert_isEqualTo_Test.java
--- a/assertj-core/src/test/java/org/assertj/core/api/date/DateAssert_isEqualTo_Test.java
+++ b/assertj-core/src/test/java/org/assertj/core/api/date/DateAssert_isEqualTo_Test.java
@@ -12,42 +12,125 @@
  */
 package org.assertj.core.api.date;
 
-import static org.mockito.Mockito.verify;
+import static java.time.Instant.parse;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.BDDAssertions.then;
+import static org.assertj.core.util.AssertionsUtil.expectAssertionError;
+import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
 
+import java.sql.Timestamp;
 import java.time.Instant;
 import java.util.Date;
 
-import org.assertj.core.api.DateAssert;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.opentest4j.AssertionFailedError;
 
 /**
- * Tests for {@link DateAssert#isEqualTo(Date)} and {@link DateAssert#isEqualTo(String)}.
- *
  * @author Joel Costigliola
  */
-class DateAssert_isEqualTo_Test extends AbstractDateAssertWithDateArg_Test {
+class DateAssert_isEqualTo_Test {
 
-  @Override
-  protected DateAssert assertionInvocationWithDateArg() {
-    return assertions.isEqualTo(otherDate);
-  }
+  @Nested
+  @TestInstance(PER_CLASS)
+  class With_Object {
 
-  @Override
-  protected DateAssert assertionInvocationWithStringArg(String dateAsString) {
-    return assertions.isEqualTo(dateAsString);
-  }
+    @ParameterizedTest
+    @MethodSource
+    void should_pass(Date actual, Object expected) {
+      // WHEN/THEN
+      assertThat(actual).isEqualTo(expected);
+    }
+
+    Arguments[] should_pass() {
+      return new Arguments[] {
+          arguments(Date.from(parse("1970-01-01T00:00:00.000000001Z")),
+                    Date.from(parse("1970-01-01T00:00:00.000000001Z"))),
+          arguments(Date.from(parse("1970-01-01T00:00:00.000000001Z")),
+                    Timestamp.from(parse("1970-01-01T00:00:00.000000001Z")))
+      };
+    }
+
+    @ParameterizedTest
+    @MethodSource
+    void should_fail(Date actual, Object expected) {
+      // WHEN
+      AssertionError error = expectAssertionError(() -> assertThat(actual).isEqualTo(expected));
+      // THEN
+      then(error).isInstanceOf(AssertionFailedError.class);
+    }
+
+    Arguments[] should_fail() {
+      return new Arguments[] {
+          arguments(Timestamp.from(parse("1970-01-01T00:00:00.000000001Z")),
+                    Date.from(parse("1970-01-01T00:00:00.000000001Z")))
+      };
+    }
 
-  protected DateAssert assertionInvocationWithInstantArg(Instant instant) {
-    return assertions.isEqualTo(instant);
   }
 
-  @Override
-  protected void verifyAssertionInvocation(Date date) {
-    verify(objects).assertEqual(getInfo(assertions), getActual(assertions), date);
+  @Nested
+  @TestInstance(PER_CLASS)
+  class With_Instant {
+
+    @ParameterizedTest
+    @MethodSource
+    void should_pass(Date actual, Instant expected) {
+      // WHEN/THEN
+      assertThat(actual).isEqualTo(expected);
+    }
+
+    Arguments[] should_pass() {
+      return new Arguments[] {
+          arguments(Date.from(parse("1970-01-01T00:00:00.000000001Z")),
+                    parse("1970-01-01T00:00:00.000000001Z")),
+          arguments(Timestamp.from(parse("1970-01-01T00:00:00.000000001Z")),
+                    parse("1970-01-01T00:00:00.000000001Z"))
+      };
+    }
+
   }
 
-  @Override
-  protected DateAssert assertionInvocationWithInstantArg() {
-    return assertions.isEqualTo(otherDate.toInstant());
+  @Nested
+  @TestInstance(PER_CLASS)
+  class With_String {
+
+    @ParameterizedTest
+    @MethodSource
+    void should_pass(Date actual, String expected) {
+      // WHEN/THEN
+      assertThat(actual).isEqualTo(expected);
+    }
+
+    Arguments[] should_pass() {
+      return new Arguments[] {
+          arguments(Date.from(parse("1970-01-01T00:00:00.000000001Z")),
+                    "1970-01-01T00:00:00.000Z")
+      };
+    }
+
+    @ParameterizedTest
+    @MethodSource
+    void should_fail(Date actual, String expected) {
+      // WHEN
+      AssertionError error = expectAssertionError(() -> assertThat(actual).isEqualTo(expected));
+      // THEN
+      then(error).isInstanceOf(AssertionFailedError.class);
+    }
+
+    Arguments[] should_fail() {
+      return new Arguments[] {
+          arguments(Date.from(parse("1970-01-01T00:00:00.000000001Z")),
+                    "1970-01-01T00:00:00.000000001Z"),
+          arguments(Timestamp.from(parse("1970-01-01T00:00:00.000000001Z")),
+                    "1970-01-01T00:00:00.000000001Z")
+      };
+    }
+
   }
 
 }
diff --git a/assertj-core/src/test/java/org/assertj/core/internal/StandardComparisonStrategy_areEqual_Test.java b/assertj-core/src/test/java/org/assertj/core/internal/StandardComparisonStrategy_areEqual_Test.java
--- a/assertj-core/src/test/java/org/assertj/core/internal/StandardComparisonStrategy_areEqual_Test.java
+++ b/assertj-core/src/test/java/org/assertj/core/internal/StandardComparisonStrategy_areEqual_Test.java
@@ -16,9 +16,6 @@
 import static org.assertj.core.api.BDDAssertions.then;
 import static org.junit.jupiter.params.provider.Arguments.arguments;
 
-import java.sql.Timestamp;
-import java.time.Instant;
-import java.util.Date;
 import java.util.stream.Stream;
 
 import org.junit.jupiter.api.Test;
@@ -258,29 +255,6 @@ void should_return_false_if_array_is_non_null_and_other_is_null(Object actual) {
     then(result).isFalse();
   }
 
-  @Test
-  void should_return_true_if_date_and_timestamp_are_equal() {
-    // GIVEN
-    Instant now = Instant.now();
-    Date actual = Date.from(now);
-    Timestamp other = Timestamp.from(now);
-    // WHEN
-    boolean result = underTest.areEqual(actual, other);
-    // THEN
-    then(result).isTrue();
-  }
-
-  @Test
-  void should_return_false_if_dates_are_not_equal() {
-    // GIVEN
-    Date actual = Date.from(Instant.parse("2024-03-30T00:00:00.00Z"));
-    Timestamp other = Timestamp.from(Instant.parse("2024-03-30T00:00:00.01Z"));
-    // WHEN
-    boolean result = underTest.areEqual(actual, other);
-    // THEN
-    then(result).isFalse();
-  }
-
   private static Stream<Object> arrays() {
     return Stream.of(argument(new Object[] { "Luke", "Yoda", "Leia" }),
                      new byte[] { 1, 2, 3 },