25

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?

ovgolovin
  • 13,063
  • 6
  • 47
  • 78
  • And what are the semantics at the db side? If it's *instant* and not *date*, you're fried anyway. – Marko Topolnik Apr 24 '13 at 10:54
  • @MarkoTopolnik As I'm getting from the [table](http://www.postgresql.org/docs/9.1/static/datatype-datetime.html) it's just date withouth time (as it takes 4 bytes, not 8 bytes as DateTime types). – ovgolovin Apr 24 '13 at 10:56
  • It should be safe to use your technique because all the timezone issues should be taken into account by `LocalDate#toDate`. – Marko Topolnik Apr 24 '13 at 11:04
  • @MarkoTopolnik But what if we create an instance of `LocalDate`, then we call `toDate()` on it, then clock is rewinded one hour back and the day becomes the previous one, then `getTime()` is called (it's still a previous day), and then `java.sql.Date` is constructed (it's still a previous day). So we end up with the previous day. I understand that I can use `UTC` with no daylight saving times. But still, I see no purpose in using any time-related values, when I don't need time at all (but just date). – ovgolovin Apr 24 '13 at 11:08
  • That is not what can happen. The millisecond instant you have is context-independent: it uniquely relates to a timezone valid at that point in time. The only real trouble you can get is when two instants map to the same time label (events when the clocks are turned backwards so the same time labels repeat). There have actually been some clarifications as to the exact semantics in those cases, but you shouldn't be affected by them, anyhow. – Marko Topolnik Apr 24 '13 at 11:39
  • As for your hope to get sane date/time semantics with Java, you're out of luck. Java 8 is/was supposed to give us a new API (again!), based primarily on JodaTime. But I have no idea if that implies that JDBC will make use of it in the same version. – Marko Topolnik Apr 24 '13 at 11:44
  • @MarkoTopolnik Could you clarify (may be as an answer) why I shouldn't care about the clock being turned backwards? Because now I'm getting it this way (and I may be wrong in it): We create the object `localDate.toDate()` holding `00:00` for **some** day. But today at `3:00` the clock are getting taken backwards. At this very moment our object will still be representing the same instance of time, but in my local time system it will be -1 hour, namely `23:00` **of the previous day**. So the day of the time instance is dependent on the current DST status of the moment we are at. – ovgolovin Apr 24 '13 at 11:52
  • Did you actually test this? Because this is **not** what should happen if you properly convert back from instant to date/time. An instant is converted to date/time labels by considering the TZ valid at that instant, and not what is valid at the moment of conversion. 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**. – Marko Topolnik Apr 24 '13 at 11:59
  • Check out [this legendary answer by Jon Skeet](http://stackoverflow.com/a/6841479/1103872). I think it should be relevant to the understanding of your case. – Marko Topolnik Apr 24 '13 at 12:10
  • @MarkoTopolnik Ah. This is the moment I was taking wrong! So, if I convert `localDate.toDate()`, it will save the time zone in the object, so that it will always represent the date of original `localDate` object. And all subsequent conversions and transubstantiations will use that time zone so the date won't change. Right? – ovgolovin Apr 24 '13 at 12:10
  • Saving the TZ isn't necessary. An instance of `Date` is just a wrapper around the millisecond value and can be converted to date/time labels only with the desired target TZ specified. You can see that all TZ-related functionality in `Date` is now deprecated. `Calendar`, on the other hand, is locale-sensitive. – Marko Topolnik Apr 24 '13 at 12:19
  • So this is where the danger lies: the JDBC driver must convert milliseconds into date labels (if that is really what your DB is saving), and later it must convert from those labels retrieved from the DB back into a millisecond instant. In principle this is still context-independent. – Marko Topolnik Apr 24 '13 at 12:23
  • Maybe this is a point that is confusing you: the conversion is done according to a *locale*, not a specific *time zone*. Timezone is derived from locale for the instant being converted. – Marko Topolnik Apr 24 '13 at 12:27
  • @MarkoTopolnik So, timezone is taking from the locale valid at the moment of DateTime instance. And so this object `java.sql.Date(localDate.toDate().getTime())` will be invariant. – ovgolovin Apr 24 '13 at 12:36
  • Yes, exactly. So if there are no glitches at the lower level (JDBC/database), this should work properly. – Marko Topolnik Apr 24 '13 at 12:38
  • @MarkoTopolnik Thank you! Now the topic seems to be more or less clear to me! – ovgolovin Apr 24 '13 at 12:40
  • OK, I should now rewrite it as an answer to make it more accessible to future visitors. – Marko Topolnik Apr 24 '13 at 12:42
  • @MarkoTopolnik It would be great! – ovgolovin Apr 24 '13 at 12:42

5 Answers5

12

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.

Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
  • Hopefully JDBC will also take advantage of this and start accepting `LocatDate` object as parameter to `setDate`! – ovgolovin Apr 24 '13 at 12:55
  • 1
    @ovgolovin Indeed, you *can* exchange *java.time* objects directly with JDBC 4.2 and later. Use `PreparedStatement::setObject` and `ResultSet::getObject` rather than `setDate`/`getDate`. See [my Answer](https://stackoverflow.com/a/32805605/642706) on this same Question for details. `myPreparedStatement.setObject( … , localDate )` and `myResultSet.getObject( … , LocalDate.class )`. – Basil Bourque Feb 02 '18 at 19:47
4

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.

Ajang R
  • 49
  • 1
  • 1
    You're confusing Java 8 LocalDate with Joda LocalDate. The question is about Joda LocalDate. – oulenz Sep 07 '16 at 11:45
1

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())));
Oshkosh1017
  • 109
  • 9
  • Is `toDateTimeAtStartOfDay` a typo? Did you mean the [withTimeAtStartOfDay](http://www.joda.org/joda-time/apidocs/org/joda/time/DateTime.html#withTimeAtStartOfDay()) method on the Joda-Time [`DateTime`](http://www.joda.org/joda-time/apidocs/org/joda/time/DateTime.html) class? – Basil Bourque Sep 27 '15 at 05:01
  • No, it is not a typo. Before using it I set startDate to, let's say, "2015-01-01' and in it was persisted to database as "2014-12-31". This now works because the conversion to LocalDate is done as it should. – Oshkosh1017 Sep 27 '15 at 12:09
  • So can you link to the doc? I can't find that method. – Basil Bourque Sep 27 '15 at 17:05
1

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?

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.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
0

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

Khalil
  • 259
  • 2
  • 8
  • [JodaTime-DateMidnight](http://joda-time.sourceforge.net/apidocs/org/joda/time/DateMidnight.html) – Khalil Apr 24 '13 at 11:24
  • 1
    This still casts date to a specific instance of the moment. And it may happen that after this conversion the current moment will pass the end of DST period and the clock will be rewound one hour back. After that the instance of the moment we saved will represent 23:00 of the **previous day**. That is the problem. And I expected there should be a way to work with dates without resorting to time instances. – ovgolovin Apr 24 '13 at 11:28
  • Also, what additionally it offers to what [`toDate`](http://joda-time.sourceforge.net/apidocs/org/joda/time/LocalDate.html#toDate()) method does? – ovgolovin Apr 24 '13 at 12:44
  • The midnight-related classes and methods have been deprecated in latest versions of Joda-Time. Instead call method `withTimeAtStartOfDay`. The first moment of the day is *not* always `00:00:00.0`. – Basil Bourque Sep 26 '15 at 15:57