Thursday, August 16, 2007

How do I… Perform date/time arithmetic with Java’s Calendar class?

Java’s Calendar class offers a set of methods for converting and manipulating temporal information. In addition to retrieving the current date and time, the Calendar class also provides an API for date arithmetic. The API takes care of the numerous minor adjustments that have to be made when adding and subtracting intervals to date and time values.

Calendar’s built-in date/time arithmetic API is extremely useful. For example, consider the number of lines of code that go into calculating what the date will be five months from today. Try doing this yourself — you need to know the number of days in the current month and in the intervening months, as well as make end-of-year and leap year modifications to arrive at an accurate final result. These kinds of calculations are fairly complex and are quite easy to get wrong — especially if you’re a novice developer.

This tutorial examines the Calendar class API and presents examples of how you can use Calendar objects to add and subtract time spans to and from dates and times, as well as how to evaluate whether one date precedes or follows another.

Adding time spans

Let’s say you want to add a time span to a starting date and print the result. Consider the following example, which initializes a Calendar to 01 Jan 2007 and then adds two months and one day to it to obtain a new value:

package datetime;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;

public class myClass
{
public static void main(String[] args)
{
myClass tdt = new myClass();
tdt.doMath();
}

/**
* method to create a calendar object, add 2m 1d, and print result
*/
private void doMath()
{
// set calendar to 1 Jan 2007
Calendar calendar = new GregorianCalendar(2007,Calendar.JANUARY,1);
System.out.println("Starting date is: ");
printCalendar(calendar);

// add 2m 1d
System.out.println("Adding 2m 1d... ");
calendar.add(Calendar.MONTH,2);
calendar.add(Calendar.DAY_OF_MONTH,1);

// print ending date value
System.out.println("Ending date is: ");
PrintCalendar(calendar);
}

/**
* utility method to print a Calendar object using SimpleDateFormat.
* @param calendar calendar object to be printed.
*/
private void printCalendar(Calendar calendar)
{
// define output format and print
SimpleDateFormat sdf = new SimpleDateFormat("d MMM yyyy hh:mm aaa");
String date = sdf.format(calendar.getTime());
System.out.println(date);
}
}

The main workhorse of this class is the doMath() method, which begins by initializing a new GregorianCalendar object to 1 Jan 2007. Next, the object’s add() method is invoked; this method accepts two arguments: the name of the field to add the value to and the amount of time to be added. In this example, the add() method is called twice — first to add two months to the starting date and then to add a further one day to the result. Once the addition is performed, the printCalendar() utility method is used to print the final result. Notice the use of the SimpleDateFormat object to turn the output of getTime() into a human-readable string.

When you run the class, this is the output you’ll see:

Starting date is:
1 Jan 2007 12:00 AM
Adding 2m 1d...
Ending date is:
2 Mar 2007 12:00 AM

This kind of addition also works with time values. To illustrate, consider the next example, which adds 14 hours and 55 minutes to a starting time value:

package datetime;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;

public class myClass
{
public static void main(String[] args)
{
myClass tdt = new myClass();
tdt.doMath();
}

/**
* method to create a calendar object, add 14h 55min, and print result
*/
private void doMath()
{
// set calendar to 1 Jan 2007
Calendar calendar = new GregorianCalendar(2007,Calendar.JANUARY,1, 1,0);
System.out.println("Starting date is: ");
printCalendar(calendar);

// add 14h 55min
System.out.println("Adding 14h 55min... ");
calendar.add(Calendar.HOUR,14);
calendar.add(Calendar.MINUTE,55);

// print final value
System.out.println("Ending date is: ");
printCalendar(calendar);
}

/**
* utility method to print a Calendar object using SimpleDateFormat.
* @param calendar calendar object to be printed.
*/
private void printCalendar(Calendar calendar)
{
// define output format and print
SimpleDateFormat sdf = new SimpleDateFormat("d MMM yyyy hh:mm aaa");
String date = sdf.format(calendar.getTime());
System.out.println(date);
}
}

This is almost identical to the previous class except that the calls to add() involve the calendar’s hour and minute fields. Here’s the output:

Starting date is:
1 Jan 2007 01:00 AM
Adding 14h 55min…
Ending date is:
1 Jan 2007 03:55 PM

Tip: You can obtain a complete list of the calendar constants that can be used with add() from the Calendar class’ documentation.
Subtracting time spans

Subtraction is fairly easy as well — you simply use negative values as the second argument to add(). Here’s an example:

package datetime;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;

public class myClass
{
public static void main(String[] args)
{
myClass tdt = new myClass();
tdt.doMath();
}

/**
* method to create a calendar object, subtract time, and print result
*/
private void doMath()
{
// initialize calendar
Calendar calendar = new GregorianCalendar(2007,Calendar.JANUARY,2, 3,30);
System.out.println("Starting date is: ");
printCalendar(calendar);

// subtract 1y 1d 4h 5min
System.out.println("Subtracting 1y 1d 4h 5min... ");
calendar.add(Calendar.YEAR,-1);
calendar.add(Calendar.DAY_OF_MONTH,-1);
calendar.add(Calendar.HOUR,-4);
calendar.add(Calendar.MINUTE,-5);

// print result
System.out.println("Ending date is ");
printCalendar(calendar);
}

/**
* utility method to print a Calendar object using SimpleDateFormat.
* @param calendar calendar object to be printed.
*/
private void printCalendar(Calendar calendar)
{
// define output format and print
SimpleDateFormat sdf = new SimpleDateFormat("d MMM yyyy hh:mm aaa");
String date = sdf.format(calendar.getTime());
System.out.println(date);
}
}

Here’s the output:

Starting date is:
2 Jan 2007 03:30 AM
Subtracting 1y 1d 4h 5min...
Ending date is
31 Dec 2005 11:25 PM

