UnsupportedOperationException - Why can't you call toInstant() on a java.sql.Date?
Asked Answered
X

6

81

The java.util.Date class has a method called toInstant() that converts the Date instance to a java.time.Instant.

The java.sql.Date class extends the java.util.Date class, but when I attempt to call toInstant() on a java.sql.Date, I receive an UnsupportedOperationException.

Why is toInstant() an unsupported operation on java.sql.Date?

And what is the "correct" way to convert a java.sql.Date to a java.time.Instant?

Xerophyte answered 5/4, 2016 at 19:35 Comment(0)
M
42

According to the JavaDoc

Since sql.Date does not have a time component, there is no possibility to convert it to time.Instant

This method always throws an UnsupportedOperationException and should not be used because SQL Date values do not have a time component.

Marou answered 5/4, 2016 at 19:38 Comment(4)
Now, the question is why it was designed this way. One could imagine it defaults with time info set to 0. Can't find anything in the mailing list.Verify
@Verify The logic makes me think it is because the parent class util.Date has a public toInstant method that should not be used by the child class. So instead of making it final (to keep the Inheritance logic), they preferred the option of overriding it and making it unusable.Marou
@Verify probably because, as you point out, any conversion into an Instant would require setting some data arbitrarily (time, time zone).Steger
But Instant using the same logic that java.util.Date uses would produce correct and logical results, while avoiding violating the polymorphism contract. Oracle has made a very headscratching choice, there.Grovel
S
64

The correct mapping between java.sql.Date and java.time is LocalDate:

LocalDate date = sqlDate.toLocalDate();

If you really must, you can then derive an Instant, although the extra information (time) will be arbitrary. For example:

Instant i = date.atStartOfDay(ZoneOffset.UTC).toInstant();
Steger answered 6/4, 2016 at 7:22 Comment(6)
Wish I could accept two answers... This answers the second part of my question perfectly, but the main question was why you can't call toInstant() on a java.sql.Date, so I had to accept @YassinHajaj's answer. This answer needs more upvotes.Xerophyte
Since date is a LocalDate (local timezone), shouldn't the conversion to Instant (which is UTC) specify the local timezone?: Instant i = date.atStartOfDay(ZoneId.systemDefault()).toInstant()Auxochrome
LocalDate date = sqlDate.toLocalDate(); is not performableFossa
@YannickMussche not sure what you mean: docs.oracle.com/en/java/javase/17/docs/api/java.sql/java/sql/…Steger
try it in your IDE, it doesn't work.Fossa
@YannickMussche ideone.com/4G8O06Steger
M
42

According to the JavaDoc

Since sql.Date does not have a time component, there is no possibility to convert it to time.Instant

This method always throws an UnsupportedOperationException and should not be used because SQL Date values do not have a time component.

Marou answered 5/4, 2016 at 19:38 Comment(4)
Now, the question is why it was designed this way. One could imagine it defaults with time info set to 0. Can't find anything in the mailing list.Verify
@Verify The logic makes me think it is because the parent class util.Date has a public toInstant method that should not be used by the child class. So instead of making it final (to keep the Inheritance logic), they preferred the option of overriding it and making it unusable.Marou
@Verify probably because, as you point out, any conversion into an Instant would require setting some data arbitrarily (time, time zone).Steger
But Instant using the same logic that java.util.Date uses would produce correct and logical results, while avoiding violating the polymorphism contract. Oracle has made a very headscratching choice, there.Grovel
T
38

java.sql.Date supports only Date components (date, month, year). It does NOT support time components (hour, minute, second, millisecond). toInstant requires both Date and Time components, so toInstant on java.sql.Date instance throws UnsupportedOperationException exception.

toInstant Java doc

This method always throws an UnsupportedOperationException and should not be used because SQL Date values do not have a time component.

java.util.Date OR java.sql.Timestamp has both Date/Time components, so toInstant() works!

You can do like this:

// Time is 00:00:00.000

new java.util.Date(sqlDate.getTime()).toInstant() 

Updated:

Instant.ofEpochMilli(sqlDate.getTime());

