Ну, это было избито до смерти. :) Однако я все еще собираюсь дать другой ответ, потому что мне нужно что-то немного другое. Это решение отличается тем, что оно возвращает значение Business TimeSpan между началом и концом, и вы можете установить рабочее время дня и добавить праздничные дни. Таким образом, вы можете использовать его для расчета, если это произойдет в течение дня, по дням, в выходные и даже в праздничные дни. И вы можете получить только рабочие дни или нет, просто получив то, что вам нужно, от возвращенного объекта TimeSpan. И по тому, как он использует списки дней, вы можете видеть, как легко было бы добавить список нерабочих дней, если это не обычные суббота и воскресенье.
И я проверил в течение года, и, кажется, очень быстро.
Я просто надеюсь, что вставка кода будет точной. Но я знаю, что это работает.
public static TimeSpan GetBusinessTimespanBetween(
DateTime start, DateTime end,
TimeSpan workdayStartTime, TimeSpan workdayEndTime,
List<DateTime> holidays = null)
{
if (end < start)
throw new ArgumentException("start datetime must be before end datetime.");
// Just create an empty list for easier coding.
if (holidays == null) holidays = new List<DateTime>();
if (holidays.Where(x => x.TimeOfDay.Ticks > 0).Any())
throw new ArgumentException("holidays can not have a TimeOfDay, only the Date.");
var nonWorkDays = new List<DayOfWeek>() { DayOfWeek.Saturday, DayOfWeek.Sunday };
var startTime = start.TimeOfDay;
// If the start time is before the starting hours, set it to the starting hour.
if (startTime < workdayStartTime) startTime = workdayStartTime;
var timeBeforeEndOfWorkDay = workdayEndTime - startTime;
// If it's after the end of the day, then this time lapse doesn't count.
if (timeBeforeEndOfWorkDay.TotalSeconds < 0) timeBeforeEndOfWorkDay = new TimeSpan();
// If start is during a non work day, it doesn't count.
if (nonWorkDays.Contains(start.DayOfWeek)) timeBeforeEndOfWorkDay = new TimeSpan();
else if (holidays.Contains(start.Date)) timeBeforeEndOfWorkDay = new TimeSpan();
var endTime = end.TimeOfDay;
// If the end time is after the ending hours, set it to the ending hour.
if (endTime > workdayEndTime) endTime = workdayEndTime;
var timeAfterStartOfWorkDay = endTime - workdayStartTime;
// If it's before the start of the day, then this time lapse doesn't count.
if (timeAfterStartOfWorkDay.TotalSeconds < 0) timeAfterStartOfWorkDay = new TimeSpan();
// If end is during a non work day, it doesn't count.
if (nonWorkDays.Contains(end.DayOfWeek)) timeAfterStartOfWorkDay = new TimeSpan();
else if (holidays.Contains(end.Date)) timeAfterStartOfWorkDay = new TimeSpan();
// Easy scenario if the times are during the day day.
if (start.Date.CompareTo(end.Date) == 0)
{
if (nonWorkDays.Contains(start.DayOfWeek)) return new TimeSpan();
else if (holidays.Contains(start.Date)) return new TimeSpan();
return endTime - startTime;
}
else
{
var timeBetween = end - start;
var daysBetween = (int)Math.Floor(timeBetween.TotalDays);
var dailyWorkSeconds = (int)Math.Floor((workdayEndTime - workdayStartTime).TotalSeconds);
var businessDaysBetween = 0;
// Now the fun begins with calculating the actual Business days.
if (daysBetween > 0)
{
var nextStartDay = start.AddDays(1).Date;
var dayBeforeEnd = end.AddDays(-1).Date;
for (DateTime d = nextStartDay; d <= dayBeforeEnd; d = d.AddDays(1))
{
if (nonWorkDays.Contains(d.DayOfWeek)) continue;
else if (holidays.Contains(d.Date)) continue;
businessDaysBetween++;
}
}
var dailyWorkSecondsToAdd = dailyWorkSeconds * businessDaysBetween;
var output = timeBeforeEndOfWorkDay + timeAfterStartOfWorkDay;
output = output + new TimeSpan(0, 0, dailyWorkSecondsToAdd);
return output;
}
}
А вот тестовый код: обратите внимание, что вам просто нужно поместить эту функцию в класс с именем DateHelper , чтобы тестовый код работал.
[TestMethod]
public void TestGetBusinessTimespanBetween()
{
var workdayStart = new TimeSpan(8, 0, 0);
var workdayEnd = new TimeSpan(17, 0, 0);
var holidays = new List<DateTime>()
{
new DateTime(2018, 1, 15), // a Monday
new DateTime(2018, 2, 15) // a Thursday
};
var testdata = new[]
{
new
{
expectedMinutes = 0,
start = new DateTime(2016, 10, 19, 9, 50, 0),
end = new DateTime(2016, 10, 19, 9, 50, 0)
},
new
{
expectedMinutes = 10,
start = new DateTime(2016, 10, 19, 9, 50, 0),
end = new DateTime(2016, 10, 19, 10, 0, 0)
},
new
{
expectedMinutes = 5,
start = new DateTime(2016, 10, 19, 7, 50, 0),
end = new DateTime(2016, 10, 19, 8, 5, 0)
},
new
{
expectedMinutes = 5,
start = new DateTime(2016, 10, 19, 16, 55, 0),
end = new DateTime(2016, 10, 19, 17, 5, 0)
},
new
{
expectedMinutes = 15,
start = new DateTime(2016, 10, 19, 16, 50, 0),
end = new DateTime(2016, 10, 20, 8, 5, 0)
},
new
{
expectedMinutes = 10,
start = new DateTime(2016, 10, 19, 16, 50, 0),
end = new DateTime(2016, 10, 20, 7, 55, 0)
},
new
{
expectedMinutes = 5,
start = new DateTime(2016, 10, 19, 17, 10, 0),
end = new DateTime(2016, 10, 20, 8, 5, 0)
},
new
{
expectedMinutes = 0,
start = new DateTime(2016, 10, 19, 17, 10, 0),
end = new DateTime(2016, 10, 20, 7, 5, 0)
},
new
{
expectedMinutes = 545,
start = new DateTime(2016, 10, 19, 12, 10, 0),
end = new DateTime(2016, 10, 20, 12, 15, 0)
},
// Spanning multiple weekdays
new
{
expectedMinutes = 835,
start = new DateTime(2016, 10, 19, 12, 10, 0),
end = new DateTime(2016, 10, 21, 8, 5, 0)
},
// Spanning multiple weekdays
new
{
expectedMinutes = 1375,
start = new DateTime(2016, 10, 18, 12, 10, 0),
end = new DateTime(2016, 10, 21, 8, 5, 0)
},
// Spanning from a Thursday to a Tuesday, 5 mins short of complete day.
new
{
expectedMinutes = 1615,
start = new DateTime(2016, 10, 20, 12, 10, 0),
end = new DateTime(2016, 10, 25, 12, 5, 0)
},
// Spanning from a Thursday to a Tuesday, 5 mins beyond complete day.
new
{
expectedMinutes = 1625,
start = new DateTime(2016, 10, 20, 12, 10, 0),
end = new DateTime(2016, 10, 25, 12, 15, 0)
},
// Spanning from a Friday to a Monday, 5 mins beyond complete day.
new
{
expectedMinutes = 545,
start = new DateTime(2016, 10, 21, 12, 10, 0),
end = new DateTime(2016, 10, 24, 12, 15, 0)
},
// Spanning from a Friday to a Monday, 5 mins short complete day.
new
{
expectedMinutes = 535,
start = new DateTime(2016, 10, 21, 12, 10, 0),
end = new DateTime(2016, 10, 24, 12, 5, 0)
},
// Spanning from a Saturday to a Monday, 5 mins short complete day.
new
{
expectedMinutes = 245,
start = new DateTime(2016, 10, 22, 12, 10, 0),
end = new DateTime(2016, 10, 24, 12, 5, 0)
},
// Spanning from a Saturday to a Sunday, 5 mins beyond complete day.
new
{
expectedMinutes = 0,
start = new DateTime(2016, 10, 22, 12, 10, 0),
end = new DateTime(2016, 10, 23, 12, 15, 0)
},
// Times within the same Saturday.
new
{
expectedMinutes = 0,
start = new DateTime(2016, 10, 22, 12, 10, 0),
end = new DateTime(2016, 10, 23, 12, 15, 0)
},
// Spanning from a Saturday to the Sunday next week.
new
{
expectedMinutes = 2700,
start = new DateTime(2016, 10, 22, 12, 10, 0),
end = new DateTime(2016, 10, 30, 12, 15, 0)
},
// Spanning a year.
new
{
expectedMinutes = 143355,
start = new DateTime(2016, 10, 22, 12, 10, 0),
end = new DateTime(2017, 10, 30, 12, 15, 0)
},
// Spanning a year with 2 holidays.
new
{
expectedMinutes = 142815,
start = new DateTime(2017, 10, 22, 12, 10, 0),
end = new DateTime(2018, 10, 30, 12, 15, 0)
},
};
foreach (var item in testdata)
{
Assert.AreEqual(item.expectedMinutes,
DateHelper.GetBusinessTimespanBetween(
item.start, item.end,
workdayStart, workdayEnd,
holidays)
.TotalMinutes);
}
}