In this example, the Calendar object automatically takes care of adjusting the year and the day when the subtraction results in the date “overflowing” from 1 Jan 2006 to 31 Dec 2005.
Adding vs. rolling

As the previous example illustrates, the add() method automatically takes care of rolling over days, months, and years when a particular calendar field “overflows” as a result of addition or subtraction. However, this behavior is often not what you want. In those situations, the Calendar object also has a roll() method, which avoids incrementing or decrementing larger calendar fields when such overflow occurs. To see how this works, look at the following example:

package datetime;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;

public class myClass
{
public static void main(String[] args)
{
myClass tdt = new myClass();
tdt.doAdd();
tdt.doRoll();
}

/**
* method to create a calendar object, add 1m, and print result
*/
private void doAdd()
{
// initialize calendar
Calendar calendar = new GregorianCalendar(2006, Calendar.DECEMBER,1);
System.out.println("Starting date is: ");
printCalendar(calendar);

System.out.println("After add()ing 1 month, ending date is: ");
calendar.add(Calendar.MONTH, 1);
printCalendar(calendar);
}

/**
* method to create a calendar object, roll 1m, and print result
*/
private void doRoll()
{
// initialize calendar
Calendar calendar = new GregorianCalendar(2006, Calendar.DECEMBER,1);
System.out.println("Starting date is: ");
printCalendar(calendar);

System.out.println("After roll()ing 1 month, ending date is: ");
calendar.roll(Calendar.MONTH, 1);
printCalendar(calendar);
}

/**
* utility method to print a Calendar object using SimpleDateFormat.
* @param calendar calendar object to be printed.
*/
private void printCalendar(Calendar calendar)
{
// define output format and print
SimpleDateFormat sdf = new SimpleDateFormat("d MMM yyyy hh:mm aaa");
String date = sdf.format(calendar.getTime());
System.out.println(date);
}
}

Here’s the output:

Starting date is:
1 Dec 2006 12:00 AM
After add()ing 1 month, ending date is:
1 Jan 2007 12:00 AM

Starting date is:
1 Dec 2006 12:00 AM
After roll()ing 1 month, ending date is:
1 Jan 2006 12:00 AM

In the first case, when one month is added to the starting date of 1 Dec 2006, the add() method realizes that a year change will occur as a result of the addition, and the year is rolled over to 2007. When using roll(), this behavior is disabled, and only the month field is incremented by 1, with the year change ignored. Depending on the requirements of your application, you may find such restricted changes useful in certain situations.
Checking date precedence

The Calendar object also includes the compareTo() method, which lets you compare two dates to find out which one comes earlier. The compareTo() method accepts another Calendar object as an input argument and returns a value less than zero if the following conditions are true:

* The date and time of the input Calendar object is later than that of the calling Calendar object.
* A value greater than zero if the reverse is true.
* A value of 0 if the two Calendar objects represent the same date.

Here’s an example that compares 1 Jan 2007 12:00 AM and 1 Jan 2007 12:01 AM with compareTo():

package datetime;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;

public class myClass
{
public static void main(String[] args)
{
// initialize two calendars
Calendar calendar1 = new GregorianCalendar(2007,Calendar.JANUARY,1,0,0,0);
Calendar calendar2 = new GregorianCalendar(2007,Calendar.JANUARY,1,0,1,0);

// define date format
String date1 = null;
String date2 = null;
SimpleDateFormat sdf = new SimpleDateFormat("d MMM yyyy hh:mm aaa");

// compare dates
if((calendar1.compareTo(calendar2)) < 0)
{
date1 = sdf.format(calendar1.getTime());
date2 = sdf.format(calendar2.getTime());
}
else
{
date1 = sdf.format(calendar2.getTime());
date2 = sdf.format(calendar1.getTime());
}
System.out.println(date1 + " occurs before " + date2);
System.out.println(date2 + " occurs after " + date1);
}
}

Here’s the output:

1 Jan 2007 12:00 AM occurs before 1 Jan 2007 12:01 AM
1 Jan 2007 12:01 AM occurs after 1 Jan 2007 12:00 AM

This example uses two Calendar objects and a little date arithmetic:

package datetime;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;

public class myClass
{
public static void main(String[] args)
{
// initialize two calendars
Calendar calendar1 = new GregorianCalendar(2007,Calendar.FEBRUARY,16,0,0,0);
Calendar calendar2 = new GregorianCalendar(2007,Calendar.FEBRUARY,18,0,1,0);

// define date format
SimpleDateFormat sdf = new SimpleDateFormat("d MMM yyyy hh:mm aaa");

// add 2d to calendar #1
calendar1.add(Calendar.DAY_OF_MONTH, 2);

// subtract 1min from calendar #2
calendar2.add(Calendar.MINUTE, -1);

// compare dates
String date1 = sdf.format(calendar1.getTime());
String date2 = sdf.format(calendar2.getTime());
if((calendar1.compareTo(calendar2)) < 0)
{
System.out.println(date1 + " occurs before " + date2);
}
else if((calendar1.compareTo(calendar2)) > 0)
{
System.out.println(date1 + " occurs after " + date2);
}
else
{
System.out.println("The two dates are identical: " + date1);
}
}
}

Although both calendars start out differently, they’re converted to the same time stamp through a bit of date arithmetic. This is verified via the compareTo() method, which returns 0 when asked to compare them, indicating that they represent the same instant in time:

The two dates are identical: 18 Feb 2007 12:00 AM

Imagine the possibilities

The Calendar class’ date/time API is a nifty tool for accurately manipulating date and time values without too much stress or custom coding. The examples in this article are just some of many possibilities this API opens up — the rest is up to your imagination.

No comments: