What is the Best Practice for manipulating and storing Dates e.g. using GregorianCalendar in an enterprise java application?
Looking for feedback and I will consolidate
To get the discussion started, here's been my experience:
When creating standards for a typical 3-tier Java Enterprise project, I would generally recommend that the project use GregorianCalendar for manipulating dates. Reason is GregorianCalendar is the de facto standard over any other Calendar instance e.g. Julian calendar etc. It's the recognized calendar in most countries and properly handles leap years, etc. On top of that, I would recommend that the application store its dates as UTC so that you can easily perform date calculations such as finding the difference between two dates (if it were stored as EST for example, you'd have to take day light savings time into account). The date can be then be localized to whatever timezone you need it to be displayed to the user as -- such as localizing it to EST if you are an east-coast US company and you want your time information shown in EST.
The best practice is usually precisely NOT to think in term of heavy date objects but to store a point in time. This is typically done by storing a value that doesn't suffer from corner cases nor from potential parsing problems. To do this, people usually store the number of milliseconds (or seconds) elapsed since a fixed point that we call the epoch (1970-01-01). This is very common and any Java API will always allow you to convert any kind of date to/from the time expressed in ms since the epoch.
That's for storage. You can also store, for example, the user's preferred timezone, if there's such a need.
Now such a date in milliseconds, like:
System.out.println( System.currentTimeMillis() );
1264875453
ain't very useful when it's displayed to the end user, that's for granted.
Which is why you use, for example, the example Joda time to convert it to some user-friendly format before displaying it to the end-user.
You asked for best practice, here's my take on it: storing "date" objects in a DB instead of the time in milliseconds is right there with using floating point numbers to represent monetary amounts.
It's usually a huge code smell.
So Joda time in Java is the way to manipulate date, yes. But is Joda the way to go to store dates? CERTAINLY NOT.
Joda time (100% interoperable with the JDK)
Joda-Time provides a quality replacement for the Java date and time classes. The design allows for multiple calendar systems, while still providing a simple API
Think, work, and store data in UTC rather than any time zone. Think of UTC as the One True Time, and all other time zones are mere variations. So while coding, forget all about your own time zone. Do your business logic, logging, data storage, and data exchange in UTC. I suggest every programmer keep a second clock on their desk set to UTC.
The modern way is the java.time classes.
The mentioned Joda-Time project provided the inspiration for the java.time classes, and the project is now in maintenance mode with the team advising migration to java.time classes.
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, & java.text.SimpleDateFormat.
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.
When serializing a date-time value to text, use the ISO 8601 standard.
For example, a date-time in UTC is 2016-10-17T01:24:35Z
where the Z
is short for Zulu
and means UTC. For other offset-from-UTC the offset of hours and minutes appears at the end such as 2016-01-23T12:34:56+05:30
. The java.time classes extend this standard format to append the name of the time zone (if known) in square brackets, such as 2016-01-23T12:34:56+05:30[Asia/Kolkata]
.
The standard has many other handy formats as well including for durations, intervals, ordinals, and year-week.
For database storage, use date-time types for date-time values, such as the SQL standard data types which are primarily DATE
, TIME
, and TIMESTAMP WITH TIME ZONE
.
Let your JDBC driver do the heavy lifting. The driver handles the nitty-gritty details about mediating and adapting between the internals of how Java handles the data and how your database handles the data on its side. But be sure to practice with example data to learn the behaviors of your driver and your database. The SQL standard defines very little about date-time handling and so behaviors vary widely, surprisingly so.
If using a JDBC driver compliant with JDBC 4.2 and later, you can fetch and store java.time types directly via the ResultSet::getObject
and PreparedStatement::setObject
methods.
Instant instant = myResultSet.getObject( … );
myPreparedStatement.setObject( … , instant );
For older drivers, you will need to fall back to converting through the java.sql types. Look for new conversion methods added to the old classes. For example, java.sql.Timestamp.toInstant().
Instant instant = myResultSet.getTimestamp( … ).toInstant();
myPreparedStatement.setObject( … , java.sql.Timestamp.from( instant ) );
Use the java.sql types as briefly as possible. They are a badly designed hack, such as java.sql.Date
masquerading as a date-only value but actually as a subclass of java.util.Date
it does indeed have a time-of-day set to the 00:00:00
in UTC. And, oh, you are supposed to ignore the fact of that inheritance says the class doc. An ugly mess.
Get the current moment in UTC.
Instant instant = Instant.now();
Storing and fetching that Instant
object to/from a database is shown above.
To generate an ISO 8601 string, merely call toString
. The java.time classes all use ISO 8601 formats by default for parsing and generating strings of their various date-time values.
String output = instant.toString();
Adjust into any offset-from-UTC by applying a ZoneOffset
to get an OffsetDateTime
. Call toString
to generate a String in ISO 8601 format.
ZoneOffset offset = ZoneOffset.ofHoursMinutes( 5 , 30 );
OffsetDateTime odt = instant.atOffset( offset );
A time zone is an offset plus a set of rules for handling anomalies such as Daylight Saving Time (DST). When you need to see that same moment through the lens of some region’s own wall-clock time, apply a time zone (ZoneId) to get a ZonedDateTime object.
Specify a proper time zone name in the format of continent/region
. Never use the 3-4 letter abbreviation such as EST
or IST
as they are not true time zones, not standardized, and not even unique(!).
ZoneId z = ZoneId.of( "Asia/Kolkata" );
ZonedDateTime zdt = instant.atZone( z );
Going the other direction, you can extract an Instant
from an OffsetDateTime
or ZonedDateTime
by calling toInstant
.
Instant instant = zdt.toInstant();
For presentation to the user as strings in formats other than ISO 8601, search Stack Overflow for use of the DateTimeFormatter
class.
While you can specify an custom format, usually best to let java.time automatically localize. To localize, specify:
Example:
Locale l = Locale.CANADA_FRENCH ;
DateTimeFormatter f = DateTimeFormatter.ofLocalizedDateTime( FormatStyle.FULL ).withLocale( l );
String output = zdt.format( f );
Best to avoid the legacy date-time types whenever possible. But if working with old code not yet updated for the java.time types, you can convert to/from the java.time types. For details, see the Question, Convert java.util.Date to what “java.time” type?.
Use objects rather than mere coded primitives and simple strings. For example:
DayOfWeek.TUESDAY
. Using such objects makes your code more self-documenting, ensures valid values, and provides type-safety.
Joda is the way to go. Why ?
At some stage the Java Date/Time API is going to be superseded (by JSR-310). I believe this is going to be based upon the work done by those behind Joda, and as such you'll be learning an API that will influence a new standard Java API.