Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How would you represent date of birth in your java model?

And wait, don't rush to answer "java.util.Date", consider the following scenario.

Person object having 2 fields: "birthday" and "nextMeeting" both java.util.Date. Now birthday stored in database as date type column (no time) for eg. 01-10-1979, and nextMeeting as datetime type for ex. 01-10-2010 20:00:00.

You pull it from db, "birthday" will be auto set to midnight by JDBC. Now you need to send this object to other JVM using lets say RMI or whatever technology.

On the other end JVM has timezone -1h from originating JVM. This is where problem starts. nextMeeting become 01-10-2010 19:00:00 which is absolutely FINE and CORRECT from user perspective etc...

BUT birthday become 30-09-1979 23:00:00 which will be represented to user as 30th of September, which is really not what we want, cause obviously birthday is something static and NOT dependent on timezones.

So column type in db chosen correctly (date). This type of column usually represented as java.util.Date. But in our case it is wrong java type to use.

So how would you represent a birthday? Consider that you need to manipulate this object on a UI like in a datepicker component etc...

like image 890
Ilja S. Avatar asked Oct 27 '10 07:10

Ilja S.


2 Answers

The other answers use outmoded classes.

java.time

Both Joda-Time and the old java.util.Date/.Calendar classes have been supplanted by the java.time framework built into Java 8 and later. Defined by JSR 310. Extended by the ThreeTen-Extra project. Back-ported to Java 6 & 7 by the ThreeTen-BackPort project, which is wrapped for Android by the ThreeTenABP project.

LocalDate

A date-only value without time-of-day and without time zone can be represented by the LocalDate class. Such a class was lacking in the old date-time classes bundled with the earliest versions of Java. The old java.sql.Date class pretends to be date-only but in fact has a time-of-day inherited from java.util.Date (a date-time value).

LocalDate dateOfBirth = LocalDate.of( 1979 , 1 , 10 );

Without a time of day nor time zone, a date of birth is inherently inaccurate for determining one’s age. But in nearly all use-cases we don't care; give-or-take part of a day is close enough.

ZonedDateTime

For a meeting we cannot be so loosey-goosey. We need a date, a time, and a time zone. In java.time that means ZonedDateTime class. The time zone is the key element missing from the scenario in the Question. Add the time zone and all is well.

ZoneId zoneIdMontreal = ZoneId.of( "America/Montreal" );
ZonedDateTime zdtMontreal = ZonedDateTime.of( 2010 , 1 , 10 , 20 , 0 , 0 , zoneIdMontreal );

Now communicate the objects to another machine. Both remain intact, the same date-only value for date-of-birth (1979-01-10) and the same Montréal date-time for the meeting.

Adjust time zone

You might then want to adjust that meeting to another time zone expected by the person using this other machine.

ZoneId zoneIdParis = ZoneId.of( "Europe/Paris" );
ZonedDateTime zdtParis = zdtMontreal.withZone( zoneIdParis );

We have the same moment on the timeline represented in two fashions, in two objects, zdtMontreal & zdtParis.

LocalDateTime

If your nextMeeting has no time zone nor offset-from-UTC info, then represent as a LocalDateTime object. In the database it would be stored as a type like TIMESTAMP WITHOUT TIME ZONE.

Such values do not represent a moment in the timeline. They represent only a range of possible moments. To determine an actual moment you must provide the context of a specific time zone.

For storing future date-time values such as a planned meeting further out than a few weeks, doing so without time zone may be appropriate. Politicians around the world have shown a proclivity for often changing Daylight Saving Time and otherwise redefining their time zones. And they often do so with little warning, as little as only several weeks warning.

To determine an actual moment such as showing a schedule in a calendar, apply a time zone ZoneId to get a ZonedDateTime.

ISO 8601

The java.time classes use the standard ISO 8601 formats by default when parsing/generating textual representations of date-time values. The ZonedDateTime class goes one step further by extending ISO 8601 to append the name of the time zone in square brackets.

If serializing values via text, use to the sensible and unambiguous formats of ISO 8601.

None of the issues raised in the Question remain. Using an excellent date-time library and ISO 8601 such as java.time solves the problem.

Database

Your database should use date-only type for the birthdate, and a timestamp-with-time-zone type for the meeting (see SQL data types in Wikipedia and in your database’s documentation). Your JDBC driver mediates both types for you.

Eventually JDBC drivers will be updated to directly use java.time types. But until then we must convert to java.sql types such as java.sql.Date and java.sql.Timestamp. New methods have been added to the old classes to support these conversions.

java.sql.Date sqlDateOfBirth = java.sql.Date.valueOf( dateOfBirth );
java.sql.Timestamp sqlMeeting = java.sql.Timestamp.valueOf( zdtMontreal );

Then call setDate and setTimestamp on your PreparedStatement.

Going the other direction, from database to Java, call getDate and getTimestamp on your ResultSet. Then translate immediately to java.time types, avoiding the use of the java.sql types in your business logic.

For the date-time value, we must go through an Instant object. The Instant is a moment on the timeline in UTC. We apply a time zone to get a wall-clock time for the user.

LocalDate dateOfBirth = mySqlDate.toLocalDate();
Instant instant = mySqlTimestamp.toInstant();
ZonedDateTime zdtMontreal = ZonedDateTime.ofInstant( instant , zoneIdMontreal );
like image 112
Basil Bourque Avatar answered Nov 16 '22 05:11

Basil Bourque


Use LocalDate from JodaTime and only store the date for the birthday, not the time.

like image 7
Jeroen Rosenberg Avatar answered Nov 16 '22 06:11

Jeroen Rosenberg