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>publicstaticintBusinessDaysUntil(thisDateTime firstDay,DateTime lastDay,paramsDateTime[] bankHolidays){
firstDay = firstDay.Date;
lastDay = lastDay.Date;if(firstDay > lastDay)thrownewArgumentException("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 weeksif(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 weeksint 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;elseif(lastDayOfWeek >=6)// Only Saturday is in the remaining time interval
businessDays -=1;}elseif(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 intervalforeach(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.Sundaytiene el valor, lo 7cual no es el caso. El valor es en realidad 0. Conduce a un cálculo incorrecto si, por ejemplo, firstDayy lastDayambos son el mismo domingo. El método regresa 1en 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 firstDayOfWeeky lastDayOfWeekse declaran por lo siguiente:
int firstDayOfWeek = firstDay.DayOfWeek==DayOfWeek.Sunday?7:(int)firstDay.DayOfWeek;int lastDayOfWeek = lastDay.DayOfWeek==DayOfWeek.Sunday?7:(int)lastDay.DayOfWeek;
+1 Esa es probablemente la forma más fácil y eficiente de hacerlo (mi solución que viene de C ++ no usa el soporte de TimeSpan, C # hace que algunas tareas sean mucho más fáciles). ¡The bankHolidays también es un buen toque!
RedGlyph
2
También asegúrese de que los días festivos sean los siguientes: if (firstDay <= bh && bh <= lastDay && bh.IsWorkingDay ())
Tawani
5
Gracias por el metodo. Aunque, tuve que agregar lo siguiente a la sustracción / iteración de los días festivos if-statement:, de lo && !(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.
KristianB
Cambié el último bucle de una declaración Linq: businessDays - = bankHolidays.Select (bankHoliday => bankHoliday.Date) .Count (bh => firstDay <= bh && bh <= lastDay);
Buen trabajo, pero ¿quizás usar las enumeraciones de DayOfWeek en lugar de lanzarlas a ints?
Neo
3
En serio, la mejor solución que existe. Cheers Alec
Mizmor
6
Tenga en cuenta que aunque esta función devuelve un doble, solo se debe confiar en días hábiles completos . No devuelve la respuesta correcta para días fraccionarios cuando se trata de tiempos.
Pakman
4
Solo para comentar, con el '1+' asume el inicio del primer día hasta el final del último día, sin el '1+' asume el final del primer día hasta el final del último día. Me tomó un tiempo darme cuenta de eso, ya que estaba asumiendo el comienzo del primer día hasta el comienzo del último día, lo que tenía más sentido para mí.
Jeffry van de Vuurst
11
Ésta NO es la respuesta correcta. Los días pueden terminar hasta en 4. Casi correcto, no tiene en cuenta cuándo el día de inicio y finaliza el fin de semana, que es la parte más complicada. El inicio y el final tampoco deben estar entre paréntesis. No tiene nada que ver con el problema. El 60% de las veces esta solución es INCORRECTA .
Búho
47
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.
publicintGetWorkingDays(DateTimefrom,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;}
Mucho más claro, y las soluciones enumeradas se prestan a eliminar los días festivos. Sin embargo, son mucho más lentos en masa; En LINQPad, el cálculo de los días laborables para los intervalos de 90 días en un ciclo de 1 millón de iteraciones toma 10 segundos con esta solución, y solo alrededor de 0,2 segundos con la respuesta aceptada o la mucho mejor de Alec Pojidaev.
Whelkaholism
Para ser inclusivo, el código debe ser: return Enumerable .Range (0, dayDifference + 1) ...
Edza
no devuelve días en el pasado. Como -18 días laborables.
iwtu
@iwtu Esto asume eso to > from. ¿Quizás ese es el problema?
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();
¿No seguiría adelante y ampliaría el período de tiempo también para poder usar eso, ya que él dijo que quería usar la distancia entre dos fechas y no una lista de fechas?
WesleyJohnson
La distancia entre las dos fechas es el número de días entre ellas, por lo que Count () es suficiente.
Carles Company
3
No estoy seguro de por qué esta es una respuesta adecuada ... no tiene una lista de días individuales, tiene dos fechas y quiere encontrar la cantidad de días hábiles entre ellas. Para usar esta solución, tendría que proporcionar otra función que produjera una lista de todas las fechas entre el twyp.
Adam Robinson
1
Adam, este es un ejemplo simple con la cantidad mínima de código que se necesita para demostrar un concepto. En mi respuesta original, también incluí un bucle que completó la lista allDates que desde entonces he abstraído en la función "GetDates". La prueba IsWorkingDay se puede mover fácilmente de la instrucción LINQ a ese bucle. Personalmente, me gusta cómo es ahora porque es muy legible por humanos en cuanto a lo que está sucediendo.
Qwerty
10
Se podría acortar cambiando Dónde contar y eliminando Contar
recursivo
12
Utilicé el siguiente código para incluir también los días festivos en la cuenta:
publicclassWorkingDays{publicList<DateTime>GetHolidays(){var client =newWebClient();var json = client.DownloadString("https://www.gov.uk/bank-holidays.json");var js =newJavaScriptSerializer();var holidays = js.Deserialize<Dictionary<string,Holidays>>(json);return holidays["england-and-wales"].events.Select(d => d.date).ToList();}publicintGetWorkingDays(DateTimefrom,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;}}publicclassHolidays{publicstring division {get;set;}publicList<Event> events {get;set;}}publicclassEvent{publicDateTime date {get;set;}publicstring notes {get;set;}publicstring title {get;set;}}
Y pruebas unitarias:
[TestClass]publicclassWorkingDays{[TestMethod]publicvoidSameDayIsZero(){var service =newWorkingDays();varfrom=newDateTime(2013,8,12);Assert.AreEqual(0, service.GetWorkingDays(from,from));}[TestMethod]publicvoidCalculateDaysInWorkingWeek(){var service =newWorkingDays();varfrom=newDateTime(2013,8,12);var to =newDateTime(2013,8,16);Assert.AreEqual(4, service.GetWorkingDays(from, to),"Mon - Fri = 4");Assert.AreEqual(1, service.GetWorkingDays(from,newDateTime(2013,8,13)),"Mon - Tues = 1");}[TestMethod]publicvoidNotIncludeWeekends(){var service =newWorkingDays();varfrom=newDateTime(2013,8,9);var to =newDateTime(2013,8,16);Assert.AreEqual(5, service.GetWorkingDays(from, to),"Fri - Fri = 5");Assert.AreEqual(2, service.GetWorkingDays(from,newDateTime(2013,8,13)),"Fri - Tues = 2");Assert.AreEqual(1, service.GetWorkingDays(from,newDateTime(2013,8,12)),"Fri - Mon = 1");}[TestMethod]publicvoidAccountForHolidays(){var service =newWorkingDays();varfrom=newDateTime(2013,8,23);Assert.AreEqual(0, service.GetWorkingDays(from,newDateTime(2013,8,26)),"Fri - Mon = 0");Assert.AreEqual(1, service.GetWorkingDays(from,newDateTime(2013,8,27)),"Fri - Tues = 1");}}
¿Por qué empiezas a contar agregando 1 día a "desde" @ para (var date = from.AddDays (1); date <= to; date = date.AddDays (1))?
Oncel Umut TURER
6
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.
publicstaticTimeSpanGetBusinessTimespanBetween(DateTime start,DateTime end,TimeSpan workdayStartTime,TimeSpan workdayEndTime,List<DateTime> holidays =null){if(end < start)thrownewArgumentException("start datetime must be before end datetime.");// Just create an empty list for easier coding.if(holidays ==null) holidays =newList<DateTime>();if(holidays.Where(x => x.TimeOfDay.Ticks>0).Any())thrownewArgumentException("holidays can not have a TimeOfDay, only the Date.");var nonWorkDays =newList<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 =newTimeSpan();// If start is during a non work day, it doesn't count.if(nonWorkDays.Contains(start.DayOfWeek)) timeBeforeEndOfWorkDay =newTimeSpan();elseif(holidays.Contains(start.Date)) timeBeforeEndOfWorkDay =newTimeSpan();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 =newTimeSpan();// If end is during a non work day, it doesn't count.if(nonWorkDays.Contains(end.DayOfWeek)) timeAfterStartOfWorkDay =newTimeSpan();elseif(holidays.Contains(end.Date)) timeAfterStartOfWorkDay =newTimeSpan();// Easy scenario if the times are during the day day.if(start.Date.CompareTo(end.Date)==0){if(nonWorkDays.Contains(start.DayOfWeek))returnnewTimeSpan();elseif(holidays.Contains(start.Date))returnnewTimeSpan();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;elseif(holidays.Contains(d.Date))continue;
businessDaysBetween++;}}var dailyWorkSecondsToAdd = dailyWorkSeconds * businessDaysBetween;var output = timeBeforeEndOfWorkDay + timeAfterStartOfWorkDay;
output = output +newTimeSpan(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]publicvoidTestGetBusinessTimespanBetween(){var workdayStart =newTimeSpan(8,0,0);var workdayEnd =newTimeSpan(17,0,0);var holidays =newList<DateTime>(){newDateTime(2018,1,15),// a MondaynewDateTime(2018,2,15)// a Thursday};var testdata =new[]{new{
expectedMinutes =0,
start =newDateTime(2016,10,19,9,50,0),
end =newDateTime(2016,10,19,9,50,0)},new{
expectedMinutes =10,
start =newDateTime(2016,10,19,9,50,0),
end =newDateTime(2016,10,19,10,0,0)},new{
expectedMinutes =5,
start =newDateTime(2016,10,19,7,50,0),
end =newDateTime(2016,10,19,8,5,0)},new{
expectedMinutes =5,
start =newDateTime(2016,10,19,16,55,0),
end =newDateTime(2016,10,19,17,5,0)},new{
expectedMinutes =15,
start =newDateTime(2016,10,19,16,50,0),
end =newDateTime(2016,10,20,8,5,0)},new{
expectedMinutes =10,
start =newDateTime(2016,10,19,16,50,0),
end =newDateTime(2016,10,20,7,55,0)},new{
expectedMinutes =5,
start =newDateTime(2016,10,19,17,10,0),
end =newDateTime(2016,10,20,8,5,0)},new{
expectedMinutes =0,
start =newDateTime(2016,10,19,17,10,0),
end =newDateTime(2016,10,20,7,5,0)},new{
expectedMinutes =545,
start =newDateTime(2016,10,19,12,10,0),
end =newDateTime(2016,10,20,12,15,0)},// Spanning multiple weekdaysnew{
expectedMinutes =835,
start =newDateTime(2016,10,19,12,10,0),
end =newDateTime(2016,10,21,8,5,0)},// Spanning multiple weekdaysnew{
expectedMinutes =1375,
start =newDateTime(2016,10,18,12,10,0),
end =newDateTime(2016,10,21,8,5,0)},// Spanning from a Thursday to a Tuesday, 5 mins short of complete day.new{
expectedMinutes =1615,
start =newDateTime(2016,10,20,12,10,0),
end =newDateTime(2016,10,25,12,5,0)},// Spanning from a Thursday to a Tuesday, 5 mins beyond complete day.new{
expectedMinutes =1625,
start =newDateTime(2016,10,20,12,10,0),
end =newDateTime(2016,10,25,12,15,0)},// Spanning from a Friday to a Monday, 5 mins beyond complete day.new{
expectedMinutes =545,
start =newDateTime(2016,10,21,12,10,0),
end =newDateTime(2016,10,24,12,15,0)},// Spanning from a Friday to a Monday, 5 mins short complete day.new{
expectedMinutes =535,
start =newDateTime(2016,10,21,12,10,0),
end =newDateTime(2016,10,24,12,5,0)},// Spanning from a Saturday to a Monday, 5 mins short complete day.new{
expectedMinutes =245,
start =newDateTime(2016,10,22,12,10,0),
end =newDateTime(2016,10,24,12,5,0)},// Spanning from a Saturday to a Sunday, 5 mins beyond complete day.new{
expectedMinutes =0,
start =newDateTime(2016,10,22,12,10,0),
end =newDateTime(2016,10,23,12,15,0)},// Times within the same Saturday.new{
expectedMinutes =0,
start =newDateTime(2016,10,22,12,10,0),
end =newDateTime(2016,10,23,12,15,0)},// Spanning from a Saturday to the Sunday next week.new{
expectedMinutes =2700,
start =newDateTime(2016,10,22,12,10,0),
end =newDateTime(2016,10,30,12,15,0)},// Spanning a year.new{
expectedMinutes =143355,
start =newDateTime(2016,10,22,12,10,0),
end =newDateTime(2017,10,30,12,15,0)},// Spanning a year with 2 holidays.new{
expectedMinutes =142815,
start =newDateTime(2017,10,22,12,10,0),
end =newDateTime(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:
privatestaticreadonlyint[,] _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};publicstaticDateTimeAddWeekdays(thisDateTime 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:
staticreadonlyint[,] _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};publicstaticintGetWeekdaysDiff(thisDateTime 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 !!
La idea para esto vino de una solución SQL que encontré en el desbordamiento de pila. Su idea era sólida, pero lamentablemente también tenía un error. Funcionó para los valores + ve pero su mapeo de la tabla de búsqueda era incorrecta para los valores -ve.
Tony O'Hagan
4
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
publicstaticintGetWorkdays(DateTimefrom,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){return0;}while(current <= to && counter < limit){if(IsSwedishWorkday(current)){
result++;}
current = current.AddDays(1);
counter++;}return result;}publicstaticboolIsSwedishWorkday(DateTime date){return(!IsSwedishHoliday(date)&& date.DayOfWeek!=DayOfWeek.Saturday&& date.DayOfWeek!=DayOfWeek.Sunday);}publicstaticboolIsSwedishHoliday(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));}// TrettondagenpublicstaticDateTimeGetEpiphanyDay(int year){returnnewDateTime(year,1,6);}// Första majpublicstaticDateTimeGetMayDay(int year){returnnewDateTime(year,5,1);}// JuldagenpublicstaticDateTimeGetSwedishNationalDay(int year){returnnewDateTime(year,6,6);}// JuldagenpublicstaticDateTimeGetNewYearsDay(int year){returnnewDateTime(year,1,1);}// JuldagenpublicstaticDateTimeGetChristmasDay(int year){returnnewDateTime(year,12,25);}// Annandag julpublicstaticDateTimeGetBoxingDay(int year){returnnewDateTime(year,12,26);}// LångfredagenpublicstaticDateTimeGetGoodFriday(int year){returnGetEasterDay(year).AddDays(-3);}// Kristi himmelsfärdsdagpublicstaticDateTimeGetAscensionDay(int year){returnGetEasterDay(year).AddDays(5*7+4);}// MidsommarpublicstaticDateTimeGetAllSaintsDay(int year){DateTime result =newDateTime(year,10,31);while(result.DayOfWeek!=DayOfWeek.Saturday){
result = result.AddDays(1);}return result;}// MidsommarpublicstaticDateTimeGetMidsummersDay(int year){DateTime result =newDateTime(year,6,20);while(result.DayOfWeek!=DayOfWeek.Saturday){
result = result.AddDays(1);}return result;}// PingstdagenpublicstaticDateTimeGetPentecostDay(int year){returnGetEasterDay(year).AddDays(7*7);}// Annandag påskpublicstaticDateTimeGetEasterMonday(int year){returnGetEasterDay(year).AddDays(1);}publicstaticDateTimeGetEasterDay(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);// daydouble days =((m ==3)? d : d +31);DateTime result =newDateTime(y,3,1).AddDays(days-1);return result;}
la función issamedate falta pero es simplemente public static bool IsSameDay (DateTime date1, DateTime date2) {return date1.Date == date2.Date; }
Choco Smith
Puede usar una tabla de búsqueda de matriz int en lugar de crear instancias de nuevos objetos Date.
TheRealChx101
3
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).
privateIEnumerable<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)yieldreturn d;}
Este método crea una variable de bucle d, la inicializa al día de inicio sdy 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 IEnumerableen 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.
Este método no devuelve el número de días hábiles entre dos fechas, devuelve las fechas hábiles entre dos fechas. El código que propones es muy limpio y me gusta el uso de yield, pero no responde a la pregunta.
Martín
3
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:
publicstaticintNumberOfWorkingDaysBetween2Dates(DateTime start,DateTime due,IEnumerable<DateTime> holidays){var dic =newDictionary<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:
No es sábado
No es domingo
No es fiesta nacional
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:
staticvoidMain(string[] args){var start =newDateTime(2017,1,1);var due =newDateTime(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}
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:
publicdoubleWorkDays(DateTime startDate,DateTime endDate){double weekendDays;double days = endDate.Subtract(startDate).TotalDays;if(days<0)return0;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 daysdouble diffStart = startDate.Subtract(startMonday).TotalDays-5;double diffEnd = endDate.Subtract(endMonday).TotalDays-5;// compensate weekenddaysif(diffStart>0) weekendDays -= diffStart;if(diffEnd>0) weekendDays += diffEnd;return days - weekendDays;}
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
t: Número total de días entre fechas (1 si min = max)
a + b: días adicionales necesarios para expandir el total a semanas completas
k: 1.4 es el número de días de la semana por semana, es decir, (t / 7) * 5
c: Número de días de la semana para restar del total
m: una tabla de búsqueda utilizada para encontrar el valor de "c" para cada día de la semana
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
publicintWeekdays(DateTime min,DateTime max){if(min.Date> max.Date)thrownewException("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 =newint[]{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.
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 outif there are weekends during the time exceedng the full weeks
IF @BusinessDays>(@FullWeekCount*7)
BEGIN
-- we are here to find outif 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)--BothSaturday and Sunday are in the remaining time interval
BEGIN
SELECT @BusinessDays=@BusinessDays-2
END
ELSE IF @lastDayOfWeek>=6--OnlySaturdayisin the remaining time interval
BEGIN
SELECT @BusinessDays=@BusinessDays-1
END
END
ELSE IF @firstDayOfWeek<=7 AND @lastDayOfWeek>=7--OnlySundayisin 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
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.
classProgram{staticvoidMain(string[] args){DateTime day =newDateTime();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>publicstaticintBusinessHoursUntil(DateTime firstDay,DateTime lastDay,paramsDateTime[] 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 weeksif(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 weeksint 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;elseif(lastDayOfWeek >=6)// Only Saturday is in the remaining time interval
businessDays -=1;}elseif(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 intervalforeach(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>publicstaticintBusinessDaysUntil(thisDateTime fDay,DateTime lDay,DayOfWeek[]BusinessDaysOfWeek=null,DateTime[]Holidays=null){if(BusinessDaysOfWeek==null)BusinessDaysOfWeek=newDayOfWeek[]{DayOfWeek.Monday,DayOfWeek.Tuesday,DayOfWeek.Wednesday,DayOfWeek.Thursday,DayOfWeek.Friday};if(Holidays==null)Holidays=newDateTime[]{};
fDay = fDay.Date;
lDay = lDay.Date;if(fDay > lDay)thrownewArgumentException("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 weeksif(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 subtractionif(lDayOfWeek != fDayOfWeek){// Here we need to see if any of the days between are considered business daysfor(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
publicstaticintGetBussinessDaysBetweenTwoDates(DateTimeStartDate,DateTimeEndDate){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;}
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.
publicstaticintCalculateWorkingDays(DateTime startDate,DateTime endDate,IList<DateTime> holidays,DayOfWeek firstDayOfWeek,DayOfWeek lastDayOfWeek){// Make sure the defined working days run contiguouslyif(lastDayOfWeek < firstDayOfWeek){thrownewException("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 weekendvar weekendStart = lastDayOfWeek ==DayOfWeek.Saturday?DayOfWeek.Sunday: lastDayOfWeek +1;var weekendEnd = firstDayOfWeek ==DayOfWeek.Sunday?DayOfWeek.Saturday: firstDayOfWeek -1;var weekendDays =newList<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 rangeif(endDate < startDate){var t = startDate;
startDate = endDate;
endDate = t;}// setup some working variables and constantsconstint daysInWeek =7;// yeah - really!var actualStartDate = startDate;// this will end up on startOfWeek boundaryvar actualEndDate = endDate;// this will end up on weekendEnd boundaryint workingDaysInWeek = daysInWeek - weekendDays.Count;int workingDays =0;// the result we are trying to findint leadingDays =0;// the number of working days leading up to the firstDayOfWeek boundaryint 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 nearestif(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 nearestif(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 actualEndDatevar 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 intervalforeach(DateTime bankHoliday in bankHolidays){DateTime bh = bankHoliday.Date;// Do not subtract bank holidays when they fall in the weekend to avoid double subtractionif(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 inEachDay(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++;}}
Aquí hay una función auxiliar que escribí para esa tarea. también devuelve el recuento de fines de semana mediante el outpará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:
publicstaticintGetNetworkDays(DateTime startDate,DateTime endDate,outint totalWeekenDays,DayOfWeek[] weekendDays =null){if(startDate >= endDate){thrownewException("start date can not be greater then or equel to end date");}DayOfWeek[] weekends =newDayOfWeek[]{DayOfWeek.Sunday,DayOfWeek.Saturday};if(weekendDays !=null){
weekends = weekendDays;}var totaldays =(endDate - startDate).TotalDays+1;// add one to include the first day tovar 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;}
publicstaticintCalculateBusinessDaysInRange(thisDateTime startDate,DateTime endDate,paramsDateTime[] holidayDates){
endDate = endDate.Date;if(startDate > endDate)thrownewArgumentException("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:
Respuestas:
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.
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, lo7
cual no es el caso. El valor es en realidad0
. Conduce a un cálculo incorrecto si, por ejemplo,firstDay
ylastDay
ambos son el mismo domingo. El método regresa1
en este caso pero debería serlo0
.La solución más fácil para este error: reemplace en el código sobre las líneas donde
firstDayOfWeek
ylastDayOfWeek
se declaran por lo siguiente:Ahora el resultado es:
fuente
&& !(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:
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.
fuente
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:
Esta fue mi presentación original:
fuente
to > from
. ¿Quizás ese es el problema?Defina un método de extensión en DateTime así:
Luego, use está dentro de una cláusula Where para filtrar una lista más amplia de fechas:
fuente
Utilicé el siguiente código para incluir también los días festivos en la cuenta:
Y pruebas unitarias:
fuente
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.
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.
fuente
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:
Calcular la diferencia entre semana:
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.
fuente
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
fuente
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 aprivate static
(opublic static
).Este método crea una variable de bucle
d
, la inicializa al día de iniciosd
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 uniterator
. Lo bueno de los iteradores es que no contienen todos los valores deIEnumerable
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.fuente
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:
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:
Editar: un nuevo método más simple:
fuente
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:
fuente
fuente
Funciona y sin bucles
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
fuente
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.
fuente
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.
fuente
fuente
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.
fuente
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.
fuente
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:
fuente
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
fuente
Creo que esta podría ser una forma más sencilla:
fuente
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.
fuente
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:
fuente
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.
Leavemodel es una lista aquí
fuente
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:fuente
Se me ocurrió la siguiente solución
fuente
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.
fuente
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.
fuente
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:
Puedes jugar con él aquí: https://rextester.com/ASHRS53997
fuente
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
fuente