Monday, July 18, 2011

Groovy, Java, and the Unix Epoch Time

The link What Every Programmer Should Know about Time was recently posted on DZone and was a highly popular link. It references the original Emil Mikulic post Time and What Programmers Should Know About It. This post and its popularity was reminder to me of how often I find myself dealing with dates and times in software development and especially when writing scripts. I like many of Mikulic's recommendations because I feel that conventions and standards can keep the intricacies of working with time zones, Daylight Saving Time, and leap seconds/leap years somewhat manageable.

I have several times appreciated the fact that Java (and by extension, Groovy) base time against the same epoch as Unix. Java and Unix both use midnight, January 1, 1970 UTC/GMT as their epoch time. The Javadoc documentation for both of Java's standard "Date" classes (java.sql.Date and java.util.Date) talk about the significance of that date. The java.sql.Date documentation states, "A milliseconds value represents the number of milliseconds that have passed since January 1, 1970 00:00:00.000 GMT" while java.util.Date documentation on the constructor accepting a long states, "Allocates a Date object and initializes it to represent the specified number of milliseconds since the standard base time known as "the epoch", namely January 1, 1970, 00:00:00 GMT." The java.util.Date documentation also goes into a discussion of the subtle differences between GMT/UTC/UT.

Because Java (and Groovy) base their date/time APIs (Date, Date, and Calendar) off of the same epoch starting time as Unix, it is easier to deal with dates and times when writing Groovy scripts in a Linux environment. The most important thing to keep in mind when converting between the two is that Unix APIs tend to use seconds since the epoch time while Java's APIs tend to use milliseconds since that same epoch time. In short, the typical conversion is to multiply Unix's seconds since epoch by 1000 to get Java's milliseconds since epoch or divide Java's milliseconds since epoch by 1000 to get Unix's seconds since epoch.

I now show a couple of simple examples. It is easy to get a Unix example. The next screen snapshot shows the number of seconds since Unix epoch time for that last time that page was updated.


As the image above indicates, the integer 1310724379 represents "2011-07-15 10:06:19Z." This is also shown with the online Epoch Converter as shown in the next screen snapshot.


It's really easy to calculate the same time in Groovy from that integer. This is shown in the next screen snapshot.


The code for the above was so simple that I was able to invoke it simply via the command line Groovy launcher with the -e option. The inline Groovy code was fairly simple and is shown again here:

println new Date((1310724379 as long)*1000)

There were some minor things that needed to be done to convert the number used for Unix seconds since epoch to Java/Groovy's milliseconds since epoch. First, there was the obvious need to multiply the Unix number by 1000 to get from seconds to milliseconds. The Groovy coercion to a long is also necessary so that the numeral is not treated as an int, leading to the wrong time. The effect of this is shown in the next screen snapshot where the numeral by itself is shown leading to the wrong time and using the more traditional Java approach of coercing the numeral to a long with the "L designation. As the image indicates, coercion to a long is vital to perform the conversion correctly.




The Groovy example shows MST time rather than GMT. There are ways to address this in Java such as using DateFormat.setTimeZone(TimeZone) or Calendar.setTimeZone(TimeZone). One can use one of these approaches to get a UTC/GMT representation of the time.

In Java or Groovy development, it is easy to save dates and times in the database as such, but it can also be effective to simply store the integer representing the time since epoch in the database. When this approach is used, my preference is to always store UTC/GMT times. This avoids the need to store an offset to have the correct time unless the offset is desired to display the time in a local timezone. Understanding the Unix Epoch (in Java), Time Zones and UTC is a good post on Java's handling of dates/times in UTC format. That post references the post Current time and System.currentTimeMillis() as a good source of information on how date/time storage works in Java. Making Sense of Java's Dates is another good source of information on Java and dates.

It is easy to determine what Java and Groovy use as their epoch time without going to the documentation. The following Groovy script does just that.

displayZeroEpochTime.groovy
#!/usr/bin/env groovy
def zeroEpoch = Calendar.getInstance(TimeZone.getTimeZone('GMT'))
zeroEpoch.setTimeInMillis(0)
println zeroEpoch.format("dd-MMM-yyyy HH:mm:ss zzz")

The output of it is shown in the next screen snapshot and verifies that 0 milliseconds since the epoch time means January 1, 1970, 00:00:00 GMT.


If Java/Groovy and Unix had different epoch times, one could calculate the difference between their starting points and continually apply that difference in conversions between the two systems. However, by using the same epoch time as Unix, Java's original creators have made it easier for those of us writing Groovy scripts that deal with time to integrate with the Unix time handling functions. That definitely makes scripting in a Linux environment easier.

3 comments:

Martijn Verburg said...

I still prefer Joda time to deal with data and time issues, it'll be great when JSR-310 comes out and all of the JVM languages can utilize it :-).

Dustin said...

Martijn,

I definitely agree with that. Although we've all learned how to deal with Java's standard date/time "nuances," it'd be nice to not have to learn and practice these "dark arts" and be able to use a more straightforward API. I hope that Java 8 will finally deliver that.

Dustin

Steve Allen said...

I hold little hope for complete standardization, as the javascript shows the international legal situation does not permit a single API to give the answer.