Já tive essa tarefa antes e encontrei a solução. Eu evitaria enumerar todos os dias intermediários em que é evitável, o que é o caso aqui. Nem mencionei a criação de várias instâncias de DateTime, como vi em uma das respostas acima. Isso é realmente um desperdício de poder de processamento. Especialmente na situação do mundo real, quando você tem que examinar intervalos de tempo de vários meses. Veja meu código, com comentários, abaixo.
/// <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;}
Editado por Slauma, agosto de 2011
Ótima resposta! No entanto, há um pequeno bug. Tomo a liberdade de editar esta resposta, pois o respondente está ausente desde 2009.
O código acima assume que DayOfWeek.Sundaypossui o valor 7que não é o caso. O valor é realmente 0. Isso leva a um cálculo errado se, por exemplo, firstDaye lastDayforem ambos no mesmo domingo. O método retorna 1neste caso, mas deveria ser 0.
A correção mais fácil para esse bug: substitua no código acima das linhas onde firstDayOfWeeke lastDayOfWeeksão declaradas pelo seguinte:
int firstDayOfWeek = firstDay.DayOfWeek==DayOfWeek.Sunday?7:(int)firstDay.DayOfWeek;int lastDayOfWeek = lastDay.DayOfWeek==DayOfWeek.Sunday?7:(int)lastDay.DayOfWeek;
+1 Essa é provavelmente a maneira mais fácil e eficiente de fazer isso (minha solução vinda de C ++ não usa o suporte de TimeSpan, C # torna algumas tarefas muito mais fáceis). O bankHolidays também é um toque bacana!
RedGlyph
2
Verifique também os feriados bancários da seguinte forma: if (firstDay <= bh && bh <= lastDay && bh.IsWorkingDay ())
Tawani
5
Obrigado pelo método. Embora, eu tivesse que adicionar o seguinte à subtração / iteração if-statement de feriados:, do && !(bh.DayOfWeek == DayOfWeek.Sunday || bh.DayOfWeek == DayOfWeek.Saturday)contrário, ela subtrairia o mesmo dia duas vezes, se um feriado cair em um fim de semana.
KristianB
Mudei o último loop de uma instrução Linq: businessDays - = bankHolidays.Select (bankHoliday => bankHoliday.Date) .Count (bh => firstDay <= bh && bh <= lastDay);
Bom trabalho, mas talvez usar os enums do DayOfWeek em vez de jogá-los em ints?
Neo
3
Sério, a melhor solução que existe. Cheers Alec
Mizmor
6
Observe que, embora essa função retorne um dobro, ela deve ser confiável para fornecer dias úteis inteiros . Ele não retorna a resposta correta para dias fracionários quando há tempos envolvidos.
Pakman
4
Apenas para observar, com o '1+' ele assume o início do primeiro dia até o final do último dia, sem o '1+' ele assume o final do primeiro dia até o final do último dia. Levei um tempo para descobrir isso, já que eu estava assumindo o início do primeiro dia até o início do último dia, o que fez mais sentido para mim.
Jeffry van de Vuurst
11
Esta NÃO é a resposta correta. Os dias podem acabar em até 4. Quase certo, não leva em conta quando o dia de início e fim termina no fim de semana, que é a parte mais complicada. O início - fim também não deve estar entre parênteses. Não tem nada a ver com o problema. 60% das vezes, essa solução é ERRADA .
Owl
47
Sei que esta questão já foi resolvida, mas achei que poderia fornecer uma resposta mais direta que possa ajudar outros visitantes no 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;}
Muito mais claro, e as soluções enumeradas se prestam a eliminar feriados bancários. Eles são muito mais lentos em massa; No LINQPad, o cálculo de dias úteis para intervalos de 90 dias em um loop de 1 milhão de iterações leva 10s usando essa solução e apenas cerca de 0,2s usando a resposta aceita ou a muito mais agradável de Alec Pojidaev.
Whelkaholism
Para ser inclusivo, o código deve ser: return Enumerable .Range (0, dayDifference + 1) ...
Edza
não retorna dias no passado. Como -18 dias úteis.
iwtu 01 de
@iwtu Isso pressupõe isso to > from. Talvez seja esse o problema?
Em seguida, use está dentro de uma cláusula Where para filtrar uma lista mais ampla de datas:
var allDates =GetDates();// method which returns a list of dates// filter dates by working day's var countOfWorkDays = allDates
.Where(day => day.IsWorkingDay()).Count();
Você não poderia simplesmente estender o intervalo de tempo também para poder usar isso - já que ele disse que queria usar a distância entre duas datas e não uma lista de datas?
WesleyJohnson
A distância entre as duas datas é o número de dias entre elas, portanto, Count () é suficiente.
Carles Company
3
Não sei por que essa é uma resposta adequada ... ele não tem uma lista de dias individuais, ele tem duas datas e quer saber o número de dias úteis entre elas. Para usar esta solução, você teria que fornecer outra função que produzisse uma lista de todas as datas entre o twyp.
Adam Robinson
1
adam, este é um exemplo simples com a quantidade mínima de código necessária para demonstrar um conceito. Em minha resposta original, também incluí um loop que preencheu a lista allDates que desde então abstraia na função "GetDates". O teste IsWorkingDay pode ser facilmente movido da instrução LINQ para esse loop. Eu pessoalmente gosto de como está agora, porque é muito humano legível quanto ao que está acontecendo.
Qwerty
10
Pode ser encurtado alterando Onde contar e eliminando Contagem
recursivo
12
Usei o seguinte código para também levar em conta feriados bancários:
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;}}
E testes de unidade:
[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 que você começa a contar adicionando 1 dia a "de" @ para (var date = from.AddDays (1); date <= to; date = date.AddDays (1))?
Oncel Umut TURER
6
Bem, isso foi espancado até a morte. :) No entanto, darei outra resposta porque precisava de algo um pouco diferente. Esta solução é diferente porque retorna um Business TimeSpan entre o início e o fim, e você pode definir o horário comercial do dia e adicionar feriados. Portanto, você pode usá-lo para calcular se isso acontece em um dia, em vários dias, nos finais de semana e até em feriados. E você pode obter apenas os dias úteis ou não, apenas obtendo o que precisa do objeto TimeSpan retornado. E da forma como ele usa listas de dias, você pode ver como seria muito fácil adicionar a lista de dias não úteis se não fosse o típico sábado e sol. E eu testei por um ano, e parece super rápido.
Só espero que a colagem do código esteja correta. Mas eu sei 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;}}
E aqui está o código de teste: Observe que você só precisa colocar essa função em uma classe chamada DateHelper para que o código de teste funcione.
[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);}}
Essa solução evita a iteração, funciona para diferenças de dias da semana + ve e -ve e inclui um conjunto de testes de unidade para regressão em relação ao método mais lento de contagem dos dias da semana. Também incluí um método conciso para adicionar dias da semana que também funciona da mesma maneira não iterativa.
Os testes de unidade cobrem alguns milhares de combinações de datas para testar exaustivamente todas as combinações de início / fim da semana com intervalos de datas grandes e pequenos.
Importante : presumimos que estamos contando os dias, excluindo a data de início e incluindo a data de término. Isso é importante ao contar os dias da semana, pois os dias de início / término específicos que você inclui / exclui afetam o resultado. Isso também garante que a diferença entre dois dias iguais seja sempre zero e que incluamos apenas dias úteis completos, pois normalmente você deseja que a resposta seja correta para qualquer horário na data de início atual (geralmente hoje) e inclua a data de término completa (por exemplo uma data de vencimento).
NOTA: Este código precisa de um ajuste adicional para feriados, mas de acordo com a suposição acima, este código deve excluir feriados na data de início.
Adicionar dias da 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);}
Calcule a diferença do dia da 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];}
Descobri que a maioria das outras soluções no estouro de pilha eram lentas (iterativas) ou excessivamente complexas e muitas eram simplesmente incorretas. Moral da história é ... Não confie a menos que você tenha testado exaustivamente !!
A ideia para isso veio de uma solução SQL que encontrei no estouro de pilha. A ideia deles era sólida, mas infelizmente também tinha um bug. Funcionou para + cinco valores, mas o mapeamento da tabela de pesquisa estava incorreto para valores -ve.
Tony O'Hagan
4
Aqui está um código para esse fim, com feriados suecos, mas você pode adaptar quais feriados contar. Observe que eu adicionei um limite que você pode querer remover, mas era para um sistema baseado na web e eu não queria que ninguém inserisse uma data enorme para monopolizar o processo
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;}
a função issamedate está ausente, mas é simplesmente público estático bool IsSameDay (DateTime date1, DateTime date2) {return date1.Date == date2.Date; }
Choco Smith
Você poderia usar uma tabela de pesquisa int array em vez de instanciar novos objetos Date.
TheRealChx101
3
Aqui está um código de exemplo rápido. É um método de classe, então só funcionará dentro de sua classe. Se desejar static, altere a assinatura para private static(ou 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 cria uma variável de loop d, inicializa-a no dia de início sde aumenta em um dia a cada iteração ( d = d.AddDays(1)).
Ele retorna os valores desejados usando yield, o que cria um iterator. O legal dos iteradores é que eles não mantêm todos os valores de IEnumerablena memória, apenas chamando cada um sequencialmente. Isso significa que você pode chamar esse método desde o início dos tempos até agora, sem ter que se preocupar em ficar sem memória.
Este método não retorna o número de dias úteis entre duas datas, ele retorna as datas comerciais entre duas datas. O código que você está propondo é muito claro e gosto do uso de yield, mas não responde à pergunta.
Martin
3
Pesquisei muito por um algoritmo fácil de digerir para calcular os dias úteis entre 2 datas e também para excluir os feriados nacionais e, finalmente, decidi seguir esta abordagem:
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();}
Basicamente, eu queria ir com cada data e avaliar minhas condições:
Não é sábado
Não é domingo
Não é feriado nacional
mas também queria evitar a iteração de datas.
Ao correr e medir o tempo necessário para avaliar 1 ano completo, obtenho o seguinte 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}
Acho que nenhuma das respostas acima está realmente correta. Nenhum deles resolve todos os casos especiais, como quando as datas começam e terminam no meio de um fim de semana, quando a data começa em uma sexta-feira e termina na próxima segunda-feira, etc. Além disso, todos eles arredondam os cálculos para todo dias, então se a data de início for no meio de um sábado por exemplo, vai subtrair um dia inteiro dos dias úteis, dando resultados errados ...
De qualquer forma, aqui está a minha solução que é bastante eficiente e simples e funciona para todos os casos. O truque é apenas encontrar a segunda-feira anterior para as datas de início e término e, em seguida, fazer uma pequena compensação quando o início e o fim acontecerem durante o fim 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 não usa nenhum laço e é bastante simples. Ele expande o intervalo de datas para semanas inteiras, pois sabemos que cada semana tem 5 dias úteis. Em seguida, usa uma tabela de consulta para encontrar o número de dias úteis a subtrair do início e do fim para obter o resultado correto. Expandi o cálculo para ajudar a mostrar o que está acontecendo, mas a coisa toda pode ser condensada em uma única linha, se necessário.
Enfim, isso funciona para mim e então pensei em postá-lo aqui caso possa ajudar outras pessoas. Boa codificação.
Cálculo
t: Número total de dias entre as datas (1 se mín = máx)
a + b: dias extras necessários para expandir o total para semanas inteiras
k: 1,4 é o número de dias da semana por semana, ou seja, (t / 7) * 5
c: Número de dias da semana a subtrair do total
m: uma tabela de pesquisa usada para encontrar o valor de "c" para cada dia da semana
Cultura
O código pressupõe uma semana de trabalho de segunda a sexta-feira. Para outras culturas, como domingo a quinta-feira, você precisará compensar as datas antes do 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;}
Vou apenas compartilhar minha solução. Funcionou para mim, talvez eu simplesmente não note / saiba que existe um bug. Comecei obtendo a primeira semana incompleta, se houver alguma. uma semana completa era de domingo a sábado, portanto, se (int) _now.DayOfWeek não fosse 0 (domingo), a primeira semana estava incompleta.
Acabei de subtrair 1 à contagem das primeiras semanas para o sábado da primeira semana e adicioná-la à nova contagem;
Então, eu obtenho a última semana incompleta, subtraio 1 para o domingo e acrescento à nova contagem.
Então, finalmente, o número de semanas completas multiplicadas por 5 (dias da semana) foi adicionado à nova contagem.
Eu estava tendo problemas para encontrar uma versão TSQL sólida deste código. Abaixo está essencialmente uma conversão do código C # aqui com adição da tabela de feriados que deve ser usada para pré-calcular os feriados.
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
Aqui está uma solução muito simples para esse problema. Temos data de início, data de término e "for loop" para aumentar o dia e calcular para ver se é um dia de trabalho ou um fim de semana, convertendo para a string 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);}}
Com base no comentário marcado como resposta e patch recomendado, bem como -> Esta versão deseja converter os Dias em Horário Comercial ... Considera os mesmos dias também.
/// <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;}
Acabei de melhorar o @Alexander e o @Slauma answer para dar suporte a uma semana útil como parâmetro, para casos em que sábado é um dia útil, ou mesmo casos em que apenas alguns dias da semana são considerados dias úteis:
/// <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;}
Aqui está a função que podemos usar para calcular dias úteis entre duas datas. Não estou usando a lista de feriados, pois ela pode variar de acordo com o país / região.
Se quisermos usá-lo de qualquer maneira, podemos tomar o terceiro argumento como lista de feriados e antes de aumentar a contagem devemos verificar se a lista não contém 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;}
Aqui está outra ideia - este método permite especificar qualquer semana de trabalho e feriados.
A ideia aqui é encontrarmos o núcleo do intervalo de datas do primeiro primeiro dia útil da semana até o último dia de fim de semana da semana. Isso nos permite calcular as semanas inteiras facilmente ( sem iterar em todas as datas). Tudo o que precisamos fazer então é adicionar os dias úteis que caem antes do início e do final deste intervalo principal.
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;}
Já que não posso comentar. Há mais um problema com a solução aceita, em que os feriados bancários são subtraídos mesmo quando são no fim de semana. Vendo como outras entradas são verificadas, é justo que isso também seja.
Portanto, o foreach deve 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;}
Aqui está uma abordagem se você estiver usando MVC. Também calculei feriados nacionais ou quaisquer dias festivos a serem excluídos, obtendo-o no calendário de feriados, que você precisará fazer um.
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++;}}
Aqui está uma função auxiliar que escrevi para essa tarefa. ele também retorna a contagem de fins de semana por meio do outparâmetro. se desejar, pode personalizar os dias de "fim de semana" em tempo de execução para países que usam dias de fim de semana diferentes ou incluir feriados através do 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
}
Só estou postando isso porque, apesar de todas as respostas excelentes que foram dadas, nenhuma matemática fazia sentido para mim. Este é definitivamente um método KISS que deve funcionar e ser razoavelmente sustentável. Concedido, se você estiver calculando intervalos superiores a 2-3 meses, essa não será a maneira mais eficaz. Nós simplesmente determinamos se é um sábado ou domingo ou se a data é uma determinada data de feriado. Caso contrário, adicionamos um dia útil. Se for, então está tudo bem.
Tenho certeza de que isso poderia ser ainda mais simplificado com o LINQ, mas dessa forma é muito mais fácil de entender.
Outra abordagem para calcular dias úteis, não considerando feriados, mas levando em consideração a hora do dia retornando uma quantidade fracionária de dias:
Respostas:
Já tive essa tarefa antes e encontrei a solução. Eu evitaria enumerar todos os dias intermediários em que é evitável, o que é o caso aqui. Nem mencionei a criação de várias instâncias de DateTime, como vi em uma das respostas acima. Isso é realmente um desperdício de poder de processamento. Especialmente na situação do mundo real, quando você tem que examinar intervalos de tempo de vários meses. Veja meu código, com comentários, abaixo.
Editado por Slauma, agosto de 2011
Ótima resposta! No entanto, há um pequeno bug. Tomo a liberdade de editar esta resposta, pois o respondente está ausente desde 2009.
O código acima assume que
DayOfWeek.Sunday
possui o valor7
que não é o caso. O valor é realmente0
. Isso leva a um cálculo errado se, por exemplo,firstDay
elastDay
forem ambos no mesmo domingo. O método retorna1
neste caso, mas deveria ser0
.A correção mais fácil para esse bug: substitua no código acima das linhas onde
firstDayOfWeek
elastDayOfWeek
são declaradas pelo seguinte:Agora o resultado é:
fonte
&& !(bh.DayOfWeek == DayOfWeek.Sunday || bh.DayOfWeek == DayOfWeek.Saturday)
contrário, ela subtrairia o mesmo dia duas vezes, se um feriado cair em um fim de semana.Está bem. Acho que é hora de postar a resposta certa:
Fonte original:
http://alecpojidaev.wordpress.com/2009/10/29/work-days-calculation-with-c/
Soluções PS postadas acima me deixando doente por algum motivo.
fonte
Sei que esta questão já foi resolvida, mas achei que poderia fornecer uma resposta mais direta que possa ajudar outros visitantes no futuro.
Aqui está minha opinião sobre isso:
Este foi o meu envio original:
fonte
to > from
. Talvez seja esse o problema?Defina um método de extensão em DateTime assim:
Em seguida, use está dentro de uma cláusula Where para filtrar uma lista mais ampla de datas:
fonte
Usei o seguinte código para também levar em conta feriados bancários:
E testes de unidade:
fonte
Bem, isso foi espancado até a morte. :) No entanto, darei outra resposta porque precisava de algo um pouco diferente. Esta solução é diferente porque retorna um Business TimeSpan entre o início e o fim, e você pode definir o horário comercial do dia e adicionar feriados. Portanto, você pode usá-lo para calcular se isso acontece em um dia, em vários dias, nos finais de semana e até em feriados. E você pode obter apenas os dias úteis ou não, apenas obtendo o que precisa do objeto TimeSpan retornado. E da forma como ele usa listas de dias, você pode ver como seria muito fácil adicionar a lista de dias não úteis se não fosse o típico sábado e sol. E eu testei por um ano, e parece super rápido.
Só espero que a colagem do código esteja correta. Mas eu sei que funciona.
E aqui está o código de teste: Observe que você só precisa colocar essa função em uma classe chamada DateHelper para que o código de teste funcione.
fonte
Essa solução evita a iteração, funciona para diferenças de dias da semana + ve e -ve e inclui um conjunto de testes de unidade para regressão em relação ao método mais lento de contagem dos dias da semana. Também incluí um método conciso para adicionar dias da semana que também funciona da mesma maneira não iterativa.
Os testes de unidade cobrem alguns milhares de combinações de datas para testar exaustivamente todas as combinações de início / fim da semana com intervalos de datas grandes e pequenos.
Importante : presumimos que estamos contando os dias, excluindo a data de início e incluindo a data de término. Isso é importante ao contar os dias da semana, pois os dias de início / término específicos que você inclui / exclui afetam o resultado. Isso também garante que a diferença entre dois dias iguais seja sempre zero e que incluamos apenas dias úteis completos, pois normalmente você deseja que a resposta seja correta para qualquer horário na data de início atual (geralmente hoje) e inclua a data de término completa (por exemplo uma data de vencimento).
NOTA: Este código precisa de um ajuste adicional para feriados, mas de acordo com a suposição acima, este código deve excluir feriados na data de início.
Adicionar dias da semana:
Calcule a diferença do dia da semana:
Descobri que a maioria das outras soluções no estouro de pilha eram lentas (iterativas) ou excessivamente complexas e muitas eram simplesmente incorretas. Moral da história é ... Não confie a menos que você tenha testado exaustivamente !!
Testes de unidade baseados em testes NUnit Combinatorial e extensão ShouldBe NUnit.
fonte
Aqui está um código para esse fim, com feriados suecos, mas você pode adaptar quais feriados contar. Observe que eu adicionei um limite que você pode querer remover, mas era para um sistema baseado na web e eu não queria que ninguém inserisse uma data enorme para monopolizar o processo
fonte
Aqui está um código de exemplo rápido. É um método de classe, então só funcionará dentro de sua classe. Se desejar
static
, altere a assinatura paraprivate static
(oupublic static
).Este método cria uma variável de loop
d
, inicializa-a no dia de iníciosd
e aumenta em um dia a cada iteração (d = d.AddDays(1)
).Ele retorna os valores desejados usando
yield
, o que cria umiterator
. O legal dos iteradores é que eles não mantêm todos os valores deIEnumerable
na memória, apenas chamando cada um sequencialmente. Isso significa que você pode chamar esse método desde o início dos tempos até agora, sem ter que se preocupar em ficar sem memória.fonte
Pesquisei muito por um algoritmo fácil de digerir para calcular os dias úteis entre 2 datas e também para excluir os feriados nacionais e, finalmente, decidi seguir esta abordagem:
Basicamente, eu queria ir com cada data e avaliar minhas condições:
mas também queria evitar a iteração de datas.
Ao correr e medir o tempo necessário para avaliar 1 ano completo, obtenho o seguinte resultado:
Edit: um novo método mais simples:
fonte
Acho que nenhuma das respostas acima está realmente correta. Nenhum deles resolve todos os casos especiais, como quando as datas começam e terminam no meio de um fim de semana, quando a data começa em uma sexta-feira e termina na próxima segunda-feira, etc. Além disso, todos eles arredondam os cálculos para todo dias, então se a data de início for no meio de um sábado por exemplo, vai subtrair um dia inteiro dos dias úteis, dando resultados errados ...
De qualquer forma, aqui está a minha solução que é bastante eficiente e simples e funciona para todos os casos. O truque é apenas encontrar a segunda-feira anterior para as datas de início e término e, em seguida, fazer uma pequena compensação quando o início e o fim acontecerem durante o fim de semana:
fonte
fonte
Funciona e sem loops
Este método não usa nenhum laço e é bastante simples. Ele expande o intervalo de datas para semanas inteiras, pois sabemos que cada semana tem 5 dias úteis. Em seguida, usa uma tabela de consulta para encontrar o número de dias úteis a subtrair do início e do fim para obter o resultado correto. Expandi o cálculo para ajudar a mostrar o que está acontecendo, mas a coisa toda pode ser condensada em uma única linha, se necessário.
Enfim, isso funciona para mim e então pensei em postá-lo aqui caso possa ajudar outras pessoas. Boa codificação.
Cálculo
Cultura
O código pressupõe uma semana de trabalho de segunda a sexta-feira. Para outras culturas, como domingo a quinta-feira, você precisará compensar as datas antes do cálculo.
Método
fonte
Vou apenas compartilhar minha solução. Funcionou para mim, talvez eu simplesmente não note / saiba que existe um bug. Comecei obtendo a primeira semana incompleta, se houver alguma. uma semana completa era de domingo a sábado, portanto, se (int) _now.DayOfWeek não fosse 0 (domingo), a primeira semana estava incompleta.
Acabei de subtrair 1 à contagem das primeiras semanas para o sábado da primeira semana e adicioná-la à nova contagem;
Então, eu obtenho a última semana incompleta, subtraio 1 para o domingo e acrescento à nova contagem.
Então, finalmente, o número de semanas completas multiplicadas por 5 (dias da semana) foi adicionado à nova contagem.
fonte
Eu estava tendo problemas para encontrar uma versão TSQL sólida deste código. Abaixo está essencialmente uma conversão do código C # aqui com adição da tabela de feriados que deve ser usada para pré-calcular os feriados.
fonte
fonte
Aqui está uma solução muito simples para esse problema. Temos data de início, data de término e "for loop" para aumentar o dia e calcular para ver se é um dia de trabalho ou um fim de semana, convertendo para a string DayOfWeek.
fonte
Com base no comentário marcado como resposta e patch recomendado, bem como -> Esta versão deseja converter os Dias em Horário Comercial ... Considera os mesmos dias também.
fonte
Acabei de melhorar o @Alexander e o @Slauma answer para dar suporte a uma semana útil como parâmetro, para casos em que sábado é um dia útil, ou mesmo casos em que apenas alguns dias da semana são considerados dias úteis:
fonte
Aqui está a função que podemos usar para calcular dias úteis entre duas datas. Não estou usando a lista de feriados, pois ela pode variar de acordo com o país / região.
Se quisermos usá-lo de qualquer maneira, podemos tomar o terceiro argumento como lista de feriados e antes de aumentar a contagem devemos verificar se a lista não contém d
fonte
Eu acredito que esta poderia ser uma maneira mais simples:
fonte
Aqui está outra ideia - este método permite especificar qualquer semana de trabalho e feriados.
A ideia aqui é encontrarmos o núcleo do intervalo de datas do primeiro primeiro dia útil da semana até o último dia de fim de semana da semana. Isso nos permite calcular as semanas inteiras facilmente ( sem iterar em todas as datas). Tudo o que precisamos fazer então é adicionar os dias úteis que caem antes do início e do final deste intervalo principal.
fonte
Já que não posso comentar. Há mais um problema com a solução aceita, em que os feriados bancários são subtraídos mesmo quando são no fim de semana. Vendo como outras entradas são verificadas, é justo que isso também seja.
Portanto, o foreach deve ser:
fonte
Aqui está uma abordagem se você estiver usando MVC. Também calculei feriados nacionais ou quaisquer dias festivos a serem excluídos, obtendo-o no calendário de feriados, que você precisará fazer um.
Leavemodel é uma lista aqui
fonte
Aqui está uma função auxiliar que escrevi para essa tarefa.
ele também retorna a contagem de fins de semana por meio do
out
parâmetro.se desejar, pode personalizar os dias de "fim de semana" em tempo de execução para países que usam dias de fim de semana diferentes ou incluir feriados através do
weekendDays[]
parâmetro opcional:fonte
Eu vim com a seguinte solução
fonte
Você só precisa iterar cada dia do intervalo de tempo e subtrair um dia do contador se for um sábado ou domingo.
fonte
Só estou postando isso porque, apesar de todas as respostas excelentes que foram dadas, nenhuma matemática fazia sentido para mim. Este é definitivamente um método KISS que deve funcionar e ser razoavelmente sustentável. Concedido, se você estiver calculando intervalos superiores a 2-3 meses, essa não será a maneira mais eficaz. Nós simplesmente determinamos se é um sábado ou domingo ou se a data é uma determinada data de feriado. Caso contrário, adicionamos um dia útil. Se for, então está tudo bem.
Tenho certeza de que isso poderia ser ainda mais simplificado com o LINQ, mas dessa forma é muito mais fácil de entender.
fonte
Outra abordagem para calcular dias úteis, não considerando feriados, mas levando em consideração a hora do dia retornando uma quantidade fracionária de dias:
Você pode mexer nele aqui: https://rextester.com/ASHRS53997
fonte
Esta é uma solução genérica.
startdayvalue é o número do dia da data de início.
fim de semana_1 é o número do dia de fim de semana.
número do dia - SEG - 1, TER - 2, ... SÁB - 6, DOM -7.
diferença é a diferença entre duas datas ..
Exemplo: Data de início: 4 de abril de 2013, Data de término: 14 de abril de 2013
Diferença: 10, valor do dia de início: 4, dia de fim de semana_1: 7 (se DOMINGO for um fim de semana para você.)
Isso lhe dará vários feriados.
Nº do dia útil = (Diferença + 1) - feriado1
fonte