En C #, ¿cómo puedo calcular la cantidad de días hábiles (o entre semana) entre dos fechas?
En C #, ¿cómo puedo calcular la cantidad de días hábiles (o entre semana) entre dos fechas?
He tenido una tarea así antes y tengo la solución. Evitaría enumerar todos los días intermedios cuando sea evitable, que es el caso aquí. Ni siquiera menciono la creación de un montón de instancias de DateTime, como vi en una de las respuestas anteriores. Esto es realmente una pérdida de potencia de procesamiento. Especialmente en la situación del mundo real, cuando tienes que examinar intervalos de tiempo de varios meses. Vea mi código, con comentarios, a continuación.
/// <summary>
/// Calculates number of business days, taking into account:
/// - weekends (Saturdays and Sundays)
/// - bank holidays in the middle of the week
/// </summary>
/// <param name="firstDay">First day in the time interval</param>
/// <param name="lastDay">Last day in the time interval</param>
/// <param name="bankHolidays">List of bank holidays excluding weekends</param>
/// <returns>Number of business days during the 'span'</returns>
public static int BusinessDaysUntil(this DateTime firstDay, DateTime lastDay, params DateTime[] bankHolidays)
{
firstDay = firstDay.Date;
lastDay = lastDay.Date;
if (firstDay > lastDay)
throw new ArgumentException("Incorrect last day " + lastDay);
TimeSpan span = lastDay - firstDay;
int businessDays = span.Days + 1;
int fullWeekCount = businessDays / 7;
// find out if there are weekends during the time exceedng the full weeks
if (businessDays > fullWeekCount*7)
{
// we are here to find out if there is a 1-day or 2-days weekend
// in the time interval remaining after subtracting the complete weeks
int firstDayOfWeek = (int) firstDay.DayOfWeek;
int lastDayOfWeek = (int) lastDay.DayOfWeek;
if (lastDayOfWeek < firstDayOfWeek)
lastDayOfWeek += 7;
if (firstDayOfWeek <= 6)
{
if (lastDayOfWeek >= 7)// Both Saturday and Sunday are in the remaining time interval
businessDays -= 2;
else if (lastDayOfWeek >= 6)// Only Saturday is in the remaining time interval
businessDays -= 1;
}
else if (firstDayOfWeek <= 7 && lastDayOfWeek >= 7)// Only Sunday is in the remaining time interval
businessDays -= 1;
}
// subtract the weekends during the full weeks in the interval
businessDays -= fullWeekCount + fullWeekCount;
// subtract the number of bank holidays during the time interval
foreach (DateTime bankHoliday in bankHolidays)
{
DateTime bh = bankHoliday.Date;
if (firstDay <= bh && bh <= lastDay)
--businessDays;
}
return businessDays;
}
Edición de Slauma, agosto de 2011
¡Gran respuesta! Sin embargo, hay un pequeño error. Tomo la libertad de editar esta respuesta ya que el respondedor está ausente desde 2009.
El código anterior asume que DayOfWeek.Sunday
tiene el valor, lo 7
cual no es el caso. El valor es en realidad 0
. Conduce a un cálculo incorrecto si, por ejemplo, firstDay
y lastDay
ambos son el mismo domingo. El método regresa 1
en este caso pero debería serlo 0
.
La solución más fácil para este error: reemplace en el código sobre las líneas donde firstDayOfWeek
y lastDayOfWeek
se declaran por lo siguiente:
int firstDayOfWeek = firstDay.DayOfWeek == DayOfWeek.Sunday
? 7 : (int)firstDay.DayOfWeek;
int lastDayOfWeek = lastDay.DayOfWeek == DayOfWeek.Sunday
? 7 : (int)lastDay.DayOfWeek;
Ahora el resultado es:
&& !(bh.DayOfWeek == DayOfWeek.Sunday || bh.DayOfWeek == DayOfWeek.Saturday)
contrario, restaría el mismo día dos veces, si un feriado cae en un fin de semana.
Okay. Creo que es hora de publicar la respuesta correcta:
public static double GetBusinessDays(DateTime startD, DateTime endD)
{
double calcBusinessDays =
1 + ((endD - startD).TotalDays * 5 -
(startD.DayOfWeek - endD.DayOfWeek) * 2) / 7;
if (endD.DayOfWeek == DayOfWeek.Saturday) calcBusinessDays--;
if (startD.DayOfWeek == DayOfWeek.Sunday) calcBusinessDays--;
return calcBusinessDays;
}
Fuente original:
http://alecpojidaev.wordpress.com/2009/10/29/work-days-calculation-with-c/
PS Solutions publicado arriba me hace sentir sic por alguna razón.
Sé que esta pregunta ya está resuelta, pero pensé que podría proporcionar una respuesta más sencilla que podría ayudar a otros visitantes en el futuro.
Aquí está mi opinión:
public int GetWorkingDays(DateTime from, DateTime to)
{
var dayDifference = (int)to.Subtract(from).TotalDays;
return Enumerable
.Range(1, dayDifference)
.Select(x => from.AddDays(x))
.Count(x => x.DayOfWeek != DayOfWeek.Saturday && x.DayOfWeek != DayOfWeek.Sunday);
}
Esta fue mi presentación original:
public int GetWorkingDays(DateTime from, DateTime to)
{
var totalDays = 0;
for (var date = from; date < to; date = date.AddDays(1))
{
if (date.DayOfWeek != DayOfWeek.Saturday
&& date.DayOfWeek != DayOfWeek.Sunday)
totalDays++;
}
return totalDays;
}
to > from
. ¿Quizás ese es el problema?
Defina un método de extensión en DateTime así:
public static class DateTimeExtensions
{
public static bool IsWorkingDay(this DateTime date)
{
return date.DayOfWeek != DayOfWeek.Saturday
&& date.DayOfWeek != DayOfWeek.Sunday;
}
}
Luego, use está dentro de una cláusula Where para filtrar una lista más amplia de fechas:
var allDates = GetDates(); // method which returns a list of dates
// filter dates by working day's
var countOfWorkDays = allDates
.Where(day => day.IsWorkingDay())
.Count() ;
Utilicé el siguiente código para incluir también los días festivos en la cuenta:
public class WorkingDays
{
public List<DateTime> GetHolidays()
{
var client = new WebClient();
var json = client.DownloadString("https://www.gov.uk/bank-holidays.json");
var js = new JavaScriptSerializer();
var holidays = js.Deserialize <Dictionary<string, Holidays>>(json);
return holidays["england-and-wales"].events.Select(d => d.date).ToList();
}
public int GetWorkingDays(DateTime from, DateTime to)
{
var totalDays = 0;
var holidays = GetHolidays();
for (var date = from.AddDays(1); date <= to; date = date.AddDays(1))
{
if (date.DayOfWeek != DayOfWeek.Saturday
&& date.DayOfWeek != DayOfWeek.Sunday
&& !holidays.Contains(date))
totalDays++;
}
return totalDays;
}
}
public class Holidays
{
public string division { get; set; }
public List<Event> events { get; set; }
}
public class Event
{
public DateTime date { get; set; }
public string notes { get; set; }
public string title { get; set; }
}
Y pruebas unitarias:
[TestClass]
public class WorkingDays
{
[TestMethod]
public void SameDayIsZero()
{
var service = new WorkingDays();
var from = new DateTime(2013, 8, 12);
Assert.AreEqual(0, service.GetWorkingDays(from, from));
}
[TestMethod]
public void CalculateDaysInWorkingWeek()
{
var service = new WorkingDays();
var from = new DateTime(2013, 8, 12);
var to = new DateTime(2013, 8, 16);
Assert.AreEqual(4, service.GetWorkingDays(from, to), "Mon - Fri = 4");
Assert.AreEqual(1, service.GetWorkingDays(from, new DateTime(2013, 8, 13)), "Mon - Tues = 1");
}
[TestMethod]
public void NotIncludeWeekends()
{
var service = new WorkingDays();
var from = new DateTime(2013, 8, 9);
var to = new DateTime(2013, 8, 16);
Assert.AreEqual(5, service.GetWorkingDays(from, to), "Fri - Fri = 5");
Assert.AreEqual(2, service.GetWorkingDays(from, new DateTime(2013, 8, 13)), "Fri - Tues = 2");
Assert.AreEqual(1, service.GetWorkingDays(from, new DateTime(2013, 8, 12)), "Fri - Mon = 1");
}
[TestMethod]
public void AccountForHolidays()
{
var service = new WorkingDays();
var from = new DateTime(2013, 8, 23);
Assert.AreEqual(0, service.GetWorkingDays(from, new DateTime(2013, 8, 26)), "Fri - Mon = 0");
Assert.AreEqual(1, service.GetWorkingDays(from, new DateTime(2013, 8, 27)), "Fri - Tues = 1");
}
}
Bueno, esto ha sido golpeado hasta la muerte. :) Sin embargo, todavía voy a dar otra respuesta porque necesitaba algo un poco diferente. Esta solución es diferente en que devuelve un intervalo de tiempo comercial entre el inicio y el final, y puede establecer el horario comercial del día y agregar días festivos. Por lo tanto, puede usarlo para calcular si ocurre dentro de un día, entre días, fines de semana e incluso feriados. Y puede obtener solo los días hábiles o no simplemente obteniendo lo que necesita del objeto TimeSpan devuelto. Y la forma en que utiliza listas de días, puede ver lo fácil que sería agregar la lista de días no laborables si no es el típico sábado y domingo. Y probé durante un año, y parece súper rápido.
Solo espero que el pegado del código sea preciso. Pero sé que funciona.
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;
}
}
Y aquí está el código de prueba: tenga en cuenta que solo tiene que poner esta función en una clase llamada DateHelper para que funcione el código de prueba.
[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);
}
}
Esta solución evita la iteración, funciona para las diferencias de días de la semana + ve y -ve e incluye un conjunto de pruebas unitarias para la regresión contra el método más lento de contar los días de la semana. También he incluido un método conciso para agregar días de la semana que también funciona de la misma manera no iterativa.
Las pruebas unitarias cubren unos pocos miles de combinaciones de fechas para probar exhaustivamente todas las combinaciones de días de semana de inicio / finalización con rangos de fechas pequeños y grandes.
Importante : asumimos que contamos los días excluyendo la fecha de inicio e incluyendo la fecha de finalización. Esto es importante cuando se cuentan los días de la semana, ya que los días de inicio / finalización específicos que incluye / excluye afectan el resultado. Esto también garantiza que la diferencia entre dos días iguales sea siempre cero y que solo incluyamos días laborables completos, ya que normalmente desea que la respuesta sea correcta en cualquier momento de la fecha de inicio actual (a menudo hoy) e incluya la fecha de finalización completa (p. Ej. una fecha de vencimiento).
NOTA: Este código necesita un ajuste adicional para las vacaciones, pero de acuerdo con la suposición anterior, este código debe excluir las vacaciones en la fecha de inicio.
Agregar días de la semana:
private static readonly int[,] _addOffset =
{
// 0 1 2 3 4
{0, 1, 2, 3, 4}, // Su 0
{0, 1, 2, 3, 4}, // M 1
{0, 1, 2, 3, 6}, // Tu 2
{0, 1, 4, 5, 6}, // W 3
{0, 1, 4, 5, 6}, // Th 4
{0, 3, 4, 5, 6}, // F 5
{0, 2, 3, 4, 5}, // Sa 6
};
public static DateTime AddWeekdays(this DateTime date, int weekdays)
{
int extraDays = weekdays % 5;
int addDays = weekdays >= 0
? (weekdays / 5) * 7 + _addOffset[(int)date.DayOfWeek, extraDays]
: (weekdays / 5) * 7 - _addOffset[6 - (int)date.DayOfWeek, -extraDays];
return date.AddDays(addDays);
}
Calcular la diferencia entre semana:
static readonly int[,] _diffOffset =
{
// Su M Tu W Th F Sa
{0, 1, 2, 3, 4, 5, 5}, // Su
{4, 0, 1, 2, 3, 4, 4}, // M
{3, 4, 0, 1, 2, 3, 3}, // Tu
{2, 3, 4, 0, 1, 2, 2}, // W
{1, 2, 3, 4, 0, 1, 1}, // Th
{0, 1, 2, 3, 4, 0, 0}, // F
{0, 1, 2, 3, 4, 5, 0}, // Sa
};
public static int GetWeekdaysDiff(this DateTime dtStart, DateTime dtEnd)
{
int daysDiff = (int)(dtEnd - dtStart).TotalDays;
return daysDiff >= 0
? 5 * (daysDiff / 7) + _diffOffset[(int) dtStart.DayOfWeek, (int) dtEnd.DayOfWeek]
: 5 * (daysDiff / 7) - _diffOffset[6 - (int) dtStart.DayOfWeek, 6 - (int) dtEnd.DayOfWeek];
}
Descubrí que la mayoría de las otras soluciones en el desbordamiento de pila eran lentas (iterativas) o demasiado complejas y muchas eran simplemente incorrectas. La moraleja de la historia es ... ¡¡No confíes en ella a menos que la hayas probado exhaustivamente !!
Las pruebas unitarias basado en NUnit prueba combinatoria y shouldbe extensión NUnit.
[TestFixture]
public class DateTimeExtensionsTests
{
/// <summary>
/// Exclude start date, Include end date
/// </summary>
/// <param name="dtStart"></param>
/// <param name="dtEnd"></param>
/// <returns></returns>
private IEnumerable<DateTime> GetDateRange(DateTime dtStart, DateTime dtEnd)
{
Console.WriteLine(@"dtStart={0:yy-MMM-dd ddd}, dtEnd={1:yy-MMM-dd ddd}", dtStart, dtEnd);
TimeSpan diff = dtEnd - dtStart;
Console.WriteLine(diff);
if (dtStart <= dtEnd)
{
for (DateTime dt = dtStart.AddDays(1); dt <= dtEnd; dt = dt.AddDays(1))
{
Console.WriteLine(@"dt={0:yy-MMM-dd ddd}", dt);
yield return dt;
}
}
else
{
for (DateTime dt = dtStart.AddDays(-1); dt >= dtEnd; dt = dt.AddDays(-1))
{
Console.WriteLine(@"dt={0:yy-MMM-dd ddd}", dt);
yield return dt;
}
}
}
[Test, Combinatorial]
public void TestGetWeekdaysDiff(
[Values(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 20, 30)]
int startDay,
[Values(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 20, 30)]
int endDay,
[Values(7)]
int startMonth,
[Values(7)]
int endMonth)
{
// Arrange
DateTime dtStart = new DateTime(2016, startMonth, startDay);
DateTime dtEnd = new DateTime(2016, endMonth, endDay);
int nDays = GetDateRange(dtStart, dtEnd)
.Count(dt => dt.DayOfWeek != DayOfWeek.Saturday && dt.DayOfWeek != DayOfWeek.Sunday);
if (dtEnd < dtStart) nDays = -nDays;
Console.WriteLine(@"countBusDays={0}", nDays);
// Act / Assert
dtStart.GetWeekdaysDiff(dtEnd).ShouldBe(nDays);
}
[Test, Combinatorial]
public void TestAddWeekdays(
[Values(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 20, 30)]
int startDay,
[Values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 20, 30)]
int weekdays)
{
DateTime dtStart = new DateTime(2016, 7, startDay);
DateTime dtEnd1 = dtStart.AddWeekdays(weekdays); // ADD
dtStart.GetWeekdaysDiff(dtEnd1).ShouldBe(weekdays);
DateTime dtEnd2 = dtStart.AddWeekdays(-weekdays); // SUBTRACT
dtStart.GetWeekdaysDiff(dtEnd2).ShouldBe(-weekdays);
}
}
Aquí hay un código para ese propósito, con días festivos suecos, pero puede adaptar qué días festivos contar. Tenga en cuenta que agregué un límite que quizás desee eliminar, pero era para un sistema basado en la web y no quería que nadie ingresara una fecha enorme para acaparar el proceso
public static int GetWorkdays(DateTime from ,DateTime to)
{
int limit = 9999;
int counter = 0;
DateTime current = from;
int result = 0;
if (from > to)
{
DateTime temp = from;
from = to;
to = temp;
}
if (from >= to)
{
return 0;
}
while (current <= to && counter < limit)
{
if (IsSwedishWorkday(current))
{
result++;
}
current = current.AddDays(1);
counter++;
}
return result;
}
public static bool IsSwedishWorkday(DateTime date)
{
return (!IsSwedishHoliday(date) && date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday);
}
public static bool IsSwedishHoliday(DateTime date)
{
return (
IsSameDay(GetEpiphanyDay(date.Year), date) ||
IsSameDay(GetMayDay(date.Year), date) ||
IsSameDay(GetSwedishNationalDay(date.Year), date) ||
IsSameDay(GetChristmasDay(date.Year), date) ||
IsSameDay(GetBoxingDay(date.Year), date) ||
IsSameDay(GetGoodFriday(date.Year), date) ||
IsSameDay(GetAscensionDay(date.Year), date) ||
IsSameDay(GetAllSaintsDay(date.Year), date) ||
IsSameDay(GetMidsummersDay(date.Year), date) ||
IsSameDay(GetPentecostDay(date.Year), date) ||
IsSameDay(GetEasterMonday(date.Year), date) ||
IsSameDay(GetNewYearsDay(date.Year), date) ||
IsSameDay(GetEasterDay(date.Year), date)
);
}
// Trettondagen
public static DateTime GetEpiphanyDay(int year)
{
return new DateTime(year, 1, 6);
}
// Första maj
public static DateTime GetMayDay(int year)
{
return new DateTime(year,5,1);
}
// Juldagen
public static DateTime GetSwedishNationalDay(int year)
{
return new DateTime(year, 6, 6);
}
// Juldagen
public static DateTime GetNewYearsDay(int year)
{
return new DateTime(year,1,1);
}
// Juldagen
public static DateTime GetChristmasDay(int year)
{
return new DateTime(year,12,25);
}
// Annandag jul
public static DateTime GetBoxingDay(int year)
{
return new DateTime(year, 12, 26);
}
// Långfredagen
public static DateTime GetGoodFriday(int year)
{
return GetEasterDay(year).AddDays(-3);
}
// Kristi himmelsfärdsdag
public static DateTime GetAscensionDay(int year)
{
return GetEasterDay(year).AddDays(5*7+4);
}
// Midsommar
public static DateTime GetAllSaintsDay(int year)
{
DateTime result = new DateTime(year,10,31);
while (result.DayOfWeek != DayOfWeek.Saturday)
{
result = result.AddDays(1);
}
return result;
}
// Midsommar
public static DateTime GetMidsummersDay(int year)
{
DateTime result = new DateTime(year, 6, 20);
while (result.DayOfWeek != DayOfWeek.Saturday)
{
result = result.AddDays(1);
}
return result;
}
// Pingstdagen
public static DateTime GetPentecostDay(int year)
{
return GetEasterDay(year).AddDays(7 * 7);
}
// Annandag påsk
public static DateTime GetEasterMonday(int year)
{
return GetEasterDay(year).AddDays(1);
}
public static DateTime GetEasterDay(int y)
{
double c;
double n;
double k;
double i;
double j;
double l;
double m;
double d;
c = System.Math.Floor(y / 100.0);
n = y - 19 * System.Math.Floor(y / 19.0);
k = System.Math.Floor((c - 17) / 25.0);
i = c - System.Math.Floor(c / 4) - System.Math.Floor((c - k) / 3) + 19 * n + 15;
i = i - 30 * System.Math.Floor(i / 30);
i = i - System.Math.Floor(i / 28) * (1 - System.Math.Floor(i / 28) * System.Math.Floor(29 / (i + 1)) * System.Math.Floor((21 - n) / 11));
j = y + System.Math.Floor(y / 4.0) + i + 2 - c + System.Math.Floor(c / 4);
j = j - 7 * System.Math.Floor(j / 7);
l = i - j;
m = 3 + System.Math.Floor((l + 40) / 44);// month
d = l + 28 - 31 * System.Math.Floor(m / 4);// day
double days = ((m == 3) ? d : d + 31);
DateTime result = new DateTime(y, 3, 1).AddDays(days-1);
return result;
}
Aquí hay un código de muestra rápido. Es un método de clase, por lo que solo funcionará dentro de su clase. Si lo desea static
, cambie la firma a private static
(o public static
).
private IEnumerable<DateTime> GetWorkingDays(DateTime sd, DateTime ed)
{
for (var d = sd; d <= ed; d = d.AddDays(1))
if (d.DayOfWeek != DayOfWeek.Saturday && d.DayOfWeek != DayOfWeek.Sunday)
yield return d;
}
Este método crea una variable de bucle d
, la inicializa al día de inicio sd
y luego la incrementa en un día en cada iteración ( d = d.AddDays(1)
).
Devuelve los valores deseados usando yield
, lo que crea un iterator
. Lo bueno de los iteradores es que no contienen todos los valores de IEnumerable
en la memoria, solo llaman a cada uno secuencialmente. Esto significa que puede llamar a este método desde el principio de los tiempos hasta ahora sin tener que preocuparse por quedarse sin memoria.
Busqué mucho un algoritmo fácil de digerir para calcular los días laborables entre 2 fechas, y también para excluir los feriados nacionales, y finalmente decido ir con este enfoque:
public static int NumberOfWorkingDaysBetween2Dates(DateTime start,DateTime due,IEnumerable<DateTime> holidays)
{
var dic = new Dictionary<DateTime, DayOfWeek>();
var totalDays = (due - start).Days;
for (int i = 0; i < totalDays + 1; i++)
{
if (!holidays.Any(x => x == start.AddDays(i)))
dic.Add(start.AddDays(i), start.AddDays(i).DayOfWeek);
}
return dic.Where(x => x.Value != DayOfWeek.Saturday && x.Value != DayOfWeek.Sunday).Count();
}
Básicamente quería ir con cada fecha y evaluar mis condiciones:
pero también quería evitar iterar fechas.
Al ejecutar y medir el tiempo necesario para evaluar 1 año completo, obtengo el siguiente resultado:
static void Main(string[] args)
{
var start = new DateTime(2017, 1, 1);
var due = new DateTime(2017, 12, 31);
var sw = Stopwatch.StartNew();
var days = NumberOfWorkingDaysBetween2Dates(start, due,NationalHolidays());
sw.Stop();
Console.WriteLine($"Total working days = {days} --- time: {sw.Elapsed}");
Console.ReadLine();
// result is:
// Total working days = 249-- - time: 00:00:00.0269087
}
Editar: un nuevo método más simple:
public static int ToBusinessWorkingDays(this DateTime start, DateTime due, DateTime[] holidays)
{
return Enumerable.Range(0, (due - start).Days)
.Select(a => start.AddDays(a))
.Where(a => a.DayOfWeek != DayOfWeek.Sunday)
.Where(a => a.DayOfWeek != DayOfWeek.Saturday)
.Count(a => !holidays.Any(x => x == a));
}
Creo que ninguna de las respuestas anteriores es correcta. Ninguno de ellos resuelve todos los casos especiales, como cuando las fechas comienzan y terminan a la mitad de un fin de semana, cuando la fecha comienza un viernes y termina el próximo lunes, etc. Además, todos redondean los cálculos a la totalidad días, por lo que si la fecha de inicio es en medio de un sábado por ejemplo, restará un día completo de los días hábiles, dando resultados incorrectos ...
De todos modos, aquí está mi solución que es bastante eficiente y simple y funciona para todos los casos. El truco consiste simplemente en encontrar el lunes anterior para las fechas de inicio y finalización, y luego hacer una pequeña compensación cuando el inicio y el final ocurran durante el fin de semana:
public double WorkDays(DateTime startDate, DateTime endDate){
double weekendDays;
double days = endDate.Subtract(startDate).TotalDays;
if(days<0) return 0;
DateTime startMonday = startDate.AddDays(DayOfWeek.Monday - startDate.DayOfWeek).Date;
DateTime endMonday = endDate.AddDays(DayOfWeek.Monday - endDate.DayOfWeek).Date;
weekendDays = ((endMonday.Subtract(startMonday).TotalDays) / 7) * 2;
// compute fractionary part of weekend days
double diffStart = startDate.Subtract(startMonday).TotalDays - 5;
double diffEnd = endDate.Subtract(endMonday).TotalDays - 5;
// compensate weekenddays
if(diffStart>0) weekendDays -= diffStart;
if(diffEnd>0) weekendDays += diffEnd;
return days - weekendDays;
}
using System;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
DateTime start = new DateTime(2014, 1, 1);
DateTime stop = new DateTime(2014, 12, 31);
int totalWorkingDays = GetNumberOfWorkingDays(start, stop);
Console.WriteLine("There are {0} working days.", totalWorkingDays);
}
private static int GetNumberOfWorkingDays(DateTime start, DateTime stop)
{
TimeSpan interval = stop - start;
int totalWeek = interval.Days / 7;
int totalWorkingDays = 5 * totalWeek;
int remainingDays = interval.Days % 7;
for (int i = 0; i <= remainingDays; i++)
{
DayOfWeek test = (DayOfWeek)(((int)start.DayOfWeek + i) % 7);
if (test >= DayOfWeek.Monday && test <= DayOfWeek.Friday)
totalWorkingDays++;
}
return totalWorkingDays;
}
}
}
Este método no utiliza bucles y en realidad es bastante simple. Amplía el rango de fechas a semanas completas, ya que sabemos que cada semana tiene 5 días hábiles. Luego, utiliza una tabla de búsqueda para encontrar la cantidad de días hábiles que se deben restar al principio y al final para obtener el resultado correcto. He ampliado el cálculo para ayudar a mostrar lo que está sucediendo, pero todo podría condensarse en una sola línea si fuera necesario.
De todos modos, esto funciona para mí, así que pensé en publicarlo aquí en caso de que pudiera ayudar a otros. Codificación feliz.
Cálculo
Cultura
El código asume una semana laboral de lunes a viernes. Para otras culturas, como de domingo a jueves, deberá compensar las fechas antes del cálculo.
Método
public int Weekdays(DateTime min, DateTime max)
{
if (min.Date > max.Date) throw new Exception("Invalid date span");
var t = (max.AddDays(1).Date - min.Date).TotalDays;
var a = (int) min.DayOfWeek;
var b = 6 - (int) max.DayOfWeek;
var k = 1.4;
var m = new int[]{0, 0, 1, 2, 3, 4, 5};
var c = m[a] + m[b];
return (int)((t + a + b) / k) - c;
}
Solo compartiré mi solución. Funcionó para mí, tal vez simplemente no me di cuenta / no sé que hay un error. Comencé obteniendo la primera semana incompleta si hay alguna. una semana completa era del domingo al sábado, por lo que si (int) _now.DayOfWeek no era 0 (domingo), la primera semana estaba incompleta.
Solo resto 1 al recuento de las primeras semanas para el sábado de la primera semana y luego lo agrego al recuento nuevo;
Luego obtengo la última semana incompleta, luego resto 1 para el domingo y luego lo agrego al nuevo recuento.
Luego, finalmente, el número de semanas completas multiplicado por 5 (días de semana) se agregó al nuevo recuento.
public int RemoveNonWorkingDays(int numberOfDays){
int workingDays = 0;
int firstWeek = 7 - (int)_now.DayOfWeek;
if(firstWeek < 7){
if(firstWeek > numberOfDays)
return numberOfDays;
workingDays += firstWeek-1;
numberOfDays -= firstWeek;
}
int lastWeek = numberOfDays % 7;
if(lastWeek > 0){
numberOfDays -= lastWeek;
workingDays += lastWeek - 1;
}
workingDays += (numberOfDays/7)*5;
return workingDays;
}
Estaba teniendo problemas para encontrar una versión TSQL sólida de este código. A continuación, se muestra esencialmente una conversión del código C # aquí con la adición de la tabla de vacaciones que debe usarse para calcular previamente los días festivos.
CREATE TABLE dbo.Holiday
(
HolidayDt DATE NOT NULL,
Name NVARCHAR(50) NOT NULL,
IsWeekday BIT NOT NULL,
CONSTRAINT PK_Holiday PRIMARY KEY (HolidayDt)
)
GO
CREATE INDEX IDX_Holiday ON Holiday (HolidayDt, IsWeekday)
GO
CREATE function dbo.GetBusinessDays
(
@FirstDay datetime,
@LastDay datetime
)
RETURNS INT
AS
BEGIN
DECLARE @BusinessDays INT, @FullWeekCount INT
SELECT @FirstDay = CONVERT(DATETIME,CONVERT(DATE,@FirstDay))
, @LastDay = CONVERT(DATETIME,CONVERT(DATE,@LastDay))
IF @FirstDay > @LastDay
RETURN NULL;
SELECT @BusinessDays = DATEDIFF(DAY, @FirstDay, @LastDay) + 1
SELECT @FullWeekCount = @BusinessDays / 7;
-- find out if there are weekends during the time exceedng the full weeks
IF @BusinessDays > (@FullWeekCount * 7)
BEGIN
-- we are here to find out if there is a 1-day or 2-days weekend
-- in the time interval remaining after subtracting the complete weeks
DECLARE @firstDayOfWeek INT, @lastDayOfWeek INT;
SELECT @firstDayOfWeek = DATEPART(DW, @FirstDay), @lastDayOfWeek = DATEPART(DW, @LastDay);
IF @lastDayOfWeek < @firstDayOfWeek
SELECT @lastDayOfWeek = @lastDayOfWeek + 7;
IF @firstDayOfWeek <= 6
BEGIN
IF (@lastDayOfWeek >= 7) --Both Saturday and Sunday are in the remaining time interval
BEGIN
SELECT @BusinessDays = @BusinessDays - 2
END
ELSE IF @lastDayOfWeek>=6 --Only Saturday is in the remaining time interval
BEGIN
SELECT @BusinessDays = @BusinessDays - 1
END
END
ELSE IF @firstDayOfWeek <= 7 AND @lastDayOfWeek >=7 -- Only Sunday is in the remaining time interval
BEGIN
SELECT @BusinessDays = @BusinessDays - 1
END
END
-- subtract the weekends during the full weeks in the interval
DECLARE @Holidays INT;
SELECT @Holidays = COUNT(*)
FROM Holiday
WHERE HolidayDt BETWEEN @FirstDay AND @LastDay
AND IsWeekday = CAST(1 AS BIT)
SELECT @BusinessDays = @BusinessDays - (@FullWeekCount + @FullWeekCount) -- - @Holidays
RETURN @BusinessDays
END
int BusinessDayDifference(DateTime Date1, DateTime Date2)
{
int Sign = 1;
if (Date2 > Date1)
{
Sign = -1;
DateTime TempDate = Date1;
Date1 = Date2;
Date2 = TempDate;
}
int BusDayDiff = (int)(Date1.Date - Date2.Date).TotalDays;
if (Date1.DayOfWeek == DayOfWeek.Saturday)
BusDayDiff -= 1;
if (Date2.DayOfWeek == DayOfWeek.Sunday)
BusDayDiff -= 1;
int Week1 = GetWeekNum(Date1);
int Week2 = GetWeekNum(Date2);
int WeekDiff = Week1 - Week2;
BusDayDiff -= WeekDiff * 2;
foreach (DateTime Holiday in Holidays)
if (Date1 >= Holiday && Date2 <= Holiday)
BusDayDiff--;
BusDayDiff *= Sign;
return BusDayDiff;
}
private int GetWeekNum(DateTime Date)
{
return (int)(Date.AddDays(-(int)Date.DayOfWeek).Ticks / TimeSpan.TicksPerDay / 7);
}
Aquí hay una solución muy simple para este problema. Tenemos fecha de inicio, fecha de finalización y "bucle for" para aumentar el día y calcular para ver si es un día laborable o un fin de semana mediante la conversión a la cadena DayOfWeek.
class Program
{
static void Main(string[] args)
{
DateTime day = new DateTime();
Console.Write("Inser your end date (example: 01/30/2015): ");
DateTime endDate = DateTime.Parse(Console.ReadLine());
int numberOfDays = 0;
for (day = DateTime.Now.Date; day.Date < endDate.Date; day = day.Date.AddDays(1))
{
string dayToString = Convert.ToString(day.DayOfWeek);
if (dayToString != "Saturday" && dayToString != "Sunday") numberOfDays++;
}
Console.WriteLine("Number of working days (not including local holidays) between two dates is "+numberOfDays);
}
}
Basado en el comentario marcado como respuesta y parche recomendado, así como -> Esta versión quiere convertir los Días a Horas Laborales ... Considera también las Horas del mismo día.
/// <summary>
/// Calculates number of business days, taking into account:
/// - weekends (Saturdays and Sundays)
/// - bank holidays in the middle of the week
/// </summary>
/// <param name="firstDay">First day in the time interval</param>
/// <param name="lastDay">Last day in the time interval</param>
/// <param name="bankHolidays">List of bank holidays excluding weekends</param>
/// <returns>Number of business hours during the 'span'</returns>
public static int BusinessHoursUntil(DateTime firstDay, DateTime lastDay, params DateTime[] bankHolidays)
{
var original_firstDay = firstDay;
var original_lastDay = lastDay;
firstDay = firstDay.Date;
lastDay = lastDay.Date;
if (firstDay > lastDay)
return -1; //// throw new ArgumentException("Incorrect last day " + lastDay);
TimeSpan span = lastDay - firstDay;
int businessDays = span.Days + 1;
int fullWeekCount = businessDays / 7;
// find out if there are weekends during the time exceedng the full weeks
if (businessDays > fullWeekCount * 7)
{
// we are here to find out if there is a 1-day or 2-days weekend
// in the time interval remaining after subtracting the complete weeks
int firstDayOfWeek = firstDay.DayOfWeek == DayOfWeek.Sunday ? 7 : (int)firstDay.DayOfWeek;
int lastDayOfWeek = lastDay.DayOfWeek == DayOfWeek.Sunday ? 7 : (int)lastDay.DayOfWeek;
if (lastDayOfWeek < firstDayOfWeek)
lastDayOfWeek += 7;
if (firstDayOfWeek <= 6)
{
if (lastDayOfWeek >= 7)// Both Saturday and Sunday are in the remaining time interval
businessDays -= 2;
else if (lastDayOfWeek >= 6)// Only Saturday is in the remaining time interval
businessDays -= 1;
}
else if (firstDayOfWeek <= 7 && lastDayOfWeek >= 7)// Only Sunday is in the remaining time interval
businessDays -= 1;
}
// subtract the weekends during the full weeks in the interval
businessDays -= fullWeekCount + fullWeekCount;
if (bankHolidays != null && bankHolidays.Any())
{
// subtract the number of bank holidays during the time interval
foreach (DateTime bankHoliday in bankHolidays)
{
DateTime bh = bankHoliday.Date;
if (firstDay <= bh && bh <= lastDay)
--businessDays;
}
}
int total_business_hours = 0;
if (firstDay.Date == lastDay.Date)
{//If on the same day, go granular with Hours from the Orginial_*Day values
total_business_hours = (int)(original_lastDay - original_firstDay).TotalHours;
}
else
{//Convert Business-Days to TotalHours
total_business_hours = (int)(firstDay.AddDays(businessDays).AddHours(firstDay.Hour) - firstDay).TotalHours;
}
return total_business_hours;
}
Acabo de mejorar la respuesta de @Alexander y @Slauma para admitir una semana laboral como parámetro, para los casos en que el sábado es un día hábil, o incluso en los casos en que solo hay un par de días de la semana que se consideran días hábiles:
/// <summary>
/// Calculate the number of business days between two dates, considering:
/// - Days of the week that are not considered business days.
/// - Holidays between these two dates.
/// </summary>
/// <param name="fDay">First day of the desired 'span'.</param>
/// <param name="lDay">Last day of the desired 'span'.</param>
/// <param name="BusinessDaysOfWeek">Days of the week that are considered to be business days, if NULL considers monday, tuesday, wednesday, thursday and friday as business days of the week.</param>
/// <param name="Holidays">Holidays, if NULL, considers no holiday.</param>
/// <returns>Number of business days during the 'span'</returns>
public static int BusinessDaysUntil(this DateTime fDay, DateTime lDay, DayOfWeek[] BusinessDaysOfWeek = null, DateTime[] Holidays = null)
{
if (BusinessDaysOfWeek == null)
BusinessDaysOfWeek = new DayOfWeek[] { DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday };
if (Holidays == null)
Holidays = new DateTime[] { };
fDay = fDay.Date;
lDay = lDay.Date;
if (fDay > lDay)
throw new ArgumentException("Incorrect last day " + lDay);
int bDays = (lDay - fDay).Days + 1;
int fullWeekCount = bDays / 7;
int fullWeekCountMult = 7 - WeekDays.Length;
// Find out if there are weekends during the time exceedng the full weeks
if (bDays > (fullWeekCount * 7))
{
int fDayOfWeek = (int)fDay.DayOfWeek;
int lDayOfWeek = (int)lDay.DayOfWeek;
if (fDayOfWeek > lDayOfWeek)
lDayOfWeek += 7;
// If they are the same, we already covered it right before the Holiday subtraction
if (lDayOfWeek != fDayOfWeek)
{
// Here we need to see if any of the days between are considered business days
for (int i = fDayOfWeek; i <= lDayOfWeek; i++)
if (!WeekDays.Contains((DayOfWeek)(i > 6 ? i - 7 : i)))
bDays -= 1;
}
}
// Subtract the days that are not in WeekDays[] during the full weeks in the interval
bDays -= (fullWeekCount * fullWeekCountMult);
// Subtract the number of bank holidays during the time interval
bDays = bDays - Holidays.Select(x => x.Date).Count(x => fDay <= x && x <= lDay);
return bDays;
}
Aquí está la función que podemos usar para calcular los días hábiles entre dos fechas. No estoy usando la lista de días festivos, ya que puede variar según el país o la región.
Si queremos usarlo de todos modos, podemos tomar el tercer argumento como lista de vacaciones y antes de incrementar el recuento debemos verificar que la lista no contenga d
public static int GetBussinessDaysBetweenTwoDates(DateTime StartDate, DateTime EndDate)
{
if (StartDate > EndDate)
return -1;
int bd = 0;
for (DateTime d = StartDate; d < EndDate; d = d.AddDays(1))
{
if (d.DayOfWeek != DayOfWeek.Saturday && d.DayOfWeek != DayOfWeek.Sunday)
bd++;
}
return bd;
}
Creo que esta podría ser una forma más sencilla:
public int BusinessDaysUntil(DateTime start, DateTime end, params DateTime[] bankHolidays)
{
int tld = (int)((end - start).TotalDays) + 1; //including end day
int not_buss_day = 2 * (tld / 7); //Saturday and Sunday
int rest = tld % 7; //rest.
if (rest > 0)
{
int tmp = (int)start.DayOfWeek - 1 + rest;
if (tmp == 6 || start.DayOfWeek == DayOfWeek.Sunday) not_buss_day++; else if (tmp > 6) not_buss_day += 2;
}
foreach (DateTime bankHoliday in bankHolidays)
{
DateTime bh = bankHoliday.Date;
if (!(bh.DayOfWeek == DayOfWeek.Saturday || bh.DayOfWeek == DayOfWeek.Sunday) && (start <= bh && bh <= end))
{
not_buss_day++;
}
}
return tld - not_buss_day;
}
Aquí hay otra idea: este método permite especificar cualquier semana laboral y feriados.
La idea aquí es que encontremos el núcleo del rango de fechas desde el primer día hábil de la semana hasta el último día de la semana del fin de semana. Esto nos permite calcular las semanas completas fácilmente ( sin iterar sobre todas las fechas). Todo lo que tenemos que hacer entonces es agregar los días hábiles que caen antes del inicio y el final de este rango básico.
public static int CalculateWorkingDays(
DateTime startDate,
DateTime endDate,
IList<DateTime> holidays,
DayOfWeek firstDayOfWeek,
DayOfWeek lastDayOfWeek)
{
// Make sure the defined working days run contiguously
if (lastDayOfWeek < firstDayOfWeek)
{
throw new Exception("Last day of week cannot fall before first day of week!");
}
// Create a list of the days of the week that make-up the weekend by working back
// from the firstDayOfWeek and forward from lastDayOfWeek to get the start and end
// the weekend
var weekendStart = lastDayOfWeek == DayOfWeek.Saturday ? DayOfWeek.Sunday : lastDayOfWeek + 1;
var weekendEnd = firstDayOfWeek == DayOfWeek.Sunday ? DayOfWeek.Saturday : firstDayOfWeek - 1;
var weekendDays = new List<DayOfWeek>();
var w = weekendStart;
do {
weekendDays.Add(w);
if (w == weekendEnd) break;
w = (w == DayOfWeek.Saturday) ? DayOfWeek.Sunday : w + 1;
} while (true);
// Force simple dates - no time
startDate = startDate.Date;
endDate = endDate.Date;
// Ensure a progessive date range
if (endDate < startDate)
{
var t = startDate;
startDate = endDate;
endDate = t;
}
// setup some working variables and constants
const int daysInWeek = 7; // yeah - really!
var actualStartDate = startDate; // this will end up on startOfWeek boundary
var actualEndDate = endDate; // this will end up on weekendEnd boundary
int workingDaysInWeek = daysInWeek - weekendDays.Count;
int workingDays = 0; // the result we are trying to find
int leadingDays = 0; // the number of working days leading up to the firstDayOfWeek boundary
int trailingDays = 0; // the number of working days counting back to the weekendEnd boundary
// Calculate leading working days
// if we aren't on the firstDayOfWeek we need to step forward to the nearest
if (startDate.DayOfWeek != firstDayOfWeek)
{
var d = startDate;
do {
if (d.DayOfWeek == firstDayOfWeek || d >= endDate)
{
actualStartDate = d;
break;
}
if (!weekendDays.Contains(d.DayOfWeek))
{
leadingDays++;
}
d = d.AddDays(1);
} while(true);
}
// Calculate trailing working days
// if we aren't on the weekendEnd we step back to the nearest
if (endDate >= actualStartDate && endDate.DayOfWeek != weekendEnd)
{
var d = endDate;
do {
if (d.DayOfWeek == weekendEnd || d < actualStartDate)
{
actualEndDate = d;
break;
}
if (!weekendDays.Contains(d.DayOfWeek))
{
trailingDays++;
}
d = d.AddDays(-1);
} while(true);
}
// Calculate the inclusive number of days between the actualStartDate and the actualEndDate
var coreDays = (actualEndDate - actualStartDate).Days + 1;
var noWeeks = coreDays / daysInWeek;
// add together leading, core and trailing days
workingDays += noWeeks * workingDaysInWeek;
workingDays += leadingDays;
workingDays += trailingDays;
// Finally remove any holidays that fall within the range.
if (holidays != null)
{
workingDays -= holidays.Count(h => h >= startDate && (h <= endDate));
}
return workingDays;
}
Ya que no puedo comentar. Hay un problema más con la solución aceptada en la que los días festivos se restan incluso cuando se encuentran en el fin de semana. Al ver cómo se verifican otras entradas, es lógico que esto también lo sea.
Por tanto, el foreach debería ser:
// subtract the number of bank holidays during the time interval
foreach (DateTime bankHoliday in bankHolidays)
{
DateTime bh = bankHoliday.Date;
// Do not subtract bank holidays when they fall in the weekend to avoid double subtraction
if (bh.DayOfWeek == DayOfWeek.Saturday || bh.DayOfWeek == DayOfWeek.Sunday)
continue;
if (firstDay <= bh && bh <= lastDay)
--businessDays;
}
Aquí hay un enfoque si está utilizando MVC. También he calculado que los días festivos nacionales o cualquier día festivo se excluirán obteniéndolo del calendario de vacaciones, que necesitará para crear uno.
foreach (DateTime day in EachDay(model))
{
bool key = false;
foreach (LeaveModel ln in holidaycalendar)
{
if (day.Date == ln.Date && day.DayOfWeek != DayOfWeek.Saturday && day.DayOfWeek != DayOfWeek.Sunday)
{
key = true; break;
}
}
if (day.DayOfWeek == DayOfWeek.Saturday || day.DayOfWeek == DayOfWeek.Sunday)
{
key = true;
}
if (key != true)
{
leavecount++;
}
}
Leavemodel es una lista aquí
Aquí hay una función auxiliar que escribí para esa tarea.
también devuelve el recuento de fines de semana mediante el out
parámetro.
Si lo desea, puede personalizar los días de "fin de semana" en tiempo de ejecución para países que usan diferentes días de fin de semana o para incluir festivos mediante el weekendDays[]
parámetro opcional:
public static int GetNetworkDays(DateTime startDate, DateTime endDate,out int totalWeekenDays, DayOfWeek[] weekendDays = null)
{
if (startDate >= endDate)
{
throw new Exception("start date can not be greater then or equel to end date");
}
DayOfWeek[] weekends = new DayOfWeek[] { DayOfWeek.Sunday, DayOfWeek.Saturday };
if (weekendDays != null)
{
weekends = weekendDays;
}
var totaldays = (endDate - startDate).TotalDays + 1; // add one to include the first day to
var counter = 0;
var workdaysCounter = 0;
var weekendsCounter = 0;
for (int i = 0; i < totaldays; i++)
{
if (weekends.Contains(startDate.AddDays(counter).DayOfWeek))
{
weekendsCounter++;
}
else
{
workdaysCounter++;
}
counter++;
}
totalWeekenDays = weekendsCounter;
return workdaysCounter;
}
Se me ocurrió la siguiente solución
var dateStart = new DateTime(2019,01,10);
var dateEnd = new DateTime(2019,01,31);
var timeBetween = (dateEnd - dateStart).TotalDays + 1;
int numberOf7DayWeeks = (int)(timeBetween / 7);
int numberOfWeekendDays = numberOf7DayWeeks * 2;
int workingDays =(int)( timeBetween - numberOfWeekendDays);
if(dateStart.DayOfWeek == DayOfWeek.Saturday || dateEnd.DayOfWeek == DayOfWeek.Sunday){
workingDays -=2;
}
if(dateStart.DayOfWeek == DayOfWeek.Sunday || dateEnd.DayOfWeek == DayOfWeek.Saturday){
workingDays -=1;
}
Solo tiene que recorrer cada día en el rango de tiempo y restar un día del contador si es sábado o domingo.
private float SubtractWeekend(DateTime start, DateTime end) {
float totaldays = (end.Date - start.Date).Days;
var iterationVal = totalDays;
for (int i = 0; i <= iterationVal; i++) {
int dayVal = (int)start.Date.AddDays(i).DayOfWeek;
if(dayVal == 6 || dayVal == 0) {
// saturday or sunday
totalDays--;
}
}
return totalDays;
}
public static int CalculateBusinessDaysInRange(this DateTime startDate, DateTime endDate, params DateTime[] holidayDates)
{
endDate = endDate.Date;
if(startDate > endDate)
throw new ArgumentException("The end date can not be before the start date!", nameof(endDate));
int accumulator = 0;
DateTime itterator = startDate.Date;
do
{
if(itterator.DayOfWeek != DayOfWeek.Saturday && itterator.DayOfWeek != DayOfWeek.Sunday && !holidayDates.Any(hol => hol.Date == itterator))
{ accumulator++; }
}
while((itterator = itterator.AddDays(1)).Date <= endDate);
return accumulator
}
Solo estoy publicando esto porque a pesar de todas las excelentes respuestas que se han dado, ninguna de las matemáticas tenía sentido para mí. Este es definitivamente un método KISS que debería funcionar y ser bastante fácil de mantener. Concedido, si está calculando rangos superiores a 2-3 meses, esta no será la forma más efectiva. Simplemente determinamos si es sábado o domingo o si la fecha es un día festivo determinado. Si no lo es, agregamos un día hábil. Si es así, todo está bien.
Estoy seguro de que esto podría simplificarse aún más con LINQ, pero de esta manera es mucho más fácil de entender.
Otro enfoque más para calcular los días hábiles, sin considerar los días festivos, pero teniendo en cuenta la hora del día que devuelve una fracción de días:
public static double GetBusinessDays(DateTime startD, DateTime endD)
{
while (IsWeekend(startD))
startD = startD.Date.AddDays(1);
while (IsWeekend(endD))
endD = endD.Date.AddDays(-1);
var bussDays = (endD - startD).TotalDays -
(2 * ((int)(endD - startD).TotalDays / 7)) -
(startD.DayOfWeek > endD.DayOfWeek ? 2 : 0);
return bussDays;
}
public static bool IsWeekend(DateTime d)
{
return d.DayOfWeek == DayOfWeek.Saturday || d.DayOfWeek == DayOfWeek.Sunday;
}
Puedes jugar con él aquí: https://rextester.com/ASHRS53997
Esta es una solución genérica.
startdayvalue es el número de día de la fecha de inicio.
weekendday_1 es el número de día del fin de semana.
número de día - LUN - 1, MAR - 2, ... SÁB - 6, DOM -7.
La diferencia es la diferencia entre dos fechas.
Ejemplo: Fecha de inicio: 4 de abril de 2013, Fecha de finalización: 14 de abril de 2013
Diferencia: 10, valor del día de inicio: 4, día de fin de semana_1: 7 (si DOMINGO es un fin de semana para usted).
Esto le dará una cantidad de vacaciones.
No de día hábil = (Diferencia + 1) - feriado1
if (startdayvalue > weekendday_1)
{
if (difference > ((7 - startdayvalue) + weekendday_1))
{
holiday1 = (difference - ((7 - startdayvalue) + weekendday_1)) / 7;
holiday1 = holiday1 + 1;
}
else
{
holiday1 = 0;
}
}
else if (startdayvalue < weekendday_1)
{
if (difference > (weekendday_1 - startdayvalue))
{
holiday1 = (difference - (weekendday_1 - startdayvalue)) / 7;
holiday1 = holiday1 + 1;
}
else if (difference == (weekendday_1 - startdayvalue))
{
holiday1 = 1;
}
else
{
holiday1 = 0;
}
}
else
{
holiday1 = difference / 7;
holiday1 = holiday1 + 1;
}