calculando a diferença em meses entre duas datas

Em C # / .net TimeSpan tem TotalDays , TotalMinutes , etc, mas eu não consigo descobrir uma fórmula para a diferença total de meses. Variável dias por mês e anos bissextos continuam me jogando fora. Como posso obter TotalMonths ?

Edit Desculpe por não ser mais claro: Eu sei que não posso realmente obter isso do TimeSpan mas eu pensei que usar TotalDays e TotalMinutes seria um bom exemplo para expressar o que eu estava procurando … exceto eu estou tentando obter meses totais .

Exemplo: 25 de dezembro de 2009 – 6 de outubro de 2009 = 2 TotalMonths. 06 de outubro a 05 de novembro é igual a 0 meses. No dia 6 de novembro, 1 mês. No dia 6 de dezembro, 2 meses

Você não poderá obter isso de um TimeSpan , porque um “mês” é uma unidade de medida variável. Você terá que calcular você mesmo e terá que descobrir exatamente como deseja que ele funcione.

Por exemplo, datas como July 5, 2009 e August 4, 2009 devem ter uma diferença de um mês ou zero meses? Se você disser que deve render um, então e July 31, 2009 e August 1, 2009 ? Isso é um mês? É simplesmente a diferença dos valores do Month para as datas ou está mais relacionada a um período real de tempo? A lógica para determinar todas essas regras não é trivial, então você terá que determinar o seu próprio e implementar o algoritmo apropriado.

Se tudo que você quer é simplesmente uma diferença nos meses – desconsiderando completamente os valores de data – então você pode usar isto:

 public static int MonthDifference(this DateTime lValue, DateTime rValue) { return (lValue.Month - rValue.Month) + 12 * (lValue.Year - rValue.Year); } 

Observe que isso retorna uma diferença relativa, o que significa que, se rValue for maior que lValue , o valor de retorno será negativo. Se você quiser uma diferença absoluta, você pode usar isto:

 public static int MonthDifference(this DateTime lValue, DateTime rValue) { return Math.Abs((lValue.Month - rValue.Month) + 12 * (lValue.Year - rValue.Year)); } 

(Eu percebo que esta é uma pergunta antiga, mas …)

Isso é relativamente doloroso de se fazer no .NET puro. Eu recomendaria minha própria biblioteca Noda Time , que é especialmente projetada para coisas como esta:

 LocalDate start = new LocalDate(2009, 10, 6); LocalDate end = new LocalDate(2009, 12, 25); Period period = Period.Between(start, end); int months = period.Months; 

(Existem outras opções, por exemplo, se você deseja apenas uma contagem de meses até anos, você usaria Period period = Period.Between(start, end, PeriodUnits.Months); )

Talvez você não queira saber sobre as frações do mês; E esse código?

public static class DateTimeExtensions { public static int TotalMonths(this DateTime start, DateTime end) { return (start.Year * 12 + start.Month) - (end.Year * 12 + end.Month); } } // Console.WriteLine( // DateTime.Now.TotalMonths( // DateTime.Now.AddMonths(-1))); // prints "1"
public static class DateTimeExtensions { public static int TotalMonths(this DateTime start, DateTime end) { return (start.Year * 12 + start.Month) - (end.Year * 12 + end.Month); } } // Console.WriteLine( // DateTime.Now.TotalMonths( // DateTime.Now.AddMonths(-1))); // prints "1" 

Você terá que definir o que você entende por TotalMonths para começar.
Uma definição simples coloca um mês em 30,4 dias (365,25 / 12).

Além disso, qualquer definição, incluindo frações, parece inútil, e o valor inteiro mais comum (meses inteiros entre datas) também depende de regras de negócio não padronizadas.

Você precisa resolver isso sozinho fora dos dados. Como você lida com os dias do stub no final dependerá do que você quer usar.

Um método seria contar o mês e depois corrigir por dias no final. Algo como:

  DateTime start = new DateTime(2003, 12, 25); DateTime end = new DateTime(2009, 10, 6); int compMonth = (end.Month + end.Year * 12) - (start.Month + start.Year * 12); double daysInEndMonth = (end - end.AddMonths(1)).Days; double months = compMonth + (start.Day - end.Day) / daysInEndMonth; 

