Find your content:

Search form

You are here

Apex: Calculate hour ranges

 
Share

Given a start time (datetime) and a finish time (datetime) how can I easily calculate the number of hours that fell between 6:00am - 12:00pm (day hours) and 12:00 - 6:00am (night hours). They would be rounded up, so if there is any time at all in a given hour, that constitutes a complete hour.

So for example if your start time is 22:00 (military time here) and finish time is at 01:00 it would yield 2 day hours, 1 night hour. If instead the finish time was 1:10, that would yield 2 day hours and 2 night hours.

I'm pretty sure I could find a brute force solution to this, but I'm pretty sure there is an elegant type of solution that is escaping me.

Thanks!


Attribution to: Kenji776

Possible Suggestion/Solution #1

I don't know how elegant this is, but it makes the calculation simpler if I shift the hours "to the left" by 6. This eliminates a whole branch of logic, although it makes it a little more abstract.

//shift both times to the left, so day will go from 0 to 6, and night is 6 to 24
Integer starthour = StartTime.hour() - 6;
Integer endhour = EndTime.hour() - 6;

//add an hour for any minutes in the final hour
if (EndTime.Minutes() > 0) endhour++;
//if it ends at midnight, treat as 0
if (endHour == 24) endHour = 0;

//get full number of days
Integer FullDays = (EndTime.day() - StartTime.day())
if (endhour < starthour) FullDays -= 1;

//negative times are moved to the end of the night hours
if (starthour < 0) starthour += 24;
if (endhour < 0) endhour += 24;

if (starthour < endhour)
{
    dayhours = (starthour >= 6) ? 0 : ((endhour >= 6) ? (endhour - 6) : (endhour - starthour));
    nighthours = (starthour >= 6) ? (endhour - starthour) : ((endhour <= 6) ? 0 : (endhour - 6);
}

if (endhour < starthour)
{
    dayhours = (endhour >= 6) ? 6 : ((starthour >= 6) ? (6 - endhour) : ((6 - starthour) + endhour)));
    nighthours = (endhour >= 6) ? ((endhour - 6) + (24 - starthour)) : ((starthour >= 6) ? (24 - starthour) : 18;
}

dayhours += FullDays * 6;
nighthours += FullDays * 18;

Attribution to: Jeremy Nottingham

Possible Suggestion/Solution #2

for anyone interested, this is the final solution I came up with. The hardest part really was accounting for timespans that cross multiple days, especially when the finishing time was earlier than the starting time from the previous day.

public class timeCalc
{    
Public class hourCount
{
    public decimal nightHours = 0;
    public decimal dayHours = 0;
}

Public static hourCount TimeCalcHr(DateTime StartTime, Datetime EndTime)
{
    hourCount hours = new hourCount();
    //everything is in minutes
    decimal morningMinutes = 0;
    decimal eveningMinutes = 0;

    decimal startMinutes = StartTime.Hour() * 60 + StartTime.Minute();
    decimal endMinutes = EndTime.Hour() * 60 + EndTime.Minute();
    integer wholeDays = startTime.date().daysBetween(EndTime.date());

    //if end minutes is greater than start minutes, that means we can use regular calculations (presumable both are on the same day)
    if(endMinutes > startMinutes)
    {
        //evening minutes take place from 0 - 360
        if(startMinutes < 360)
        {
            eveningMinutes += math.MIN(endMinutes,360) - startMinutes;     
        }
        //morning minutes take place from 360 - 1440     
        if(endMinutes > 360)
        {   
             morningMinutes += endMinutes -  Math.max(360,startMinutes);
        }
        if (wholeDays > 0)
        {
            eveningMinutes += wholeDays * 6 * 60;
            morningMinutes += wholeDays * 18 * 60;
        }            
    } 
    //otherwise we need to calculate them a bit differently  
    else
    {
         eveningMinutes += math.MIN(endMinutes,360);  
         morningMinutes += 1440 - startMinutes;
         if (wholeDays > 0)
         {
            wholeDays--;
            eveningMinutes += wholeDays * 6 * 60;
            morningMinutes += wholeDays * 18 * 60;
         }               
    }

    system.debug('Evening Minutes: ' + eveningMinutes);
    system.debug('Morning Minutes: ' + morningMinutes);  


    hours.nightHours = math.ceil(eveningMinutes / 60);
    hours.dayHours = math.ceil(morningMinutes / 60);
    return hours;
}


@isTest
public static void testGetHours()
{
    //test a timespan that goes across both day and night hours
    Datetime startDate = datetime.newInstance(2008, 12, 1, 0, 0, 1);
    Datetime endTime = datetime.newInstance(2008, 12, 1, 18, 0, 1);

    hourCount hours = TimeCalcHr(startDate,  endTime);
    system.assertEquals(6, hours.nightHours);
    system.assertEquals(12,hours.dayHours);

    //add a day to the end time. This should grow the hours by 6 night hours, and 18 day hours in addition to the original 6 night and 12 day from the previous setup
    endTime = endTime.addDays(1);
    hours = TimeCalcHr(startDate,  endTime);
    system.assertEquals(12, hours.nightHours);
    system.assertEquals(30,hours.dayHours);      

    //add an hour to the end time. This should grow the hours by 0 night hours, and 1 day hour
    endTime = endTime.addHOurs(1);
    hours = TimeCalcHr(startDate,  endTime);
    system.assertEquals(12, hours.nightHours);
    system.assertEquals(31,hours.dayHours);    

    //now lets test for a small amount of night hours. 
    startDate = datetime.newInstance(2008, 12, 1, 0, 0, 1);
    endTime = datetime.newInstance(2008, 12, 1, 5, 0, 1);
    hours = TimeCalcHr(startDate,  endTime);
    system.assertEquals(5, hours.nightHours);        
    system.assertEquals(0, hours.dayHours);

    //now lets test both times starting in the day
    startDate = datetime.newInstance(2008, 12, 1, 8, 0, 1);
    endTime = datetime.newInstance(2008, 12, 1, 12, 0, 1);
    hours = TimeCalcHr(startDate,  endTime);
    system.assertEquals(0, hours.nightHours);        
    system.assertEquals(4, hours.dayHours);    

    //now lets test a time range that crosses midnight
    startDate = datetime.newInstance(2008, 12, 1, 22, 0, 1);
    endTime = datetime.newInstance(2008, 12, 2, 1, 0, 1);
    hours = TimeCalcHr(startDate,  endTime);

    system.assertEquals(1, hours.nightHours);        
    system.assertEquals(2, hours.dayHours);            

    //k now test with several days between with the last date crossing midnight
    startDate = datetime.newInstance(2008, 12, 1, 22, 0, 1);
    endTime = datetime.newInstance(2008, 12, 3, 1, 0, 1);
    hours = TimeCalcHr(startDate,  endTime);

    system.assertEquals(7, hours.nightHours);        
    system.assertEquals(20, hours.dayHours);           
}
}

Attribution to: Kenji776

Possible Suggestion/Solution #3

So you could do something like this:

Decimal MilliSeconds = EndTime.getTime() -StartTime.getTime() ; Decimal HourConvert = Millisecds / (1000.0*60.0*60.0)

When you use .getTime() it returns the number of Milliseconds since 1970.

Subtract the two to get the total milliseconds between the two dateTimes.

Then you take the milliseconds to calculate up to get your hours. 1000 (Seconds), * 60 (Minutes) * 60 (hours)

Edit Based On actually reading the questions correctly*

So the above gives you total time.

To get the Night/Day hours you need to break it up.

  1. Figure out Hours of Day and Night for the Start Time
  2. Figure out Hours of Day and Night for End Time
  3. Figure out Total WHOLE DAYS between Start and End time. Then you can split that number to get your Day/Night Hours.

So that's the basis of this. Below is a class (Quick and Dirty) to do just what I listed above. What's not included is the Rounding up of the values.

public class TimeCalc {

Public static string TimeCalcHr(DateTime InputStart, Datetime InputEnd){
    Datetime DayStart = datetime.newinstance(InputStart.Year(),InputStart.Month(), Inputstart.Day(),6,0,0);
    Datetime DayEnd = datetime.newinstance(InputStart.Year(),InputStart.Month(), Inputstart.Day(),12,0,0);

    Decimal StartingDaysMS = InputStart.getTime() - DayStart.getTime();
    Decimal StartingNightsMS = InputStart.getTime() - DayEnd.getTime();

    if(StartingDaysMS <0){
       StartingDaysMS =0;
    }
    if(StartingNightsMS <0){
        StartingNIghtsMS = 0;
    }
    //Repeat for End Date
    DayStart = datetime.newinstance(InputEnd.Year(),InputEnd.Month(), InputEnd.Day(),6,0,0);
    DayEnd = datetime.newinstance(InputEnd.Year(),InputEnd.Month(), InputEnd.Day(),12,0,0);

    Decimal EndingDaysMS = InputEnd.getTime() - DayStart.getTime();
    Decimal EndingNightsMS = InputEnd.getTime() - DayEnd.getTime();
    if(EndingDaysMS <0){
       EndingDaysMS =0;
    }
    if(EndingNightsMS <0){
        EndingNIghtsMS = 0;
    }
    //Figure out hours between
    decimal NightMSInADay = DayEnd.GetTime() - DayStart.getTime();
    Decimal DayMSInADay = DayStart.Adddays(1).getTime()-DayEnd.GetTime();
    //figure out number of days between times
    integer NumOfDays = InputStart.Date().daysBetween(InputEnd.Date()); 
    Decimal NumMSBetweenDays = NumOfDays * DayMSInADay;
    Decimal NumMSBetweenNights = NumOfDays * NightMSInADay;

    //Add Everything Togeter
    Decimal TotalNightMS = NumMSBetweenNights +  EndingNIghtsMS + StartingNightsMS;
    Decimal TotalDaysMS = NumMSBetweenDays+ EndingDaysMS+ StartingDaysMS;
    Decimal TotalNightHrs = TotalNightMs / (1000*60*60);
    Decimal TotalDayHrs = TotalDaysMS /  (1000*60*60);

    String Output = 'Night Hours: ' + string.Valueof(TotalNighthrs) + ' Day Hours: ' + string.valueof(TotalDayHrs);
    return Output;
}

}


Attribution to: Salesforce Wizard
This content is remixed from stackoverflow or stackexchange. Please visit https://salesforce.stackexchange.com/questions/784

My Block Status

My Block Content