Understanding Java Calendar Object
This is the first of a series of posts I will dedicate to Calendar object in Java and how it interacts with JDBC API. Before moving onto JDBC API let's make sure we get the Calendar basics right. Java.util package contains 4 temporal classes: Calendar, Date, Timezone and Gregorian Calendar. Originally, Date class contained methods that performed conversions between date values and year/month/day/hour/minute/second values. However, because Date is not timezone aware, converting Date values between timezones proved to be erroneous. Today these methods are deprecated and replaced by corresponding Calendar methods. Indeed, if you ever want to manipulate time in Java across timezones different than your local timezone, I suggest sticking to Calendar objects.
Let's start with a simple example to convey how calendar conversions are carried out. Suppose I live in Seattle (which I do:-) and suppose I have an aunt who lives in New York. My local time is "2008-06-18 21:28:08.963" and I want to know if it is too late to call my aunt. I will use the Calendar object to calculate what time it is in New York.
SimpleDateFormat _formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
java.util.Date date = _formatter.parse("2008-06-18 21:28:08.963");
System.out.println("Seattle time : " + date);
Notice that I used a SimpleDateFormat object to create the java.util.Date value. SimpleDateFormat is a locale sensitive class that converts between date and text values. You can specify the locale while constructing this class. Since locale was omitted in this example, SimpleDateFormat assumes the default locale, PST for Seattle.
Now that I have a date that corresponds to my local time I can convert it to New York time. It should be as simple as the following.
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("America/New_York "));
cal.setTimeInMillis(date.getTime());
System.out.println("New York time : " + cal.getTime());
If you actually run this code snippet you will realize that the times printed are identical. Why would that be? The problem lies in how time is represented by a Date object. When you use a formatter to parse a textual date value (as we did above), you are actually retrieving the total number of milliseconds that passed since the epoch in UTC, midnight of January 1st 1970. Both getTime and setTimeInMillis operations are dealing with these milliseconds, also called fasttime. When you use toString() method of Date object you are essentially printing out the time in your local timezone when given number of milliseconds have passed since UTC. Hence, all cal.getTime() represents here is the total number of milliseconds that passed since UTC epoch (in New York) when X amount of millseconds passed since UTC epoch in Seattle. The answer, of course, would be X regardless of timezones.
Here is one simple solution to this conversion problem. Instead of dealing with milliseconds and timezone unaware Date object, retrieve the date/time values directly.
System.out.println("New York time : " + cal.get(Calendar.YEAR) + "-" + (1 + cal.get(Calendar.MONTH)) + "-" + cal.get(Calendar.DAY_OF_MONTH) + " " + cal.get(Calendar.HOUR_OF_DAY) + ":" + cal.get(Calendar.MINUTE) + ":" + cal.get(Calendar.SECOND) + "." + cal.get(Calendar.MILLISECOND));
You will receive "2008-6-19 0:28:8.893". 3 hours later than my local time as expected. Make sure to increment the month field by 1 since Calendar month is 0 based.
A more devious solution would be to print out the time in New York using our local timezone. This solution could be useful only for printing/playing purposes. It alters the underlying milliseconds since we are now calculating a completely different time in our local timezone. Partial code snippet below.
Calendar cal2 = Calendar.getInstance();
cal2.set(Calendar.YEAR, cal.get(Calendar.YEAR));
cal2.set(Calendar.MONTH, cal.get(Calendar.MONTH));
cal2.set(Calendar.DAY_OF_MONTH, cal.get(Calendar.DATE));
...
System.out.println("New York time : " + cal2.getTime());
It is indeed too late to call an aunt. Please join us next time for how to compare java.sql.date and java.sql.time values.