Eu escrevi um método de extensão muito simples em DateTime e DateTimeOffset para fazer isso. Eu queria que funcionasse exatamente como uma propriedade TotalMonths no TimeSpan iria funcionar: ou seja, retornar a contagem de meses completos entre duas datas, ignorando quaisquer meses parciais. Porque é baseado em DateTime.AddMonths() ele respeita diferentes comprimentos de mês e retorna o que um ser humano entenderia como um período de meses.

(Infelizmente você não pode implementá-lo como um método de extensão no TimeSpan porque isso não mantém o conhecimento das datas reais usadas, e por meses elas são importantes.)

O código e os testes estão disponíveis no GitHub . O código é muito simples:

 public static int GetTotalMonthsFrom(this DateTime dt1, DateTime dt2) { DateTime earlyDate = (dt1 > dt2) ? dt2.Date : dt1.Date; DateTime lateDate = (dt1 > dt2) ? dt1.Date : dt2.Date; // Start with 1 month's difference and keep incrementing // until we overshoot the late date int monthsDiff = 1; while (earlyDate.AddMonths(monthsDiff) <= lateDate) { monthsDiff++; } return monthsDiff - 1; } 

E passa todos esses casos de teste de unidade:

 // Simple comparison Assert.AreEqual(1, new DateTime(2014, 1, 1).GetTotalMonthsFrom(new DateTime(2014, 2, 1))); // Just under 1 month's diff Assert.AreEqual(0, new DateTime(2014, 1, 1).GetTotalMonthsFrom(new DateTime(2014, 1, 31))); // Just over 1 month's diff Assert.AreEqual(1, new DateTime(2014, 1, 1).GetTotalMonthsFrom(new DateTime(2014, 2, 2))); // 31 Jan to 28 Feb Assert.AreEqual(1, new DateTime(2014, 1, 31).GetTotalMonthsFrom(new DateTime(2014, 2, 28))); // Leap year 29 Feb to 29 Mar Assert.AreEqual(1, new DateTime(2012, 2, 29).GetTotalMonthsFrom(new DateTime(2012, 3, 29))); // Whole year minus a day Assert.AreEqual(11, new DateTime(2012, 1, 1).GetTotalMonthsFrom(new DateTime(2012, 12, 31))); // Whole year Assert.AreEqual(12, new DateTime(2012, 1, 1).GetTotalMonthsFrom(new DateTime(2013, 1, 1))); // 29 Feb (leap) to 28 Feb (non-leap) Assert.AreEqual(12, new DateTime(2012, 2, 29).GetTotalMonthsFrom(new DateTime(2013, 2, 28))); // 100 years Assert.AreEqual(1200, new DateTime(2000, 1, 1).GetTotalMonthsFrom(new DateTime(2100, 1, 1))); // Same date Assert.AreEqual(0, new DateTime(2014, 8, 5).GetTotalMonthsFrom(new DateTime(2014, 8, 5))); // Past date Assert.AreEqual(6, new DateTime(2012, 1, 1).GetTotalMonthsFrom(new DateTime(2011, 6, 10))); 

Eu faria assim:

 static int TotelMonthDifference(this DateTime dtThis, DateTime dtOther) { int intReturn = 0; dtThis = dtThis.Date.AddDays(-(dtThis.Day-1)); dtOther = dtOther.Date.AddDays(-(dtOther.Day-1)); while (dtOther.Date > dtThis.Date) { intReturn++; dtThis = dtThis.AddMonths(1); } return intReturn; } 
 case TipoIntervalo.Mes: retorno = inicio.AddMonths(-fim.Month).Month.ToString(); break; case TipoIntervalo.Ano: retorno = (inicio.Year - fim.Year).ToString(); break; 

Não há muitas respostas claras sobre isso, porque você está sempre assumindo as coisas.

Essa solução calcula entre duas datas os meses entre supor que você deseja salvar o dia do mês para comparação (o que significa que o dia do mês é considerado no cálculo)

Exemplo, se você tiver uma data de 30 de janeiro de 2012, 29 de fevereiro de 2012 não será um mês, mas 01 de março de 2013 será.

Ele foi testado muito bem, provavelmente irá limpá-lo mais tarde, conforme o usamos, e leva em duas datas, em vez de um período de tempo, que é provavelmente melhor. Espero que isso ajude alguém.

 private static int TotalMonthDifference(DateTime dtThis, DateTime dtOther) { int intReturn = 0; bool sameMonth = false; if (dtOther.Date < dtThis.Date) //used for an error catch in program, returns -1 intReturn--; int dayOfMonth = dtThis.Day; //captures the month of day for when it adds a month and doesn't have that many days int daysinMonth = 0; //used to caputre how many days are in the month while (dtOther.Date > dtThis.Date) //while Other date is still under the other { dtThis = dtThis.AddMonths(1); //as we loop, we just keep adding a month for testing daysinMonth = DateTime.DaysInMonth(dtThis.Year, dtThis.Month); //grabs the days in the current tested month if (dtThis.Day != dayOfMonth) //Example 30 Jan 2013 will go to 28 Feb when a month is added, so when it goes to march it will be 28th and not 30th { if (daysinMonth < dayOfMonth) // uses day in month max if can't set back to day of month dtThis.AddDays(daysinMonth - dtThis.Day); else dtThis.AddDays(dayOfMonth - dtThis.Day); } if (((dtOther.Year == dtThis.Year) && (dtOther.Month == dtThis.Month))) //If the loop puts it in the same month and year { if (dtOther.Day >= dayOfMonth) //check to see if it is the same day or later to add one to month intReturn++; sameMonth = true; //sets this to cancel out of the normal counting of month } if ((!sameMonth)&&(dtOther.Date > dtThis.Date))//so as long as it didn't reach the same month (or if i started in the same month, one month ahead, add a month) intReturn++; } return intReturn; //return month } 

Pergunta antiga que conheço, mas que pode ajudar alguém. Eu usei @Adam aceito resposta acima, mas, em seguida, verifiquei se a diferença é de 1 ou -1, em seguida, verifique se é uma diferença de mês do calendar completo. Então 21/07/55 e 20/08/55 não seria um mês completo, mas 21/07/55 e 21/07/55 seria.

 ///  /// Amended date of birth cannot be greater than or equal to one month either side of original date of birth. ///  /// Date of birth user could have amended. /// Original date of birth to compare against. ///  public JsonResult ValidateDateOfBirth(string dateOfBirth, string originalDateOfBirth) { DateTime dob, originalDob; bool isValid = false; if (DateTime.TryParse(dateOfBirth, out dob) && DateTime.TryParse(originalDateOfBirth, out originalDob)) { int diff = ((dob.Month - originalDob.Month) + 12 * (dob.Year - originalDob.Year)); switch (diff) { case 0: // We're on the same month, so ok. isValid = true; break; case -1: // The month is the previous month, so check if the date makes it a calendar month out. isValid = (dob.Day > originalDob.Day); break; case 1: // The month is the next month, so check if the date makes it a calendar month out. isValid = (dob.Day < originalDob.Day); break; default: // Either zero or greater than 1 month difference, so not ok. isValid = false; break; } if (!isValid) return Json("Date of Birth cannot be greater than one month either side of the date we hold.", JsonRequestBehavior.AllowGet); } else { return Json("Date of Birth is invalid.", JsonRequestBehavior.AllowGet); } return Json(true, JsonRequestBehavior.AllowGet); } 

O problema com os meses é que não é realmente uma medida simples – eles não são de tamanho constante. Você precisaria definir suas regras para o que você deseja include e trabalhar a partir daí. Por exemplo, 1 de janeiro a 1 de fevereiro – você poderia argumentar que 2 meses estão envolvidos, ou você poderia dizer que é um mês. Então, o que dizer de “1 de janeiro de 20:00” a “1 de fevereiro de 00:00” – isso não é exatamente um mês completo. Isso é 0? 1? que tal o contrário (1 de janeiro de 00:00 a 1 de fevereiro de 20:00) … 1? 2?

Primeiro defina as regras, então você vai ter que codificar, temo …

Se você quer ter um resultado entre 28th Feb e 1st March :

 DateTime date1, date2; int monthSpan = (date2.Year - date1.Year) * 12 + date2.Month - date1.Month 

Esta biblioteca calcula a diferença de meses, considerando todas as partes do DateTime:

 // ---------------------------------------------------------------------- public void DateDiffSample() { DateTime date1 = new DateTime( 2009, 11, 8, 7, 13, 59 ); Console.WriteLine( "Date1: {0}", date1 ); // > Date1: 08.11.2009 07:13:59 DateTime date2 = new DateTime( 2011, 3, 20, 19, 55, 28 ); Console.WriteLine( "Date2: {0}", date2 ); // > Date2: 20.03.2011 19:55:28 DateDiff dateDiff = new DateDiff( date1, date2 ); // differences Console.WriteLine( "DateDiff.Years: {0}", dateDiff.Years ); // > DateDiff.Years: 1 Console.WriteLine( "DateDiff.Quarters: {0}", dateDiff.Quarters ); // > DateDiff.Quarters: 5 Console.WriteLine( "DateDiff.Months: {0}", dateDiff.Months ); // > DateDiff.Months: 16 Console.WriteLine( "DateDiff.Weeks: {0}", dateDiff.Weeks ); // > DateDiff.Weeks: 70 Console.WriteLine( "DateDiff.Days: {0}", dateDiff.Days ); // > DateDiff.Days: 497 Console.WriteLine( "DateDiff.Weekdays: {0}", dateDiff.Weekdays ); // > DateDiff.Weekdays: 71 Console.WriteLine( "DateDiff.Hours: {0}", dateDiff.Hours ); // > DateDiff.Hours: 11940 Console.WriteLine( "DateDiff.Minutes: {0}", dateDiff.Minutes ); // > DateDiff.Minutes: 716441 Console.WriteLine( "DateDiff.Seconds: {0}", dateDiff.Seconds ); // > DateDiff.Seconds: 42986489 // elapsed Console.WriteLine( "DateDiff.ElapsedYears: {0}", dateDiff.ElapsedYears ); // > DateDiff.ElapsedYears: 1 Console.WriteLine( "DateDiff.ElapsedMonths: {0}", dateDiff.ElapsedMonths ); // > DateDiff.ElapsedMonths: 4 Console.WriteLine( "DateDiff.ElapsedDays: {0}", dateDiff.ElapsedDays ); // > DateDiff.ElapsedDays: 12 Console.WriteLine( "DateDiff.ElapsedHours: {0}", dateDiff.ElapsedHours ); // > DateDiff.ElapsedHours: 12 Console.WriteLine( "DateDiff.ElapsedMinutes: {0}", dateDiff.ElapsedMinutes ); // > DateDiff.ElapsedMinutes: 41 Console.WriteLine( "DateDiff.ElapsedSeconds: {0}", dateDiff.ElapsedSeconds ); // > DateDiff.ElapsedSeconds: 29 } // DateDiffSample 

Abaixo está a maneira mais precisa que você pode fazer, já que a definição de “1 mês” muda dependendo de qual mês é, e nenhuma das outras respostas leva isso em conta! Se você quiser mais informações sobre o problema que não está embutido no framework, você pode ler este post: Um Objeto de Tempo Real com .Anos & .Meses (no entanto, ler esse post não é necessário para entender e usar a function abaixo, ele funciona 100%, sem as imprecisões inerentes à aproximação que os outros adoram usar – e sinta-se à vontade para replace a function .ReverseIt pela function Reverter incorporada que você pode ter em sua estrutura (é apenas aqui para ser completa).

Por favor, note que você pode obter qualquer número de datas / horários de precisão, segundos e minutos, ou segundos, minutos e dias, em qualquer lugar até anos (o que conteria 6 partes / segmentos). Se você especificar os dois primeiros e tiver mais de um ano, ele retornará “1 ano e 3 meses atrás” e não retornará o restante porque você solicitou dois segmentos. se tiver apenas algumas horas, só retornará “2 horas e 1 minuto atrás”. É claro que as mesmas regras se aplicam se você especificar 1, 2, 3, 4, 5 ou 6 segmets (o máximo é 6 porque segundos, minutos, horas, dias, meses, anos fazem apenas 6 tipos). Ele também corrigirá problemas gramaticais como “minutos” versus “minuto”, dependendo de se é de 1 minuto ou mais, o mesmo para todos os tipos, e a “string” gerada será sempre gramaticalmente correta.

Aqui estão alguns exemplos para uso: bAllowSegments identifica quantos segmentos para mostrar … ou seja: se 3, em seguida, string de retorno seria (como um exemplo) … "3 years, 2 months and 13 days" (não includeá horas, minutos e segundos, quando as três principais categorias de tempo forem retornadas), se, no entanto, a data for uma data mais recente, como algo há alguns dias, especificar os mesmos segmentos (3) retornará "4 days, 1 hour and 13 minutes ago" vez disso, então leva tudo em conta!

se bAllowSegments for 2, retornará "3 years and 2 months" e se 6 (valor máximo) retornará "3 years, 2 months, 13 days, 13 hours, 29 minutes and 9 seconds" , mas, lembre-se de que NEVER RETURN algo como "0 years, 0 months, 0 days, 3 hours, 2 minutes and 13 seconds ago" , pois entende que não há dados de data nos 3 principais segmentos e os ignora, mesmo se você especificar 6 segmentos, não se preocupe :). É claro que, se houver um segmento com 0, ele levará isso em conta ao formar a string e será exibido como "3 days and 4 seconds ago" e ignorando a parte “0 horas”! Aproveite e por favor, comente se você gosta.

  Public Function RealTimeUntilNow(ByVal dt As DateTime, Optional ByVal bAllowSegments As Byte = 2) As String ' bAllowSegments identifies how many segments to show... ie: if 3, then return string would be (as an example)... ' "3 years, 2 months and 13 days" the top 3 time categories are returned, if bAllowSegments is 2 it would return ' "3 years and 2 months" and if 6 (maximum value) would return "3 years, 2 months, 13 days, 13 hours, 29 minutes and 9 seconds" Dim rYears, rMonths, rDays, rHours, rMinutes, rSeconds As Int16 Dim dtNow = DateTime.Now Dim daysInBaseMonth = Date.DaysInMonth(dt.Year, dt.Month) rYears = dtNow.Year - dt.Year rMonths = dtNow.Month - dt.Month If rMonths < 0 Then rMonths += 12 : rYears -= 1 ' add 1 year to months, and remove 1 year from years. rDays = dtNow.Day - dt.Day If rDays < 0 Then rDays += daysInBaseMonth : rMonths -= 1 rHours = dtNow.Hour - dt.Hour If rHours < 0 Then rHours += 24 : rDays -= 1 rMinutes = dtNow.Minute - dt.Minute If rMinutes < 0 Then rMinutes += 60 : rHours -= 1 rSeconds = dtNow.Second - dt.Second If rSeconds < 0 Then rSeconds += 60 : rMinutes -= 1 ' this is the display functionality Dim sb As StringBuilder = New StringBuilder() Dim iSegmentsAdded As Int16 = 0 If rYears > 0 Then sb.Append(rYears) : sb.Append(" year" & If(rYears <> 1, "s", "") & ", ") : iSegmentsAdded += 1 If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn If rMonths > 0 Then sb.AppendFormat(rMonths) : sb.Append(" month" & If(rMonths <> 1, "s", "") & ", ") : iSegmentsAdded += 1 If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn If rDays > 0 Then sb.Append(rDays) : sb.Append(" day" & If(rDays <> 1, "s", "") & ", ") : iSegmentsAdded += 1 If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn If rHours > 0 Then sb.Append(rHours) : sb.Append(" hour" & If(rHours <> 1, "s", "") & ", ") : iSegmentsAdded += 1 If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn If rMinutes > 0 Then sb.Append(rMinutes) : sb.Append(" minute" & If(rMinutes <> 1, "s", "") & ", ") : iSegmentsAdded += 1 If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn If rSeconds > 0 Then sb.Append(rSeconds) : sb.Append(" second" & If(rSeconds <> 1, "s", "") & "") : iSegmentsAdded += 1 parseAndReturn: ' if the string is entirely empty, that means it was just posted so its less than a second ago, and an empty string getting passed will cause an error ' so we construct our own meaningful string which will still fit into the "Posted * ago " syntax... If sb.ToString = "" Then sb.Append("less than 1 second") Return ReplaceLast(sb.ToString.TrimEnd(" ", ",").ToString, ",", " and") End Function 

Obviamente, você precisará de uma function “ReplaceLast”, que recebe uma string de origem, e um argumento especificando o que precisa ser substituído, e outro argumento especificando com o que você deseja substituí-lo, e somente substitui a última ocorrência dessa string. … eu incluí o meu se você não tem um ou não quer implementá-lo, então aqui está, ele vai funcionar “como está” sem nenhuma modificação necessária. Eu sei que a function reverseit não é mais necessária (existe em .net), mas o ReplaceLast e o ReverseIt func são transferidos dos dias do pre-.net, então por favor, desculpe como pode parecer datado (ainda funciona 100% tho, usando por mais de dez anos, posso garantir que eles estão livres de bugs) … :). Felicidades.

  _ Public Function ReplaceLast(ByVal sReplacable As String, ByVal sReplaceWhat As String, ByVal sReplaceWith As String) As String ' let empty string arguments run, incase we dont know if we are sending and empty string or not. sReplacable = sReplacable.ReverseIt sReplacable = Replace(sReplacable, sReplaceWhat.ReverseIt, sReplaceWith.ReverseIt, , 1) ' only does first item on reversed version! Return sReplacable.ReverseIt.ToString End Function  _ Public Function ReverseIt(ByVal strS As String, Optional ByVal n As Integer = -1) As String Dim strTempX As String = "", intI As Integer If n > strS.Length Or n = -1 Then n = strS.Length For intI = n To 1 Step -1 strTempX = strTempX + Mid(strS, intI, 1) Next intI ReverseIt = strTempX + Right(strS, Len(strS) - n) End Function 

A resposta aceita funciona perfeitamente quando você quer meses completos.

Eu precisava de meses parciais. Esta é a solução que eu criei para meses parciais:

  ///  /// Calculate the difference in months. /// This will round up to count partial months. ///  ///  ///  ///  public static int MonthDifference(DateTime lValue, DateTime rValue) { var yearDifferenceInMonths = (lValue.Year - rValue.Year) * 12; var monthDifference = lValue.Month - rValue.Month; return yearDifferenceInMonths + monthDifference + (lValue.Day > rValue.Day ? 1 : 0); // If end day is greater than start day, add 1 to round up the partial month } 

Eu também precisava de uma diferença de um ano com a mesma necessidade de anos parciais. Aqui está a solução que eu criei:

  ///  /// Calculate the differences in years. /// This will round up to catch partial months. ///  ///  ///  ///  public static int YearDifference(DateTime lValue, DateTime rValue) { return lValue.Year - rValue.Year + (lValue.Month > rValue.Month // Partial month, same year ? 1 : lValue.Day > rValue.Day // Partial month, same year and month ? 1 : 0); } 

Se você quer o número exato, você não pode apenas do Timespan, desde que você precisa saber em quais meses você está lidando, e se você está lidando com um ano bissexto, como você disse.

Quer ir para um número aproximado, ou fazer alguma inquietação com os DateTimes originais

http://www.astro.uu.nl/~strous/AA/en/reken/juliaansedag.html

Se você conseguir converter o horário de uma data gregoriana em um número de dia juliano , basta criar um operador para fazer comparações com o número do dia zuliano, que pode ser do tipo duplo para obter meses, dias, segundos etc. Confira o acima link para um algoritmo para converter de gregoriano para juliano.

Não existe uma maneira de fazer isso com precisão em idiomático-c #. Existem algumas soluções alternativas, como esse exemplo de CodeProject que as pessoas codificaram.

Se você está lidando com meses e anos, precisa de algo que saiba quantos dias cada mês tem e quais são anos bissextos.

Digite o calendar gregoriano (e outras implementações de calendar específicas da cultura).

Embora o Calendário não forneça methods para calcular diretamente a diferença entre dois pontos no tempo, ele possui methods como

 DateTime AddWeeks(DateTime time, int weeks) DateTime AddMonths(DateTime time, int months) DateTime AddYears(DateTime time, int years) 
 DateTime start = new DateTime(2003, 12, 25); DateTime end = new DateTime(2009, 10, 6); int compMonth = (end.Month + end.Year * 12) - (start.Month + start.Year * 12); double daysInEndMonth = (end - end.AddMonths(1)).Days; double months = compMonth + (start.Day - end.Day) / daysInEndMonth; 

O método retorna uma lista que contém o elemento 3 primeiro é o ano, o segundo é o mês e o elemento final é o dia:

 public static List GetDurationInEnglish(DateTime from, DateTime to) { try { if (from > to) return null; var fY = from.Year; var fM = from.Month; var fD = DateTime.DaysInMonth(fY, fM); var tY = to.Year; var tM = to.Month; var tD = DateTime.DaysInMonth(tY, tM); int dY = 0; int dM = 0; int dD = 0; if (fD > tD) { tM--; if (tM <= 0) { tY--; tM = 12; tD += DateTime.DaysInMonth(tY, tM); } else { tD += DateTime.DaysInMonth(tY, tM); } } dD = tD - fD; if (fM > tM) { tY--; tM += 12; } dM = tM - fM; dY = tY - fY; return new List() { dY, dM, dD }; } catch (Exception exception) { //todo: log exception with parameters in db return null; } } 

Aqui está minha contribuição para obter a diferença em meses que achei ser preciso:

 namespace System { public static class DateTimeExtensions { public static Int32 DiffMonths( this DateTime start, DateTime end ) { Int32 months = 0; DateTime tmp = start; while ( tmp < end ) { months++; tmp = tmp.AddMonths( 1 ); } return months; } } } 

Uso:

 Int32 months = DateTime.Now.DiffMonths( DateTime.Now.AddYears( 5 ) ); 

Você pode criar outro método chamado DiffYears e aplicar exatamente a mesma lógica acima e AddYears em vez de AddMonths no loop while.

Muito tarde para o jogo, mas imagino que isso possa ser útil para alguém. A maioria das pessoas tende a medir mês a mês por data, excluindo o fato de que os meses vêm em diferentes variações. Usando esse quadro de pensamento eu criei um forro que compara as datas para nós. Usando o processo a seguir.

  1. Qualquer número de anos acima de 1 na comparação do ano será multiplicado por 12, não há nenhum caso em que isso possa ser igual a menos de 1 ano completo.
  2. Se o ano final for maior, precisamos avaliar se o dia atual é maior ou igual ao dia anterior 2A. If the end day is greater or equal we take the current month and then add 12 months subtract the month of the start month 2B. If the end day is less than the start day we perform the the same as above except we add 1 to the start month before subtracting
  3. If the end year is not greater we perform the same as 2A/2B, but without adding the 12 months because we do not need to evaluate around the year.

      DateTime date = new DateTime(2003, 11, 25); DateTime today = new DateTime(2004, 12, 26); var time = (today.Year - date.Year > 1 ? (today.Year - date.Year - 1) * 12 : 0) + (today.Year > date.Year ? (today.Day >= date.Day ? today.Month + 12 - date.Month : today.Month + 12 - (date.Month + 1)) : (today.Day >= date.Day ? today.Month - date.Month : today.Month - (date.Month + 1))); 
  DateTime dtEnd = DateTime.Now.AddDays(59); DateTime dtBegin = DateTime.Now; var diff = (dtEnd-dtBegin).TotalDays; lblDateDiff.Text = Math.Floor(diff/30).ToString() + " month('s) and " + (diff%30).ToString() + " days"; 

returns 1 month(‘s) and 29 days. Of course it is dependent on months being 30 days.. but it is pretty close to the correct value for days and months between dates.