Calendar and Timestamp Duration Determination Method in Java

If you are interested in determining the duration between two dates in Java, you will find few, if any, examples of how one goes about performing this task. There are a number of Java duration classes, such as the Android Duration class that allow you to define a duration and add it to a Calendar instance, but how to determine a duration is harder to find.

The task itself is relatively easy to accomplish by using the two dates as Calendar instances. The YEAR, DAY_OF_YEAR, HOUR_OF_DAY, and MINUTE fields can be used to obtain the corresponding values for each of the Calender instances, which can then be subtracted from one another. Next we correct for the negative values and leap years. Finally, we format the results as a string.

I have implemented this as a static method duration in the Java class CalendarUtilites, shown below. I have also provided a JUnit test class, CalendarUtilitiesTests, at the bottom of the page.

CalendarUtilities: Calendar Utilities with Duration Determination Method

package com.colabrativ.common.server.utility;

import java.sql.Timestamp;
import java.util.Calendar;

/**
 * Calendar Utility Class
 *
 * @author Marc Whitlow
 *         Colabrativ, Inc.
 *         http://www.colabrativ.com
 */
 public class CalendarUtilities {

    /**
     * Determine the duration between the start and end dates.
     *
     * @param startDate Duration starting date
     * @param endDate Duration end date
     *
     * @return String describing the time between the start and end dates,
     * e.g. 2 years 47 days 6 hours and 1 minute.
     */
    public static String duration(Timestamp startDate, Timestamp endDate) {

        Calendar startCalendar = Calendar.getInstance();
        Calendar endCalendar   = Calendar.getInstance();
        startCalendar.setTime( startDate);
        endCalendar  .setTime( endDate);

        return duration(startCalendar, endCalendar);
    }

    /**
     * Determine the duration between the start and end dates.
     *
     * @param startCalendar Duration starting date
     * @param endCalendar Duration end date
     *
     * @return String describing the time between the start and end dates,
     * e.g. 2 years 1 day 6 hours and 23 minutes.
     */
    public static String duration(Calendar startCalendar, Calendar endCalendar)
    {
        int years 	= endCalendar.get(Calendar.YEAR) 		- startCalendar.get(Calendar.YEAR);
        int days 	= endCalendar.get(Calendar.DAY_OF_YEAR) - startCalendar.get(Calendar.DAY_OF_YEAR);
        int hours 	= endCalendar.get(Calendar.HOUR_OF_DAY) - startCalendar.get(Calendar.HOUR_OF_DAY);
        int mins 	= endCalendar.get(Calendar.MINUTE) 		- startCalendar.get(Calendar.MINUTE);

        if (mins < 0) {
            hours = hours - 1;
            mins  = mins + 60;
        }

        if (hours < 0) {
            days  = days - 1;
            hours = hours + 24;
        }

        // Leap year corrections
        int daysInYear = 365;
        Calendar leapYear = Calendar.getInstance();
        leapYear.set( startCalendar.get(Calendar.YEAR), 11, 31, 23, 59, 59);
        if (leapYear.get(Calendar.DAY_OF_YEAR) == 366) {
            leapYear.set( startCalendar.get(Calendar.YEAR), 1, 29, 23, 59, 59);
            if (startCalendar.before(leapYear))
                daysInYear = 366;
        }

        leapYear.set( endCalendar.get(Calendar.YEAR), 11, 31, 23, 59, 59);
        if (leapYear.get(Calendar.DAY_OF_YEAR) == 366) {
            leapYear.set( endCalendar.get(Calendar.YEAR), 1, 29, 23, 59, 59);
            if (endCalendar.after(leapYear)) {
                daysInYear = 366;
                if (years > 0)
                    days = days - 1;
            }
        }

        if (days < 0) {
            years--;
            days = days + daysInYear;
        }

        StringBuilder durationSB = new StringBuilder();
        if (years > 0) {
            durationSB.append( years);
            addUnits( durationSB, years, "year");
        }

        if (days > 0) {
            durationSB.append( days);
            addUnits( durationSB, days, "day");
        }

        if (hours > 0) {
            durationSB.append( hours);
            addUnits( durationSB, hours, "hour");
        }

        if (mins > 0) {
            durationSB.append( mins);
            addUnits( durationSB, mins, "minute");
        }

        return durationSB.toString();
    }

    private static void addUnits( StringBuilder stringBuilder, int value, String units) {

        stringBuilder.append( " ");
        if (value == 1)
            stringBuilder.append( units + " ");
        else
            stringBuilder.append( units + "s ");
    }
}

