Salesforce Apex Code for DateTime Duration

I've been developing a lot of batch Apex routines lately. The nature of batch Apex is that it can run whenever resources are available and process many records while being more flexible with platform governor limits. So I might schedule a job to run and it could take anywhere from a few minutes to many hours. Many variables impact the run time of a batch routine. Things like the number of records being processed or platform resource availability.

It must be my personality but I always like to know when a batch process has completed and I actually like to know how long it ran. Anyone familiar with batch Apex knows that there is a finish method, which is invoked upon completion of the batch logic. Within this method I will configure and send an email to myself with the start and end times of the batch process that finished.

Over time I must have gotten lazy because I had a desire to see the actual duration of the batch process instead of trying to count backwards between the end and start times that were included in my email messages.

So I took some time to put together a method to calculate the duration between two datetime values. I thought that the logic would be pretty straightforward but as I built it out I realized that there are some complexities. Therefore, I thought this would be a great topic for a post on this blog. Plus it's been a while since I wrote anything out here and this is all I can think of to write about today.

So the design is pretty simple. At a high level I need to determine how much time has passed between a starting date/time and an ending date/time. And I want the result to be in the form of a string that will make it easy to understand exactly how much time has passed.

Basically, I found some examples where developers calculated the duration in seconds or minutes. Initially this seems fine but as the run times for my batch routines got longer I got lazier and more frustrated because I had to now divide the resulting duration by 60 or whatever.

For my code in this post I literally want to be told exactly how many hours, minutes, seconds and even days have passed. This is what I will call "idiot proof" because the result will display the duration in an easy to read (and no need to convert down further) format.

The actual code for calculating the duration is below:

/*
	Created by: Greg Hacic
	Last Update: 5 August 2011 by Greg Hacic
	Questions?: greg@interactiveties.com
	Copyright (c) 2011 Interactive Ties LLC
	
	- Takes two passed DateTime variables and returns a string for the duration between the two datetimes in the format 'y Years d Days h Hours m Minutes s Seconds'
*/
public with sharing class time_calculation {
	
	public static String segment_text(String segment_string, Integer segment_integer, String prior_segments) {
		String return_string; //string for returning
		String spacer = ''; //string for holding an additional spacer
		if (segment_string != 'Second') { //if the segment being determined is not Seconds
			spacer = ' '; //create a spacer value
		}
		if (segment_integer > 1) { //if the value is greater than 1
			return_string = segment_integer.format()+' '+segment_string+'s'+spacer; //format
		} else if (segment_integer > 0) { //if the value is greater than 0
			return_string = segment_integer.format()+' '+segment_string+spacer; //format
		} else { //otherwise
			if (prior_segments != '' || segment_string == 'Second') { //if there is a value for prior segments or this is the seconds segment
				return_string = '0 '+segment_string+'s'+spacer; //format
			} else {
				return_string = ''; //set variable to null
			}
		}
		return return_string; //pass back the string
	}
	
	public static String duration_between_two_date_times(DateTime start_date_time, DateTime end_date_time) {
		Integer start_year_as_int = start_date_time.year(); //grab the start year
		Integer start_day_as_int = start_date_time.dayOfYear(); //grab the start day
		Integer start_hour_as_int = start_date_time.hour(); //grab the start hour
		Integer start_minute_as_int = start_date_time.minute(); //grab the start minute
		Integer start_second_as_int = start_date_time.second(); //grab the start second
		Integer start_in_seconds = (start_year_as_int * 31556926) + (start_day_as_int * 86400) + (start_hour_as_int * 3600) + (start_minute_as_int * 60) + (start_second_as_int * 1); //convert the start date to a value in seconds
		//there are 31556926 seconds in one year and that is why we are mutiplying the start_year_as_int value by 31556926 > this same logic applies to the days, hours & minutes logic which is why there are weird multipliers in that line of code
		Integer end_year_as_int = end_date_time.year(); //grab the end year
		Integer end_day_as_int = end_date_time.dayOfYear(); //grab the end day
		Integer end_hour_as_int = end_date_time.hour(); //grab the end hour
		Integer end_minute_as_int = end_date_time.minute(); //grab the end minute
		Integer end_second_as_int = end_date_time.second(); //grab the end second
		Integer end_in_seconds = (end_year_as_int * 31556926) + (end_day_as_int * 86400) + (end_hour_as_int * 3600) + (end_minute_as_int * 60) + (end_second_as_int * 1); //convert the end date to a value in seconds
		Integer total_duration_in_seconds = end_in_seconds - start_in_seconds; //duration in seconds
		Integer year_result = math.mod(math.floor(total_duration_in_seconds/31556926).intValue(),10000000); //number of years
		Integer day_result = math.mod(math.floor(total_duration_in_seconds/86400).intValue(),365); //number of days
		Integer hour_result = math.mod(math.floor(total_duration_in_seconds/3600).intValue(),24); //number of hours
		Integer minute_result = math.mod(math.floor(total_duration_in_seconds/60).intValue(),60); //number of minutes
		Integer second_result = math.mod(math.floor(total_duration_in_seconds/1).intValue(),60); //number of seconds
		
		String year_text_string = segment_text('Year', year_result, ''); //string variable for text regarding Year
		String day_text_string = segment_text('Day', day_result, year_text_string); //string variable for text regarding Day
		String hour_text_string = segment_text('Hour', hour_result, year_text_string + day_text_string); //string variable for text regarding Hour
		String minute_text_string = segment_text('Minute', minute_result, year_text_string + day_text_string + hour_text_string); //string variable for text regarding Minute
		String second_text_string = segment_text('Second', second_result, year_text_string + day_text_string + hour_text_string + minute_text_string); //string variable for text regarding Second
		String return_string = year_text_string + day_text_string + hour_text_string + minute_text_string + second_text_string;//concatenate all the strings into one for our resutling test string
		return return_string; //pass back the final string
	}

}

To get a decent understanding about what is happening you will need to run the following test class.

/*
	Created by: Greg Hacic
	Last Update: 5 August 2011 by Greg Hacic
	Questions?: greg@interactiveties.com
	Copyright (c) 2011 Interactive Ties LLC
    
	- Tests the time_calculation class
*/
@isTest
private class test_time_calculations {
	
	//test for the time_calculation methods
	static testMethod void test_duration() {
		//validate that multiple years works properly
		DateTime dt1 = DateTime.valueOf('2007-01-01 2:35:21'); //datetime start variable
		DateTime dt2 = DateTime.valueOf('2010-01-02 3:56:45'); //datetime end variable
		System.assertEquals('3 Years 1 Day 18 Hours 47 Minutes 42 Seconds', time_calculation.duration_between_two_date_times(dt1, dt2));
		
		//validate that single year works properly and 0 days too
		dt1 = DateTime.newInstance(2009, 1, 1, 2, 35, 21); //datetime start variable
		dt2 = DateTime.newInstance(2010, 1, 1, 2, 56, 45); //datetime end variable
		System.assertEquals('1 Year 0 Days 6 Hours 10 Minutes 10 Seconds', time_calculation.duration_between_two_date_times(dt1, dt2));
		
		//validate that 0 year works properly
		dt1 = DateTime.newInstance(2010, 1, 1, 2, 35, 21); //datetime start variable
		dt2 = DateTime.newInstance(2010, 1, 2, 4, 56, 45); //datetime end variable
		System.assertEquals('1 Day 2 Hours 21 Minutes 24 Seconds', time_calculation.duration_between_two_date_times(dt1, dt2));
		
		//validate that greater than 1 day works properly plus 1 second
		dt1 = DateTime.valueOf('2010-01-01 3:12:55'); //datetime start variable
		dt2 = DateTime.valueOf('2010-01-04 11:34:56'); //datetime end variable
		System.assertEquals('3 Days 8 Hours 22 Minutes 1 Second', time_calculation.duration_between_two_date_times(dt1, dt2));
		
		//validate that 1 hour, 1 minute and 0 days works properly
		dt1 = DateTime.valueOf('2010-06-21 13:12:55'); //datetime start variable
		dt2 = DateTime.valueOf('2010-06-21 14:14:23'); //datetime end variable
		System.assertEquals('1 Hour 1 Minute 28 Seconds', time_calculation.duration_between_two_date_times(dt1, dt2));
		
		//validate one second
		dt1 = DateTime.valueOf('2010-09-09 20:57:00'); //datetime start variable
		dt2 = DateTime.valueOf('2010-09-09 20:57:01'); //datetime end variable
		System.assertEquals('1 Second', time_calculation.duration_between_two_date_times(dt1, dt2));
		
		//valaidate that 0 hour works properly
		dt1 = DateTime.newInstance(2010, 1, 1, 2, 35, 21); //datetime start variable
		dt2 = DateTime.newInstance(2010, 1, 1, 2, 56, 45); //datetime end variable
		System.assertEquals('21 Minutes 24 Seconds', time_calculation.duration_between_two_date_times(dt1, dt2));
		
		//validate zero seconds
		dt1 = DateTime.valueOf('2010-09-09 20:57:00'); //datetime start variable
		dt2 = DateTime.valueOf('2010-09-09 20:57:00'); //datetime end variable
		System.assertEquals('0 Seconds', time_calculation.duration_between_two_date_times(dt1, dt2));
	}
}

As you can see I really do make an attempt at taking all of the downstream thinking out of the resulting string. If the duration spans days then the method will tell me the days, hours, minutes and seconds. If the duration only spans a few minutes then the resulting string is just that portion of the logic in he format of "M minutes and S seconds."

You may or may not have a practical need for the exact code I have posted here. However, from this code you may be able to come up with some sort of duration calculation that makes more sense for whatever you may be building.

Automated Exchange Rates in Salesforce.com

Reduce Repetitive Tasks, Eliminate Errors & Free Up Your Administrators.

Birthday Reminders for Salesforce.com

It might lead to a sale. Or it might make you feel good.