Reputation: 5455
If I use java.util.Date
's toInstant()
on a variable which happens to be a java.sql.Date
, I get an UnsupportedOperationException
.
try {
java.util.Date input = new java.sql.Date(System.currentTimeMillis());
LocalDate date = input.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
} catch (UnsupportedOperationException e) {
// grrr!
}
The java.util.Date
that I'm concerned with comes from a DATE field in a mysql DB via a legacy API, and is actually a java.sql.Date
.
Now the following related questions are all very interesting:
UnsupportedOperationException - Why can't you call toInstant() on a java.sql.Date?
Convert java.util.Date to java.time.LocalDate
LocalDate to java.util.Date and vice versa simplest conversion?
but they don't provide any elegant way of truncating a java.util.Date
to get rid of the time component and get a Java 8 LocalDate
.
I admit there is an issue that the same instant in time in one timezone might be a different date from the same instant in another timezone.
I suspect the solution will involve java.util.Calendar but rather than craft my own solution I'd rather establish what others have done first.
I'd prefer to find something shorter than this:
from Resetting the time part of a timestamp in Java :
Date date = new Date(); // timestamp now Calendar cal = Calendar.getInstance(); // get calendar instance cal.setTime(date); // set cal to date cal.set(Calendar.HOUR_OF_DAY, 0); // set hour to midnight cal.set(Calendar.MINUTE, 0); // set minute in hour cal.set(Calendar.SECOND, 0); // set second in minute cal.set(Calendar.MILLISECOND, 0); // set millis in second Date zeroedDate = cal.getTime(); // actually computes the new Date
Upvotes: 15
Views: 12837
Reputation: 786
I don't no why but this code work for me :)
LocalDate ld =new java.util.Date(personEntity.getBirthday().getTime()).toInstant() .atZone(ZoneId.systemDefault()).toLocalDate();
and this do not work :
LocalDate ld =personEntity.getBirthday().getTime().toInstant() .atZone(ZoneId.systemDefault()).toLocalDate();
Upvotes: 2
Reputation: 340230
Your premise of crossing over between java.sql.Date
and java.util.Date
is incorrect.
If I use java.util.Date … on a variable which happens to be a java.sql.Date
You violated the contract laid out in the class documentation. You were told to ignore the fact that java.sql.Date
is a subclass of java.util.Date
.
But the solution may be quite simple.
ZonedDateTime zdt = myJavaSqlDate.toLocalDate().atStartOfDay( ZoneId.of( "America/Montreal" ) ) ;
Apparently you have in hand a java.sql.Date
. That class represents a date-only value without time-of-day and without time zone. At least that is the intention of that class. A tragically poor design choice was made to subclass from java.util.Date
which, despite the name, represents a date and a time-of-day in UTC. This inheritance relationship is a hack, a bad hack, where they set the internal time-of-day to "00:00:00". The documentation clearly instructs us to ignore this inheritance relationship, and ignore the fact of having a time-of-day component, and pretend that the two Date
classes are unrelated. But many people such as the author do not carefully read that documentation and makes gross assumptions based on the class names.
Both Date
classes are part of the awful mess that is the date-time framework shipped with the earliest versions of Java. These have been supplanted by the java.time classes built into Java 8 and later.
First step when encountering a legacy date-time object: Convert to java.time using new methods added to the old classes.
Convert our fake date-only object java.sql.Date
to a true date-only object of type java.util.LocalDate
. The “Local” word indicates no time zone or offset-from-UTC.
LocalDate ld = myJavaSqlDate.toLocalDate() ;
Apparently you want to transform that date-only value to a date with time of day. Perhaps you want the first moment of the day. Determining the first moment requires a time zone. For example, a new day dawns earlier in Europe/Paris
than in America/Montreal
, and still earlier in Asia/Kolkata
.
ZoneId z = ZoneId.of( "Pacific/Auckland" ) ;
ZonedDateTime zdt = ld.atStartOfDay( z ) ;
Upvotes: 0
Reputation: 117
Try this.
java.util.Date input = new java.sql.Date(System.currentTimeMillis());
Instant instant = Instant.ofEpochMilli(input.getTime());
LocalDate date = instant .atZone(ZoneId.systemDefault()).toLocalDate();
Upvotes: 1
Reputation: 5455
Often the simplest solutions are the hardest to find:
public LocalDate convertDateObject(java.util.Date suspectDate) {
try {
// Don't do this if there is the smallest chance
// it could be a java.sql.Date!
return suspectDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
} catch (UnsupportedOperationException e) {
// BOOM!!
}
// do this first:
java.util.Date safeDate = new Date(suspectDate.getTime());
return safeDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
}
Upvotes: 18
Reputation: 340230
myResultSet.getObject( … , LocalDate.class )
Your troubles started when using the legacy date-time classes such as java.sql.Date
, java.util.Date
, and Calendar
. Avoid these classes entirely. They are now legacy, supplanted by the java.time classes.
You said the value began as a stored value in MySQL column of type DATE
. That type is date-only, without a time-of-day. So you introduced a time-of-day value needlessly by using the wrong classes.
Use a JDBC driver that complies with JDBC 4.2 or later to exchange values with the database using the java.time classes.
LocalDate ld = myResultSet.getObject( … , LocalDate.class ) ;
And pass to a PreparedStatement
.
myPstmt.setObject( … , myLocalDate ) ;
Upvotes: 2
Reputation:
If the input
variable is known to be a java.sql.Date
, then you can simply cast it and call the toLocalDate()
method:
LocalDate date = ((java.sql.Date) input).toLocalDate();
Unfortunately, you can't call toInstant()
on a java.sql.Date
, because according to javadoc, it always throws an UnsupportedOperationException
.
If you don't know the type (it can be either a java.util.Date
or a java.sql.Date
), you can use the value returned by getTime()
method to build an Instant
, then convert it to a timezone (below I'm using the JVM's default) and finally get the local date from it:
LocalDate date = Instant
// get the millis value to build the Instant
.ofEpochMilli(input.getTime())
// convert to JVM default timezone
.atZone(ZoneId.systemDefault())
// convert to LocalDate
.toLocalDate();
The toLocalDate()
method gets the date part (day/month/year), ignoring the rest, so there's no need to truncate it: it doesn't matter if the time is midnight, 10 AM, or any other time of the day, toLocalDate()
will ignore it and get just the date part.
If you really want to set the time to midnight, though, you can use the with
method and pass a LocalTime
to it:
LocalDate date = Instant
// get the millis value to build the Instant
.ofEpochMilli(input.getTime())
// convert to JVM default timezone
.atZone(ZoneId.systemDefault())
// set time to midnight
.with(LocalTime.MIDNIGHT)
// convert to LocalDate
.toLocalDate();
But as I said, the toLocalDate()
method will just ignore the time part, so setting the time is not needed in this case (the LocalDate
will be the same).
You could also check the date's type and choose the proper action accordingly, like this:
if (input instanceof java.sql.Date) {
date = ((java.sql.Date) input).toLocalDate();
} else {
date = input.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
}
Instead of using the JVM default timezone (ZoneId.systemDefault()
), you can use any other timezone, according to your needs, by calling ZoneId.of("zoneName")
, where the zone name is any of the valid IANA timezones names (always in the format Region/City
, like America/New_York
or Europe/London
).
Avoid using the 3-letter abbreviations (like CET
or PST
) because they are ambiguous and not standard.
You can get a list of available timezones (and choose the one that fits best your system) by calling ZoneId.getAvailableZoneIds()
. You can also keep using the JVM default timezone if you want, but remind that it can be changed without notice, even at runtime, so it's better to always make it explicit which one you're using.
Upvotes: 8
Reputation: 9444
Did you try to use an SimpleDateFormat to help you?
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Date;
public class Utils {
private static SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd" );
public static void main( String[] args ) {
LocalDate ld = sqlDateToLocalDate( new java.sql.Date( System.currentTimeMillis() ) );
System.out.println( ld );
}
public static LocalDate sqlDateToLocalDate( java.sql.Date sqlDate ) {
try {
Date d = sdf.parse( sdf.format( sqlDate ) );
return d.toInstant().atZone( ZoneId.systemDefault() ).toLocalDate();
} catch ( ParseException exc ) {
}
return null;
}
}
Upvotes: 0