This static class does not create a duration object. It simply returns a string that describes the duration. It would be fairly straightforward to wrap the duration method in a Duration class, similar to Android Duration.

The other thing this class does not do is to output the number of weeks. I chose to output the number of days instead of the number of weeks and days, because in practice most people speak of 60 and 90 day events and not 8 weeks and 4 days, or 12 weeks and 6 days. It is relatively easy to convert from days to weeks and days. You simply divide the number of days by 7 to get weeks and subtract seven times the number of weeks from days. Then, you need to add the weeks information to the StringBuilder durationSB. This code addition, shown in the snippet below, is placed after the years information has been written to the StringBuilder, and before the days information is written to the StringBuilder.

Days to Weeks and Days Code Snippet

        int weeks = days / 7;
        days = days - (7 * weeks);

        if (days > 0) {
        durationSB.append( weeks);
        addUnits( durationSB, weeks, "week");
        }

To complete the addition of weeks, the assertEquals statements in the CalendarUtilitiesTests class, shown below, would need to be updated.

CalendarUtilitiesTests: JUnit Testing of the Calendar Duration Utility

package com.colabrativ.common.server.utility;

import java.sql.Timestamp;
import java.util.Calendar;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.*;

/**
 * Unit testing of the CalendarUtilities class.
 *
 * @author Marc Whitlow
 *         Colabrativ, Inc.
 *         http://www.colabrativ.com
 */
public class CalendarUtilitiesTests {

    @Before
    public void setUp() {}

    @Test
    public void timestampDurationTests()
    {
        // A minute difference
        Calendar calendar = Calendar.getInstance();
        calendar.set( 2011, 6, 22, 9, 34, 18);   // 2011-07-22 09:34:18
        Timestamp startDate = new Timestamp( calendar.getTimeInMillis());
        calendar.set( 2011, 6, 22, 9, 35, 18);   // 2011-07-22 09:35:18
        Timestamp endDate = new Timestamp( calendar.getTimeInMillis());
        assertEquals( "1 minute ", CalendarUtilities.duration( startDate, endDate));

        // An hour and 45 minutes
        calendar.set( 2011, 6, 22, 11, 19, 59);  // 2011-07-22 11:19:59
        endDate = new Timestamp( calendar.getTimeInMillis());
        assertEquals( "1 hour 45 minutes ", CalendarUtilities.duration( startDate, endDate));

        // A day 8 hours and 30 minutes
        calendar.set( 2011, 6, 23, 18, 04, 59);  // 2011-07-23 18:04:59
        endDate = new Timestamp( calendar.getTimeInMillis());
        assertEquals( "1 day 8 hours 30 minutes ", CalendarUtilities.duration( startDate, endDate));

        // 60 days 18 hours and 30 minutes
        calendar.set( 2011, 8, 21, 4, 04, 0);   // 2011-09-21 04:30:00
        endDate = new Timestamp( calendar.getTimeInMillis());
        assertEquals( "60 days 18 hours 30 minutes ", CalendarUtilities.duration( startDate, endDate));

        // Before leap day: 184 days 14 hours 25 minutes
        calendar.set( 2011, 6, 22, 9, 34, 18);   // 2011-07-22 09:34:18
        startDate = new Timestamp( calendar.getTimeInMillis());
        calendar.set( 2012, 0, 22, 23, 59, 59);  // 2012-01-22 23:59:59
        endDate = new Timestamp( calendar.getTimeInMillis());
        assertEquals( "184 days 14 hours 25 minutes ", CalendarUtilities.duration( startDate, endDate));

        // Over a leap day: 183 days 8 hours 1 minute
        calendar.set( 2011, 9, 8, 15, 58, 44);  // 2011-10-08 15:58:44
        startDate = new Timestamp( calendar.getTimeInMillis());
        calendar.set( 2012, 3, 8, 23, 59, 59);  // 2012-04-08 23:59:59
        endDate = new Timestamp( calendar.getTimeInMillis());
        assertEquals( "183 days 8 hours 1 minute ", CalendarUtilities.duration( startDate, endDate));

        // One year
        calendar.set( 2012, 9, 8, 23, 59, 59);  // 2012-10-08 23:59:59
        endDate = new Timestamp( calendar.getTimeInMillis());
        assertEquals( "1 year 8 hours 1 minute ", CalendarUtilities.duration( startDate, endDate));

        // Several years
        calendar.set( 2017, 5, 22, 15, 0, 0);  // 2017-06-22 15:00:00
        endDate = new Timestamp( calendar.getTimeInMillis());
        assertEquals( "5 years 256 days 23 hours 2 minutes ", CalendarUtilities.duration( startDate, endDate));
    }

    @Test
    public void calenderDurationTests() {

        // A minute difference
        Calendar startCalendar = Calendar.getInstance();
        Calendar endCalendar = Calendar.getInstance();
        startCalendar.set( 2011, 6, 22, 9, 34, 18);   // 2011-07-22 09:34:18
        endCalendar  .set( 2011, 6, 22, 9, 35, 18);   // 2011-07-22 09:35:18
        assertEquals( "1 minute ", CalendarUtilities.duration( startCalendar, endCalendar));

        // An hour and 45 minutes
        endCalendar.set( 2011, 6, 22, 11, 19, 59);  // 2011-07-22 11:19:59
        assertEquals( "1 hour 45 minutes ", CalendarUtilities.duration( startCalendar, endCalendar));

        // A day 8 hours and 30 minutes
        endCalendar.set( 2011, 6, 23, 18, 04, 59);  // 2011-07-23 18:04:59
        assertEquals( "1 day 8 hours 30 minutes ", CalendarUtilities.duration( startCalendar, endCalendar));

        // 60 days 18 hours and 30 minutes
        endCalendar.set( 2011, 8, 21, 4, 04, 0);   // 2011-09-21 04:30:00
        assertEquals( "60 days 18 hours 30 minutes ", CalendarUtilities.duration( startCalendar, endCalendar));

        // Before leap day: 184 days 14 hours 25 minutes
        startCalendar.set( 2011, 6, 22, 9, 34, 18);   // 2011-07-22 09:34:18
        endCalendar  .set( 2012, 0, 22, 23, 59, 59);  // 2012-01-22 23:59:59
        assertEquals( "184 days 14 hours 25 minutes ", CalendarUtilities.duration( startCalendar, endCalendar));

        // Over a leap day: 183 days 8 hours 1 minute
        startCalendar.set( 2011, 9, 8, 15, 58, 44);  // 2011-10-08 15:58:44
        endCalendar  .set( 2012, 3, 8, 23, 59, 59);  // 2012-04-08 23:59:59
        assertEquals( "183 days 8 hours 1 minute ", CalendarUtilities.duration( startCalendar, endCalendar));

        // One year
        endCalendar.set( 2012, 9, 8, 23, 59, 59);  // 2012-10-08 23:59:59
        assertEquals( "1 year 8 hours 1 minute ", CalendarUtilities.duration( startCalendar, endCalendar));

        // Several years
        endCalendar.set( 2017, 5, 22, 15, 0, 0);  // 2017-06-22 15:00:00
        assertEquals( "5 years 256 days 23 hours 2 minutes ", CalendarUtilities.duration( startCalendar, endCalendar));
    }

    @After
    public void tearDown() {}
}
This entry was posted in Technical and tagged , , , . Bookmark the permalink.

Comments are closed.