// and
new java.util.Date(sqlDate.getTime()).toInstant();

will return the same result because the toInstant() method calls Instant.ofEpochMilli(getTime()) internally.

public Instant toInstant() {
    return Instant.ofEpochMilli(getTime());
}
Triazine answered 5/4, 2016 at 19:39 Comment(3)
Which is more "correct"? Instant.ofEpochMilli(sqlDate.getTime()); or new java.util.Date(sqlDate.getTime()).toInstant();Xerophyte
You can’t convert a java.sql.Date into an Instant without supplying a timezone. Calling getTime() at an java.sql.Date is meaningless. As pointed out in other comments, java.sql.Date is only a LocalDate and doesn’t represent an instant in time.Madelynmademoiselle
I didn't call toInstant on java sql date. I did call toInstant on java util date and it is ok for the case.Triazine
I
20

The answers given so far until now concentrate on the detail that java.sql.Date has no time information. That is correct but not the real or sufficient reason why this type cannot offer a direct conversion to Instant. Unfortunatly the documentation of Java-8 does make the same mistake to let users think the problem is just because of missing time information.

Conceptually, the type java.sql.Date represents a local type. It models a calendar date which can be different in any region of our globe. But an Instant is the same on our globe around. So users need a timezone or a timezone offset to do the conversion.

Tragically, the type java.sql.Date inherits from java.util.Date which is a global type (instant-like). However, this inheritance really denotes implementation inheritance, and not type inheritance. One more reason to consider the design of these old JDBC-classes to be broken. Therefore it is indeed possible to use the hack to wrap the java.sql.Date via its method getTime() inside an instance of java.util.Date which finally allows direct conversion to an instant. But: This conversion implicitly uses the default timezone of the system.

So how to correctly convert in a pedantic way? Let's consider the documentation of Java-8 again which here points into the right direction:

java.sql.Date sqlDate = ...;
LocalDate calendarDate = sqlDate.toLocalDate();
ZonedDateTime zdt = calendarDate.atStartOfDay(ZoneId.of("Europe/Paris"));
Instant instant = zdt.toInstant();
Intercollegiate answered 6/4, 2016 at 7:17 Comment(0)
C
6

If you don't have time in your Date - convert it to millis:

Instant.ofEpochMilli(date.getTime())
   .atZone(ZoneId.systemDefault())
   .toLocalDate();
Casebound answered 31/3, 2021 at 19:18 Comment(0)
G
3

The implementation of java.sql.Date (imho) is really not perfect.

In order to convert java.util.Date to LocalDate the web is full of code blocks like this:

dateToConvert.toInstant()
  .atZone(ZoneId.systemDefault())
  .toLocalDate();
  • above code works perfect for java.util.Date because of the way it implements toInstant(). (As expected " return Instant.ofEpochMilli(getTime())")
  • As java.sql.Date is a subclass of java.util.Date code operating on the Date object might now know it operates on a java.sql.Date. (Hey - that's one aspect of object orientation, right??). So if it gets java.util.Date code works well, if it gets java.sql.Date it fails.
  • Code could now explicitly check for the Date type, and then (if it operates on java.sql.Date) it could do a downcast to java.sql.Date and use the toLocalDate() method. Needless to say, that this is ugly.

As developer I'd like to have one method working properly - toLocalDate (internally using deprectated methods) only exists for java.sql.Date so cannot be used on java.util.Date, and toInstant fails on java.sql.Date.

I changed my "Date to LocalDate" conversion code to this:

public static LocalDate asLocalDate(java.util.Date date) {
    return date == null ? null : LocalDate.ofInstant(Instant.ofEpochMilli(date.getTime()), ZoneId.systemDefault());
}

This works independent of the date type passed in. Basically it means "dont call java.util.Date.toInstant() as you might get a java.sql.Date, which then breaks your code.

I really don't understand why they implement such traps on purpose within the JDK.

Gamboa answered 12/4, 2022 at 9:58 Comment(1)
It's a very head-scratching choice on the part of the original designers, indeed. There was no reason to break the polymorphic contract.Grovel

© 2022 - 2024 — McMap. All rights reserved.