Saturday, February 14, 2009

Java Date/Time Objects with JSR-310

I spent the day experimenting with the proposed javax.time API's from JSR-310. Modeled after the popular joda-time the new javax.time objects offer a rich set of date and time manipulation, basic math, durations, matching and time zone awareness.

At the top level, the concept of a TimeSource is introduced. This is a replacement for using System.currentTimeMillis() or System.timeNanos() or the standard java.util.Date and Calendar contructors. It can be spring loaded, so getting a source that has an offset is possible, which makes testing very easy.

The abstract Clock class is similar to TimeSource, but it is time zone aware. It's easy to get the local time (Clock.systemDefaultZone()) or to create a clock from a different time zone (Clock.system(TimeZone.UTC)).

Once the Clock object is created you can access today(), tomorrow(), yesterday(), the current DateTime and other methods for creating complex DateTime objects. You can also get the current instant which represents the current date/time as an instant since the epoch in nanoseconds.

The first step to using javax.time is to checkout the latest version from subversion. (I'm at build version 702).

Converting Date and Calendar to javax.time

A common way to construct both Date and Calendar from the java.util package is to use milliseconds. The Instant class from javax.time includes a method called toEpochMillis() that makes this easy: (groovy syntax)

utilDate = new java.util.Date()
instant = Instant.millisInstant( utilDate.time )
zone = ZoneOffset.zoneOffset( -8 )
dateTime = OffsetDateTime.dateTime( instant, zone )

Converting javax.time to Date/Calendar

To convert a javax.time object back to either Date or Calendar is just as simple:

ts = TimeSource.system()
dt = new Date( ts.instant().toEpochMillis() )

cal = Calendar.instance
cal.timeInMillis = ts.instant().toEpochMillis()

Date Math

Here is a quick example of what you can do with a DateTime object. In this case, I'll use an OffsetDateTime to express the full ISO8601 date. (again, groovy syntax)

clock = Clock.systemDefaultZone()
assert == "2009-02-14"
assert clock.tomorrow().toString() == "2009-02-14"

dt = clock.offsetDateTime()
assert dt.toString() == "2009-02-14T20:31:50.896-08:00"

dt.plusYears(3).toString() == "2012-02-14T20:31:50.896-08:00

assert dt.year == 2009
year = dt.toYear() // get the object, not the int

assert year.lengthInDays() == 365
assert == 2010
assert year.nextLeap().value == 2012
assert year.nextLeap().lengthInDays() == 366

yearMonth = YearMonth.yearMonth( dt )
assert yearMonth.toString() == "2009-02"
assert yearMonth.plusMonths(5).toString() == "2009-07"
assert yearMonth.minusMonths(5).toString() == "2008-09"

So you get the point. Many useful methods currently missing from the java JDK. So, hopefully this will be complete in time to make it into java 7. In the mean time, though not ready for production, it's available for bleeding-edge use.

No comments: