문제

I need to determine duration between two DateTimes in minutes.

However, there is a slight twist:

  • exclude weekends
  • only count minutes which are between 7:00AM and 7:00PM. for example: [09/30/2010 6:39:00 PM] - [09/30/2010 7:39:00 PM] = 21 Minutes

I'm just having a hard time coming up with a decent way to do it and would appreciate if anyone can suggest something.

Thanks.


Edit:

I ended up going with dtb's solution. There is only one special case which needed to be taken care of: if end time is after 7:00PM, count the minutes from 7:00AM to the actual end time.

This is how I modified it:

var minutes = from day in start.DaysInRangeUntil(end)
                where !day.IsWeekendDay()
                let st = Helpers.Max(day.AddHours(7), start)
                let en = (day.DayOfYear == end.DayOfYear ? 
                            end :
                            Helpers.Min(day.AddHours(19), end)
                            )
                select (en - st).TotalMinutes;

Again, thanks for the help.

도움이 되었습니까?

해결책

You can, of course, use LINQ:

DateTime a = new DateTime(2010, 10, 30, 21, 58, 29);
DateTime b = a + new TimeSpan(12, 5, 54, 24, 623);

var minutes = from day in a.DaysInRangeUntil(b)
              where !day.IsWeekendDay()
              let start = Max(day.AddHours( 7), a)
              let end   = Min(day.AddHours(19), b)
              select (end - start).TotalMinutes;

var result = minutes.Sum();

// result == 6292.89

(Note: You probably need to check for a lot of corner cases which I completely ignored.)

Helper methods:

static IEnumerable<DateTime> DaysInRangeUntil(this DateTime start, DateTime end)
{
    return Enumerable.Range(0, 1 + (int)(end.Date - start.Date).TotalDays)
                     .Select(dt => start.Date.AddDays(dt));
}

static bool IsWeekendDay(this DateTime dt)
{
    return dt.DayOfWeek == DayOfWeek.Saturday
        || dt.DayOfWeek == DayOfWeek.Sunday;
}

static DateTime Max(DateTime a, DateTime b)
{
    return new DateTime(Math.Max(a.Ticks, b.Ticks));
}

static DateTime Min(DateTime a, DateTime b)
{
    return new DateTime(Math.Min(a.Ticks, b.Ticks));
}

다른 팁

Take your start time, get the amount of minutes to the end of that day (Ie the 7pm).

Then from the 7am the next day, count the amount of days to the final day (excluding any time into the end day).

Calculate how many (If any) weekends have passed. (For every weekends reduce the count of days by 2).

Do some simple math from there to get the total minutes for the count of days.

Add the extra time on the final day and the start days extra time.

Try the following DiffRange function.

public static DateTime DayStart(DateTime date)
{
    return date.Date.AddHours(7);
}

public static DateTime DayEnd(DateTime date)
{
    return date.Date.AddHours(19);
}

public static TimeSpan DiffSingleDay(DateTime start, DateTime end)
{
    if ( start.Date != end.Date ) {
        throw new ArgumentException();
    }

    if (start.DayOfWeek == DayOfWeek.Saturday || start.DayOfWeek == DayOfWeek.Sunday )
    {
        return TimeSpan.Zero;
    }

    start = start >= DayStart(start) ? start : DayStart(start);
    end = end <= DayEnd(end) ? end : DayEnd(end);
    return end - start;
}

public static TimeSpan DiffRange(DateTime start, DateTime end)
{
    if (start.Date == end.Date)
    {
        return DiffSingleDay(start, end);
    }

    var firstDay = DiffSingleDay(start, DayEnd(start));
    var lastDay = DiffSingleDay(DayStart(end), end);

    var middle = TimeSpan.Zero;
    var current = start.AddDays(1);
    while (current.Date != end.Date)
    {
        middle = middle + DiffSingleDay(current.Date, DayEnd(current.Date));
        current = current.AddDays(1);
    }

    return firstDay + lastDay + middle;
}
static int WorkPeriodMinuteDifference(DateTime start, DateTime end)
{
    //easier to only have to work in one direction.
    if(start > end)
        return WorkPeriodMinuteDifference(end, start);
    //if weekend, move to start of next Monday.
    while((int)start.DayOfWeek % 6 == 0)
        start = start.Add(new TimeSpan(1, 0, 0, 0)).Date;
    while((int)end.DayOfWeek % 6 == 0)
        end = end.Add(new TimeSpan(1, 0, 0, 0)).Date;
    //Move up to 07:00 or down to 19:00
    if(start.TimeOfDay.Hours < 7)
        start = new DateTime(start.Year, start.Month, start.Day, 7, 0, 0);
    else if(start.TimeOfDay.Hours > 19)
        start = new DateTime(start.Year, start.Month, start.Day, 19, 0, 0);
    if(end.TimeOfDay.Hours < 7)
        end = new DateTime(end.Year, end.Month, end.Day, 7, 0, 0);
    else if(end.TimeOfDay.Hours > 19)
        end = new DateTime(end.Year, end.Month, end.Day, 19, 0, 0);

    TimeSpan difference = end - start;

    int weeks = difference.Days / 7;
    int weekDays = difference.Days % 7;
    if(end.DayOfWeek < start.DayOfWeek)
        weekDays -= 2;

    return (weeks * 5 * 12 * 60) + (weekDays * 12 * 60) + difference.Hours * 60 + difference.Minutes
}

I'm sure there's something I missed.

  TimeSpan CalcBusinessTime(DateTime a, DateTime b)
  {
     if (a > b)
     {
        DateTime tmp = a;
        a = b;
        b = tmp;
     }

     if (a.TimeOfDay < new TimeSpan(7, 0, 0))
        a = new DateTime(a.Year, a.Month, a.Day, 7, 0, 0);
     if (b.TimeOfDay > new TimeSpan(19, 0, 0))
        b = new DateTime(b.Year, b.Month, b.Day, 19, 0, 0);

     TimeSpan sum = new TimeSpan();
     TimeSpan fullDay = new TimeSpan(12, 0, 0);
     while (a < b)
     {
        if (a.DayOfWeek != DayOfWeek.Saturday && a.DayOfWeek != DayOfWeek.Sunday)
        {
           sum += (b - a < fullDay) ? b - a : fullDay;
        }
        a = a.AddDays(1);
     }

     return sum;
  } 

It was a pretty hard question. For a basic, in a straightforward approach, I have written the below code:

DateTime start = new DateTime(2010, 01, 01, 21, 00, 00);
DateTime end = new DateTime(2010, 10, 01, 14, 00, 00);

// Shift start date's hour to 7 and same for end date
// These will be added after doing calculation:
double startAdjustmentMinutes = (start - start.Date.AddHours(7)).TotalMinutes;
double endAdjustmentMinutes = (end - end.Date.AddHours(7)).TotalMinutes;

// We can do some basic
// mathematical calculation to find weekdays count:
// divide by 7 multiply by 5 gives complete weeks weekdays
// and adding remainder gives the all weekdays:
int weekdaysCount = (((int)((end.Date - start.Date).Days / 7) * 5) 
          + ((end.Date - start.Date).Days % 7));
// so we can multiply it by minutes between 7am to 7 pm
int minutes = weekdaysCount * (12 * 60);

// after adding adjustment we have the result:
int result = minutes + startAdjustmentMinutes + endAdjustmentMinutes;

I know this not seem programmatically beautiful but I don't know if it is good to iterate through days and hours between start and end.

My implementation :) The idea is to quickly compute the total weeks, and walk the remaining week day by day...

public TimeSpan Compute(DateTime start, DateTime end)
{
    // constant start / end times per day
    TimeSpan sevenAM = TimeSpan.FromHours(7);
    TimeSpan sevenPM = TimeSpan.FromHours(19);

    if( start >= end )
    {
        throw new Exception("invalid date range");
    }

    // total # of weeks
    int completeWeeks = ((int)(end - start).TotalDays) / 7;

    // starting total
    TimeSpan total = TimeSpan.FromHours(completeWeeks * 12 * 5);

    // adjust the start date to be exactly "completeWeeks" past its original start
    start = start.AddDays(completeWeeks * 7);

    // walk days from the adjusted start to end (at most 7), accumulating time as we can...
    for(
        // start at midnight
        DateTime dt = start.Date;

        // continue while there is time left
        dt < end;

        // increment 1 day at a time
        dt = dt.AddDays(1)
    )
    {
        // ignore weekend
        if( (dt.DayOfWeek == DayOfWeek.Saturday) ||
             (dt.DayOfWeek == DayOfWeek.Sunday) )
        {
            continue;
        }

        // get the start/end time for each day...
        // typically 7am / 7pm unless we are at the start / end date
        TimeSpan dtStartTime = ((dt == start.Date) && (start.TimeOfDay > sevenAM)) ?
            start.TimeOfDay : sevenAM;
        TimeSpan dtEndTime = ((dt == end.Date) && (end.TimeOfDay < sevenPM)) ?
            end.TimeOfDay : sevenPM;

        if( dtStartTime < dtEndTime )
        {
            total = total.Add(dtEndTime - dtStartTime);
        }
    }

    return total;
}

Use TimeSpan.TotalMinutes, subtract non-business days, subtract superfluous hours.

I won't write any code, but having a DateTime you can tell the Day of the week, thus you know how many weekends are in your range, so you can tell how many minutes are in a weekend.

So it wouldn't be so hard... of course there must be an optimal one line solution... but i think you can get around with this one.

I forgot to mention that you also know the minutes btween 7:00 pm and 7:00 am so all you have to do is substract the right amount of minutes to the time difference you get.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top