I sometimes get confused by the different Date types in java and their practical usage. Here
i am trying to summarize my understanding
java.sql.Date :- A thin wrapper around a millisecond value that allows JDBC to identify this as an SQL DATE value
java.sql.Timestamp :- A thin wrapper around java.util.Date that allows the JDBC API to identify this as an SQL
TIMESTAMP value. It adds the ability to hold the SQL TIMESTAMP fractional seconds value, by allowing the specification
of fractional seconds to a precision of nanoseconds
I have seen most of the projects prefer Timestamp instead of date. I think the main reason for this is that Timestamp can hold the value till
nano seconds precision whereas Data can hold till milli seconds. Correct?
Calendar :- This class is designed for date manipulation for example :- for converting between a specific instant in time and a
set of calendar fields such as YEAR, MONTH, DAY_OF_MONTH, HOUR, and so on, and for manipulating the calendar fields, such as getting
the date of the next week.Though i dont know why this class is abstract when only one implementation exists i.e GregorianCalendar.
java.sql.Timestamp A thin wrapper around java.util.Date that allows the JDBC API to identify this as an SQL TIMESTAMP value.
If you check java.sql.Timestamp JavaDoc, it is very explicit that this class extends from java.util.Date (as java.sql.Date does). And in real world projects you must plain java.util.Date when storing the data in your database and mostly java.sql.Timestamp since it stores date and time value, while java.sql.Date just stores date value.
On the other hand, java.util.Calendar is abstract since there are more implementations of this apart from java.util.GregorianCalendar. If you see the code of Calendar#getInstance from HotSpot, you will see that it calls createCalendar(TimeZone.getDefaultRef(), Locale.getDefault(Locale.Category.FORMAT)), and this method code uses 3 different calendars: BuddhistCalendar, JapaneseImperialCalendar and GregorianCalendar. This code is copied from JDK 7 source:
private static Calendar createCalendar(TimeZone zone,
Locale aLocale) {
Calendar cal = null;
String caltype = aLocale.getUnicodeLocaleType("ca");
if (caltype == null) {
// Calendar type is not specified.
// If the specified locale is a Thai locale,
// returns a BuddhistCalendar instance.
if ("th".equals(aLocale.getLanguage())
&& ("TH".equals(aLocale.getCountry()))) {
cal = new BuddhistCalendar(zone, aLocale);
} else {
cal = new GregorianCalendar(zone, aLocale);
}
} else if (caltype.equals("japanese")) {
cal = new JapaneseImperialCalendar(zone, aLocale);
} else if (caltype.equals("buddhist")) {
cal = new BuddhistCalendar(zone, aLocale);
} else {
// Unsupported calendar type.
// Use Gregorian calendar as a fallback.
cal = new GregorianCalendar(zone, aLocale);
}
return cal;
}
Now, why to work directly with Calendar instead of GregorianCalendar? Because you must work with abstract classes and interfaces when provided instead of working directly with implementations. This is better explained here: What does it mean to "program to an interface"?
Apart from this, if you will work with date and times, I recommend using a library like Joda-Time that already handles and solves lot of the problems with the current Java Date API and also provides methods to retrieve this date and times object in java.util.Date flavor.
java.time
You must first understand that those old date-time classes bundled with early versions of Java are a confusing mess of badly designed classes with hacks. They were the industry's first attempt at a sophisticated facility for date-time handling, and deserve credit for that. But ultimately they failed.
They have been supplanted by the new java.time framework built into Java 8 and later.
java.sql.Date — Use java.time.LocalDate instead
java.sql.Timestamp — Use java.time.Instant instead
java.util.Calendar & GregorianCalendar — Use java.time.ZonedDateTime instead
For date only, without time-of-day nor time zone, use java.time.LocalDate. For a moment on the timeline in UTC, use java.time.Instant. To assign a different time zone to an Instant, use java.time.ZonedDateTime.
Understand that an offset-from-UTC is merely a number of hours and minutes ahead of, or behind, UTC. A time zone is a history of past, present, and future changes to the offset used by the people of a certain region.
If you have a date-time value that has an offset-from-UTC rather than a time zone, represent that with the OffsetDateTime class. Then call its toInstant method to obtain a Instant object to be sent to your database in a column of type akin to the SQL-standard TIMESTAMP WITH TIME ZONE.
The SQL-standard type TIMESTAMP WITHOUT TIME ZONE (without, not with) purposely lacks any concept of time zone or offset-from-UTC. The legacy date-time classes had no way to represent such a value. Now, in java.time, we have LocalDateTime.
If your JDBC driver complies with JDBC 4.2 or later, you can directly exchange java.time objects with your database. No need to ever use the java.sql date-item types again.
myPreparedStatement.setObject( … , instant ) ;
And retrieval.
Instant instant = myResultSet.getObject( … , Instant.class ) ;
Adjust into the wall-clock time used by the people of a particular region (a time zone).
ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, Java SE 10, and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
Later versions of Android bundle implementations of the java.time classes.
For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
The first line in the original question contains the phrase " the different Date types in java and their practical usage"
The practical usage of the timestamp data type is exactly as it says - an timestamp used by the SQL to record a precise chronological value typically used for transactional ordering. Timestamps are usually only used internally... where nanoseconds count. There are use cases for external timestamp data, but they are relatively rare.
The date type handles 99% of external nonscientific data needs with millisecond precision.
Related
This should be quite simple
customerSearch.get("DateOfBirth") where customerSearch is a HashMap<String, Object> and it's a Gregorian Date.
But I need a normal Date object.
I tried
Date dateOfBirth = new Date(
((GregorianCalendar) customerSearch.get("DateOfBirth")).getTimeInMillis());
but said Gregorian Date cannot be cast to Date.
Any help?
tl;dr
( (GregorianCalendar) customerSearch.get( "DateOfBirth" ) ) // Retrieve and cast object of legacy class `GregorianCalendar`.
.toZonedDateTime() // Convert from terrible legacy class to modern *java.time* class.
.toLocalDate() // Extract the date-only portion, omitting time-of-day and time zone.
java.time
You are using, and possibly abusing, terrible date-time classes that were supplanted years ago by the java.time classes defined by JSR 310.
ZonedDateTime replaces GregorianCalendar
Instant replaces java.util.Date
LocalDate replaces java.sql.Date
If you must interoperate with old code not yet updated to java.time, you can convert back-and-forth. Call new conversion methods added to the old classes.
GregorianCalendar gc = customerSearch… ;
ZonedDateTime zdt = gt.toZonedDateTime() ;
If you want just the date without the time-of-day and without the time zone, extract a LocalDate.
LocalDate dateOfBirth = zdt.toLocalDate() ;
If by Date you meant, java.sql.Date class, the above line should be used instead making use of LocalDate.
If by Date you meant java.util.Date, that would be the wrong class to use there. That class represents a moment in UTC. By adjusting from the zoned moment in GregorianCalendar/ZonedDateTime to UTC, you could be changing the date.
Converting
If you need a java.util.Date object to interoperate with old code not yet updated for java.time, you can convert. A java.util.Date represents a moment in UTC, so we need the equivalent in java.time, Instant, also a moment in UTC but with a finer resolution of nanoseconds rather than milliseconds.
We can extract an Instant from our ZonedDateTime seen above. Effectively, this is simply adjusting from a zoned value to a UTC value. Same moment, same point on the timeline, different wall-clock time.
Instant instant = zdt.toInstant() ; // Extract a `Instant`, adjusting from zone to UTC.
Use new conversion methods on the old class.
java.util.Date myJavaUtilDate = java.util.Date.from( instant ) ; // Beware of possible data-loss in the fractional second, truncating from nanos to millis.
If you need go the other direction, from legacy to modern.
Instant instant = myJavaUtilDate.toInstant() ;
Use appropriate types
If you are trying to represent a date-of-birth is is commonly done, with just a date without time-of-day and without zone, then use LocalDate in your map. The classes GregorianCalendar, java.util.Date, and ZonedDateTime all represent a moment, a specific point on the timeline. They should never be used for a date-only.
The java.sql.Date class pretends to be a date-only, but through a terrible design decision as a subclass of java.util.Date actually does indeed carry a time-of-day and offset/zone. This class, as noted above, has been replaced with LocalDate.
normal Date object
There is nothing more “normal” about either java.util.Date or java.sql.Date than any other date-time class.
java.util.Date represents a moment in UTC, always UTC, despite what its terribly designed toString method tells you (a lie). Use java.time.Instant instead when you need a moment in UTC.
java.sql.Date is meant to represent a date-only, but as discussed, actually carries internally a time and zone/offset. Use java.time.LocalDate instead to represent a date rather than a moment.
GregorianCalendar represents a moment as seen in the wall-clock time used by the people of a specific region (a time zone). Instead use ZonedDateTime for this purpose.
java.sql.Timestamp represents a moment in UTC but with a finer resolution of nanoseconds rather than milliseconds. All the moment-related classes in java.time use a resolution of nanoseconds. So for a moment in UTC with a resolution of UTC, use java.time.Instant instead of Timestamp.
As of JDBC 4.2, we can directly exchange java.time classes with a database. So no need to ever touch java.sql.Date or java.sql.Timestamp again for your database work.
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, Java SE 10, Java SE 11, and later - Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Most of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
Later versions of Android bundle implementations of the java.time classes.
For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
There is no java class GregorianDate. Unless it is your own class which if so you didn't mention. So, just in case is Gregorian Date just a regular Date as most of the world today uses Gregorian calendar? So instances of what class does your map holds in your HashMap<String, Object>? If it is just a regular Date then your code should be just:
Date dateOfBirth = (Date)(customerSearch.get("DateOfBirth"));
I had a program where I compared two dates with each other; even though date1 was before date2, date1.after(date2) returned true. Time zones had no effect; both dates were in UTC.
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
public class Test {
public static void main(String[] args) throws Exception {
TimeZone.setDefault(TimeZone.getTimeZone("Etc/UTC"));
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
Date date1 = dateFormat.parse("2018-07-27 01:22:14.077");
Date date2 = new Timestamp(1532654534390l);
System.out.println(dateFormat.format(date1));
System.out.println(dateFormat.format(date2));
System.out.println(date1.getTime() > date2.getTime());
System.out.println(date1.after(date2));
}
}
This outputs:
2018-07-27 01:22:14.077
2018-07-27 01:22:14.390
false
true
What is happening here?
In my real program, date1 is parsed from a log file and date2 is retrieved from the database by Hibernate, which causes the different data types. Even though I found the root cause and know how to avoid the problem, I'm still very interested in solutions which prevent this pitfall.
The underlying 'problem' here is that java.sql.Timestamp, while extending java.util.Date, doesn't store milliseconds in the designated field (fastTime, equivalent to Unix time), but in a separate field nanos. The after method only considers the fastTime field (which makes sense, since it can be used on all Date objects).
What happens in this situation is that the fastTime of the Timestamp is rounded down from 1532654534390 to 1532654534000, which is lower than the 1532654534077 of the other date (and lower means an earlier date). Therefore, after() and before() are not reliable in this case; the solution is to use getTime() (which is overloaded for Timestamp to provide the correct value) on both dates and compare those.
tl;dr
Use modern java.time classes, never terrible legacy date-time classes.
Instant
.ofEpochMilli( 1_532_654_534_390L )
.isAfter(
LocalDateTime
.parse(
"2018-07-27 01:22:14.077"
.replace( " " , "T" )
)
.atOffset(
ZoneOffset.UTC
)
.toInstant()
)
Doc says: Don’t use Timestamp object as Date
Your code:
Date date2 = new Timestamp… // Violates class documentation.
…violates the contract established in the class documentation.
Due to the differences between the Timestamp class and the java.util.Date class mentioned above, it is recommended that code not view Timestamp values generically as an instance of java.util.Date. The inheritance relationship between Timestamp and java.util.Date really denotes implementation inheritance, and not type inheritance.
The doc notes that while java.sql.Timestamp technically inherits from java.util.Date, you are instructed to ignore that fact of inheritance. You are not to use Timestamp object as a Date. Your code is doing exactly what the doc told you not to do.
Of course this pretend-it-is-not-a-subclass policy is a ridiculously bad class design. This hack is one of many reasons to never use these classes.
The behavior you saw, regarding mismatch of milliseconds fractional-second and nanoseconds, is documented:
Note: This type is a composite of a java.util.Date and a separate nanoseconds value. Only integral seconds are stored in the java.util.Date component. The fractional seconds - the nanos - are separate. The Timestamp.equals(Object) method never returns true when passed an object that isn't an instance of java.sql.Timestamp, because the nanos component of a date is unknown. As a result, the Timestamp.equals(Object) method is not symmetric with respect to the java.util.Date.equals(Object) method. Also, the hashCode method uses the underlying java.util.Date implementation and therefore does not include nanos in its computation.
java.time
You are using notoriously terrible classes. The problem you found is due to their awful design that used bad hacks. Do not bother trying to understand these classes; just avoid them entirely.
These legacy classes were supplanted years ago by the java.time classes.
Parse your input string.
LocalDateTime ldt = LocalDateTime.parse( "2018-07-27 01:22:14.077".replace( " " , "T" ) ; // Without a time zone or offset, this value has no specific meaning, is *not* a point on the timeline.
Apparently you know for a fact that input string was implicitly representing a moment in UTC.
OffsetDateTime odt = ldt.atOffset( ZoneOffset.UTC ) ; // Assign an offset-from-UTC to give the date and time a meaning as an actual point on the timeline.
Parse the other input, apparently a count of milliseconds since the first moment of 1970 in UTC. The Instant class is more basic than OffsetDateTime, a moment in UTC, always UTC by definition.
Instant instant = Instant.ofEpochMilli( 1_532_654_534_390L ) ; // Translate a count of milliseconds from 1970-01-01T00:00:00Z into a moment on the timeline in UTC.
Compare.
Boolean stringIsAfterLong = odt.toInstant().isAfter( instant ) ;
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, Java SE 10, Java SE 11, and later - Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Most of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
Later versions of Android bundle implementations of the java.time classes.
For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
We receive a GregorianCalendar object from a 3rd party library. We need to turn that into a Date for use in another 3rd party library. And we're on Java 1.6 so we don't have the new time/instant classes available.
The problem is calling Calendar.getTime() gives a different date, offset by (I think) our timezone. So the next day by 8 hours.
How can we do this without this shift?
Update: We get the date from an OData call. The date being returned is an employee birthdate (Northwind) and therefore shouldn't have a time. But it's returned as a GregorianCalendar object with a time of 1992-05-01 00:00:00. GMT timezone it appears.
And the getTime() is returning a Date of "Thu Apr 30 18:00:00 MDT 1992" - I'm in the Mountain Time Zone.
The problem is I need to get from the calendar object a Date object of 1992-05-01, not 1992-04-30. And preferably with the time offset matching too.
Get get the Date value in your default time zone, call setTimeZone().
GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
cal.clear();
cal.set(1992,4,1); // 1992-05-01 00:00:00 GMT
// "Fix" time zone
cal.setTimeZone(TimeZone.getDefault());
System.out.println(cal.getTime());
Output
Fri May 01 00:00:00 EDT 1992
tl;dr
No shift
java.util.Date date = myGregorianCalendar.getTime() ; // Same moment, same point on the timeline. `Date` is in UTC, `GregorianCalendar` may be in some other time zone.
String output = date.toString() ; // This new string is a lie, dynamically applying the JVM’s current time zone while the `Date` is actually in UTC, always, by definition.
There is no shift. Calling GregorianCalendar.getTime produced a java.util.Date. The Date object is always in UTC, by definition. Unfortunately the Date::toString method lies, injecting the JVM’s current default time zone while producing a String.
Be clear that the Date and String are two separate distinct objects. One holds a moment in UTC, the other is a textual representation of that moment after being adjusted into some time zone.
The GregorianCalendar, the Date, and the String all represent the same moment, same point on the timeline, but different wall-clock time.
Use java.time for clarity
Date-time handling is much easier and clear if you use modern java.time classes rather than awful mess that is the legacy classes Date, Calendar, and GregorianCalendar.
java.time
The GregorianCalendar class is one of the troublesome old date-time classes supplanted by the java.time classes built into Java 8 and later. Much of the java.time functionality is back-ported to Java 6 and Java 7 in the ThreeTen-Backport project.
Convert from legacy class to modern java.time using new methods added to the old classes, specifically GregorianCalendar::toZonedDateTime. If using the back-port, use the DateTimeUtils class.
ZonedDateTime zdt = DateTimeUtils.toZonedDateTime( myCalendar ) ;
A ZonedDateTime object is the replacement for GregorianCalendar. This class is conceptually the combination of a Instant (a moment in UTC) with an assigned time zone, a ZoneId object.
If you want the same moment as seen in UTC, extract the Instant.
Instant instant = zdt.toInstant() ;
You can convert back to a java.util.Date from an Instant, for compatibility with old code not yet updated to java.time.
java.util.Date date = DateTimeUtils.toDate( instant ) ; // Convert from modern `Instant` class to legacy `Date` class.
If you want just the date portion, without the time-of-day and without the time zone, create a LocalDate object.
LocalDate ld = zdt.toLocalDate() ;
The problem is calling Calendar.getTime() gives a different date, offset by (I think) our timezone. So the next day by 8 hours.
How can we do this without this shift?
…
And the getTime() is returning a Date of "Thu Apr 30 18:00:00 MDT 1992" - I'm in the Mountain Time Zone.
What you are seeing is an illusion. The GregorianCalendar::getTime method returns to you a java.util.Date object. Then you implicitly called toString on that Date object. That java.util.Date::toString method has an unfortunate behavior of applying your JVM’s current default time zone while generating a string to represent its value. The value of the Date is actually UTC, always UTC, by definition. That toString method creates the illusion that the Date harbors a time zone when in fact it does not†.
†Actually, the java.util.Date class does harbor a time zone, but deep within its source code. Used for stuff like the equals method implementation. But the class has no getter or setter, so it seems invisible to us. And in the context of your Question, is irrelevant.
Confusing? Yes. This is one of many reasons to avoid these terrible old date-time classes. Use only java.time classes instead.
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, Java SE 10, and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
Later versions of Android bundle implementations of the java.time classes.
For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
java.util.Date vs java.sql.Date: when to use which and why?
Congratulations, you've hit my favorite pet peeve with JDBC: Date class handling.
Basically databases usually support at least three forms of datetime fields which are date, time and timestamp. Each of these have a corresponding class in JDBC and each of them extend java.util.Date. Quick semantics of each of these three are the following:
java.sql.Date corresponds to SQL DATE which means it stores years, months and days while hour, minute, second and millisecond are ignored. Additionally sql.Date isn't tied to timezones.
java.sql.Time corresponds to SQL TIME and as should be obvious, only contains information about hour, minutes, seconds and milliseconds.
java.sql.Timestamp corresponds to SQL TIMESTAMP which is exact date to the nanosecond (note that util.Date only supports milliseconds!) with customizable precision.
One of the most common bugs when using JDBC drivers in relation to these three types is that the types are handled incorrectly. This means that sql.Date is timezone specific, sql.Time contains current year, month and day et cetera et cetera.
Finally: Which one to use?
Depends on the SQL type of the field, really. PreparedStatement has setters for all three values, #setDate() being the one for sql.Date, #setTime() for sql.Time and #setTimestamp() for sql.Timestamp.
Do note that if you use ps.setObject(fieldIndex, utilDateObject); you can actually give a normal util.Date to most JDBC drivers which will happily devour it as if it was of the correct type but when you request the data afterwards, you may notice that you're actually missing stuff.
I'm really saying that none of the Dates should be used at all.
What I am saying that save the milliseconds/nanoseconds as plain longs and convert them to whatever objects you are using (obligatory joda-time plug). One hacky way which can be done is to store the date component as one long and time component as another, for example right now would be 20100221 and 154536123. These magic numbers can be used in SQL queries and will be portable from database to another and will let you avoid this part of JDBC/Java Date API:s entirely.
LATE EDIT: Starting with Java 8 you should use neither java.util.Date nor java.sql.Date if you can at all avoid it, and instead prefer using the java.time package (based on Joda) rather than anything else. If you're not on Java 8, here's the original response:
java.sql.Date - when you call methods/constructors of libraries that use it (like JDBC). Not otherwise. You don't want to introduce dependencies to the database libraries for applications/modules that don't explicitly deal with JDBC.
java.util.Date - when using libraries that use it. Otherwise, as little as possible, for several reasons:
It's mutable, which means you have to make a defensive copy of it every time you pass it to or return it from a method.
It doesn't handle dates very well, which backwards people like yours truly, think date handling classes should.
Now, because j.u.D doesn't do it's job very well, the ghastly Calendar classes were introduced. They are also mutable, and awful to work with, and should be avoided if you don't have any choice.
There are better alternatives, like the Joda Time API (which might even make it into Java 7 and become the new official date handling API - a quick search says it won't).
If you feel it's overkill to introduce a new dependency like Joda, longs aren't all that bad to use for timestamp fields in objects, although I myself usually wrap them in j.u.D when passing them around, for type safety and as documentation.
tl;dr
Use neither.
java.time.Instant replaces java.util.Date
java.time.LocalDate replaces java.sql.Date
Neither
java.util.Date vs java.sql.Date: when to use which and why?
Both of these classes are terrible, flawed in design and in implementation. Avoid like the Plague Coronavirus.
Instead use java.time classes, defined in in JSR 310. These classes are an industry-leading framework for working with date-time handling. These supplant entirely the bloody awful legacy classes such as Date, Calendar, SimpleDateFormat, and such.
java.util.Date
The first, java.util.Date is meant to represent a moment in UTC, meaning an offset from UTC of zero hours-minutes-seconds.
java.time.Instant
Now replaced by java.time.Instant.
Instant instant = Instant.now() ; // Capture the current moment as seen in UTC.
java.time.OffsetDateTime
Instant is the basic building-block class of java.time. For more flexibility, use OffsetDateTime set to ZoneOffset.UTC for the same purpose: representing a moment in UTC.
OffsetDateTime odt = OffsetDateTime.now( ZoneOffset.UTC ) ;
You can send this object to a database by using PreparedStatement::setObject with JDBC 4.2 or later.
myPreparedStatement.setObject( … , odt ) ;
Retrieve.
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
java.sql.Date
The java.sql.Date class is also terrible and obsolete.
This class is meant to represent a date only, without a time-of-day and without a time zone. Unfortunately, in a terrible hack of a design, this class inherits from java.util.Date which represents a moment (a date with time-of-day in UTC). So this class is merely pretending to be date-only, while actually carrying a time-of-day and implicit offset of UTC. This causes so much confusion. Never use this class.
java.time.LocalDate
Instead, use java.time.LocalDate to track just a date (year, month, day-of-month) without any time-of-day nor any time zone or offset.
ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
LocalDate ld = LocalDate.now( z ) ; // Capture the current date as seen in the wall-clock time used by the people of a particular region (a time zone).
Send to the database.
myPreparedStatement.setObject( … , ld ) ;
Retrieve.
LocalDate ld = myResultSet.getObject( … , LocalDate.class ) ;
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, Java SE 10, Java SE 11, and later - Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Most of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
Later versions of Android bundle implementations of the java.time classes.
For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
The only time to use java.sql.Date is in a PreparedStatement.setDate. Otherwise, use java.util.Date. It's telling that ResultSet.getDate returns a java.sql.Date but it can be assigned directly to a java.util.Date.
I had the same issue, the easiest way i found to insert the current date into a prepared statement is this one:
preparedStatement.setDate(1, new java.sql.Date(new java.util.Date().getTime()));
The java.util.Date class in Java represents a particular moment in time (e,.g., 2013 Nov 25 16:30:45 down to milliseconds), but the DATE data type in the DB represents a date only (e.g., 2013 Nov 25). To prevent you from providing a java.util.Date object to the DB by mistake, Java doesn’t allow you to set a SQL parameter to java.util.Date directly:
PreparedStatement st = ...
java.util.Date d = ...
st.setDate(1, d); //will not work
But it still allows you to do that by force/intention (then hours and minutes will be ignored by the DB driver). This is done with the java.sql.Date class:
PreparedStatement st = ...
java.util.Date d = ...
st.setDate(1, new java.sql.Date(d.getTime())); //will work
A java.sql.Date object can store a moment in time (so that it’s easy to construct from a java.util.Date) but will throw an exception if you try to ask it for the hours (to enforce its concept of being a date only). The DB driver is expected to recognize this class and just use 0 for the hours. Try this:
public static void main(String[] args) {
java.util.Date d1 = new java.util.Date(12345);//ms since 1970 Jan 1 midnight
java.sql.Date d2 = new java.sql.Date(12345);
System.out.println(d1.getHours());
System.out.println(d2.getHours());
}
java.util.Date represents a specific instant in time with millisecond precision. It represents both date and time information without timezone. The java.util.Date class implements Serializable, Cloneable and Comparable interface. It is inherited by java.sql.Date, java.sql.Time and java.sql.Timestamp interfaces.
java.sql.Date extends java.util.Date class which represents date without time information and it should be used only when dealing with databases. To conform with the definition of SQL DATE, the millisecond values wrapped by a java.sql.Date instance must be 'normalized' by setting the hours, minutes, seconds, and milliseconds to zero in the particular time zone with which the instance is associated.
It inherits all public methods of java.util.Date such as getHours(), getMinutes(), getSeconds(), setHours(), setMinutes(), setSeconds(). As java.sql.Date does not store the time information, it override all the time operations from java.util.Dateand all of these methods throw java.lang.IllegalArgumentException if invoked as evident from their implementation details.
To make a JDBC query I need to pass date to it. The date is kept in Date field type of PostgreSql database, which represents specific day without any time.
As I need only date, I decided to use specific object which represent only date without time, which is LocalDate from Joda-Time package. I thought it is important because if I used DateTime object, it would carry redundant time data as well as it might lead to bugs at end of daylight saving time when the clock are put backward one hour (though the situation is unprecedentedly rare, it's not impossible).
But when I started trying to square LocalDate object with accepted arguments of preparedStatement.setDate method, I didn't find a proper way to do it.
setDate accepts java.sql.Date as parameter. And the only option to construct java.sql.Date object is to pass it time in milliseconds.
But this defeats all the purpose of using LocalDate from Joda-Time package, as on this conversion we get back to milliseconds and, while these conversions happen, clock may be put back one hour and change the date to the previous date.
So, now I have this line in my code:
preparedStatement.setDate(1, new java.sql.Date(localDate.toDate().getTime()));
But is this the best way to convert LocalDate to accepted by setDate format?
Are my concerns related to daylight saving time and corresponding clock-shifts justified?
Is there a better way to pass date (and only date without time) to JDBC preparedStatement?
It should be safe to use your technique because all the timezone issues will be taken into account by LocalDate#toDate. The resulting millisecond instant you have is context-independent: it uniquely relates to a timezone valid at that point in time within the locale you are using for conversion. In other words, if you repeat the conversion of the exact same millisecond value throughout a year, you will consistently get the exact same answer, even if timezone regulations change for your place in the meantime, since JDK refers to a database documenting the complete history of all timezone changes around the world.
When reasoning about these issues it is important to remember that your current timezone has no effect on the conversion, which is parameterized by your locale and resolves the timezone only within the context of the instant being converted.
I wholeheartedly sympathize with the queasiness you fell about all this: it is turning a simple and straigtforward operation into a complex maze of calculations which does nothing but invite trouble. Hopefully things will take a positive turn with Java 8 and its new (yes, again!) Date/Time API, based firmly on JodaTime.
I got the same problem today. I'm using JDK 8. After spending some hours searching finally I found the answer at Java SE 8 Documentation. This is the solution :
statement.setDate(5, java.sql.Date.valueOf(personToUpdate.getBirthday()));
statement is PreparedStatement instance. "personToUpdate.getBirthday()" is type of LocalDate.
Since org.joda.time.toDateMidnight() and org.joda.time.toDateMidnight(DateTimeZone zone) have been deprecated, this is the solution that works perfectly for me.
My typical Class, to be persisted:
...
import org.joda.time.LocalDate;
...
public class MyObject implements Serializable {
...
private LocalDate startDate;
...
private EndDate startDate;
// Getters and Setters
...
...
}
Im my other Class where I persist startDate, I have:
myObject.setStartDate(new LocalDate(myObject.getStartDate().toDateTimeAtStartOfDay(DateTimeZone.getDefault())));
tl;dr
myPreparedStatement.setObject( // Pass java.time objects directly to database with JDBC 4.2 or later.
… ,
LocalDate.now() // Get current date today. Better to pass optional `ZoneId` time zone object explicitly than rely implicitly on JVM’s current default.
)
java.time
In Java 8 and later, the new java.time framework is now built-in. This successor to Joda-Time is defined by JSR 310 and extended by the ThreeTen-Extra project.
Hopefully we well eventually see the JDBC drivers updated to directly handle the new java.time types. But until then we continue to need the java.sql.* types. Fortunately, new methods have been added to conveniently convert between the types.
For a date-only, with no time-of-day and no time zone, the Java type is LocalDate (quite similar to Joda-Time).
As for your concern about time zone related to a LocalDate, it matters when your are translating a date-only to a date-time, to a moment on the timeline. A date-only is just a vague idea, with no real meaning, until you translate it to a time-span of moment on the timeline (midnight to midnight in some time zone). For example, determining "today" requires a time zone. In java.time we use the ZoneId class.
LocalDate today = LocalDate.now( ZoneId.of( "America/Montreal" ) );
If omitted, your JVM’s current default time zone is used in determining the date. In other words, the following two lines are equivalent.
LocalDate today = LocalDate.now();
LocalDate today = LocalDate.now( ZoneId.systemDefault() );
I consider the first version, now(), to be a poor API design choice. This implicit application of the JVM’s current default time zone causes no end of confusion, bugs, and misery among naïve developers. For one thing, the JVM's current default varies by machine, by host OS settings, and by sysadmins. Worse, the JVM’s current default can change at any moment, during runtime, by any code in any thread of any app within that JVM. So best practice is to always specify your desired/expected time zone.
Now that we have a java.time object for "today", how to get it into the database?
With JDBC 4.2 or later, directly exchange java.time objects with your database.
myPreparedStatement.setObject( … , today ) ;
To retrieve:
LocalDate ld = myResultSet.getObject( … , LocalDate.class ) ;
If you cannot upgrade yet to JDBC 4.2 or later: Use a java.sql.Date object. In Java 8, that old class gained new methods, toLocalDate and valueOf. The latter is our bridge from the java.time type to the java.sql type.
java.sql.Date sqlToday = java.sql.Date.valueOf( today );
From there do the usual PreparedStatement handling.
myPreparedStatement.setDate( 1 , sqlToday );
Date-only vs Date-time
Perhaps you have concerns about such a date-only fitting your business needs.
If you need to know, for example, if a contract was signed by the end of the day for legal reasons, then date-only is the wrong data-type if mean a specific moment such as the stroke of midnight in Montréal. A new day dawns earlier in Paris than in Montréal, so "today" in Paris is "yesterday" in Montréal. If your contract deadline is defined legally as the end of the day in Montréal, then you must apply a time zone. To apply a time zone, you must have a date-time rather than a date-only. You can make a jump from the LocalDate into a ZonedDateTime, but I consider that overly complex. Your database should have used a date-time type from the beginning.
In Postgres, a date-time type means the TIMESTAMP WITH TIME ZONE type. That name is a misnomer as the time zone is not actually stored. Think of it as “timestamp with respect for time zone”. Postgres uses any offset-from-UTC or time zone information accompanying incoming data to adjust to UTC, and that offset/zone info is then discarded. The other type, TIMESTAMP WITHOUT TIME ZONE, ignores the offset/zone info entirely, and this is the wrong behavior for most any business app.
I suspect many developers or DBAs may make the naïve mistake of thinking by intuition that the date-only has obvious meaning. But in fact if you have specific or strict moment-oriented needs, such as legalities regarding events such as “contract signed”, “invoice received”, or “company executive hired”, then a date-time value should be used rather than date-only.
In other words, regarding the Question’s author’s comment:
And I expected there should be a way to work with dates without resorting to time instances.
No, I would argue that is asking for trouble. If moments matter, use a date-time rather than a date-only.
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
Later versions of Android bundle implementations of the java.time classes.
For earlier Android, the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
Try to Use LocalDate#toDateMidnight() which sets time to 0 and then DateMidnight#toDate().
Date date = localDate.toDateMidnight().toDate();
Or if you're using JodaTime 1.5 or newer, use LocalDate#toDateTimeAtStartOfDay() and then DateTime#toDate()
Hope that help