Date and time are complicated concepts. Working with them in Java (and especially in Android) adds an extra layer of complexity.

In Java 8+ there is a new Date/Time API available through the java.time.* package, which improves on the previous java.util.Date implementation. Unfortunately, if you have to support Android API levels under 26 (Android 8.0), you will not be able to use the java.time features and will have to work with java.util.

Problems with java.util.Date

The Date class is not actually a representation of a date but an instance of specific time [1]. An instance is a point in time from the UNIX Epoch time, the number of milliseconds that have elapsed since 00:00:00 UTC on 1 January 1970. It does not include timezone, so local instances can differ. When trying to convert the Date instance into an actual date, you can see why this is an issue.

If you want to display any date or time related information from the Date instance created, you have to convert it to a Calendar instance as most of the accessors are deprecated. This just adds an extra layer of complexity when passing around the Date instance and places a constraint on your codebase to maintain this connection. On top of this heavy overhead, the Calendar class itself also has numerous issues.

If for some reason you have to use any of the Date accessors, then consider the following:

  • A month is represented by an integer from 0 to 11, 0 being January and 11 being December
  • A day of month is represented by an integer from 1 to 31
  • An hour is represented by an integer from 0 to 23

The mixture of 0 index ranges for some and 1 index ranges for others can be confusing if the user of the API is not aware of these constraints. As well as that, arguments given to methods do not need to fall within the indicated ranges. For example, a date may be specified as January 32 and processed as February 1. Although this approach was intentional to help simplify arithmetic operations for date/time, it can cause issues for those unaware.

Another constraint is trying to work with different time zones when using Date. It uses the system-local time zone, including when outputting the value when toString() is called. This can be especially detrimental if you’re interacting with services or data coming from other time zones.

Using this Date class proves confusing and shows how complicated date/time can be. To rectify this problemed API, a proposal was made to improve upon time and calendar related calculations in the form of JSR-310.

JSR-310 - What is it?

The Java Specification Requests 310 (JSR-310) aimed to replace the existing date and time APIs, java.util.Date and java.util.Calendar, while still providing backwards-compatible access to these older APIs [2].

JSR-310 took inspiration from Joda-Time, a date and time library that aimed to solve all the problems associated with the old approach. If Joda-Time is a library with significant improvements over the Date class, why didn’t JDK 8+ use Joda-Time? The simple reason is, Joda-Time has design flaws [3].

Why not use Joda-Time?

Before the new Date/Time APIs for JDK 8+, Joda-Time provided a good solution to work around all the issues previously mentioned (and more). The concept of time in our societies has resulted in multiple calendar systems, different time zones, and overlaps. This makes it very difficult to encapsulate all of this into something machines can understand.

Joda-Time helped to work around the concepts, but with any implementation, over the years of usage there are areas where it could be improved.

Each date/time class in Joda-Time has a pluggable chronology, defining the calendar system that is in use. Most users of the API never check to see if the chronology is the standard ISO chronology before calling getMonthOfYear(). This may not be an issue until you realise that the Coptic chronology has 13 months in a year, and thus can return a range of 1 to 13.

Another issue is that the library can accept null values for most of the methods. This can potentially cause bugs when you may not expect to be handling nulls and instead except an error.

These errors are minor inconveniences compared to the predecessor that Joda-Time was trying to provide a replacement for. The library is still functional with many developers still using it. The creator of the library, the same individual who created JSR-310, didn’t want to add something with flaws so instead started from scratch.

If you visit the Joda-Time documentation, it recommends to migrate to the new java.time package instead, but what if you don’t have access to JDK 8+ and have to use an older version?

ThreeTen, ThreeTen Backport, and ThreeTen Android Backport

In order to provide the JSR-310 updates to JDK 6 and 7, a ThreeTen backport is available. This is not an implementation of the JSR, but a fork of the API [4]. While the ThreeTen backport might seem like the solution to finally getting date and time right, when it comes to Android it has a major problem.

The backport library uses a JAR resource for loading timezone information. This is not a good practice when concerned with the Android ecosystem [5]. The JAR resource is loaded via getResourceAsStream(), which has inefficient caching causing the process to use a lot of the available system memory.

To fix the memory issues caused from loading the JAR, there is a ThreeTen Android Backport library available which has the timezone information as a standard Android asset and provides a custom loader for parsing it efficiently.

What’s next?

Now that we’ve covered just some of the challenges (there are many, many more) of trying to use java.util.Date, we’ll look at how to use the ThreeTen Android Backport for better handling of date/time, parsing, and formatting in another future post.

References:

[1] https://docs.oracle.com/javase/7/docs/api/java/util/Date.html

[2] https://jcp.org/en/jsr/detail?id=310

[3] https://blog.joda.org/2009/11/why-jsr-310-isn-joda-time_4941.html

[4] https://www.threeten.org/threetenbp/apidocs/index.html

[5] https://blog.danlew.net/2013/08/20/joda_time_s_memory_issue_in_android/