Pergunta

Em C#/.NET TimeSpan tem TotalDays, TotalMinutes, etc.mas não consigo descobrir uma fórmula para a diferença total de meses.Dias variáveis ​​por mês e anos bissextos continuam me confundindo.Como posso obter Total de meses?

Editar Desculpe por não ser mais claro:Eu sei que não consigo tirar isso de TimeSpan mas pensei em usar TotalDays e TotalMinutes seria um bom exemplo para expressar o que procurava ...exceto que estou tentando obter o total de meses.

Exemplo:25 de dezembro de 2009 - 6 de outubro de 2009 = 2 meses totais.6 de outubro a 5 de novembro equivale a 0 meses.Em 6 de novembro, 1 mês.Em 6 de dezembro, 2 meses

Foi útil?

Solução

Você não conseguirá isso de um TimeSpan, porque um "mês" é uma unidade de medida variável.Você terá que calculá-lo sozinho e descobrir como exatamente deseja que funcione.

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

Se tudo o que você deseja é simplesmente uma diferença nos meses - desconsiderando completamente os valores das datas - 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 é melhor que lValue, então 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));
}

Outras dicas

(Sei que esta é uma pergunta antiga, mas...)

Isso é relativamente doloroso de fazer em .NET puro.Eu recomendaria o meu Hora de Noda biblioteca, 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 ex.se você quiser apenas uma contagem de meses, mesmo entre anos, você usaria Period period = Period.Between(start, end, PeriodUnits.Months);)

Talvez você não queira saber sobre frações mensais;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"


Você terá que definir o que 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 que inclua frações parece inútil, e o valor inteiro mais comum (meses inteiros entre datas) também depende de regras de negócios não padronizadas.

Você precisa resolver isso sozinho com base nas datas.Como você lida com os dias de stub no final dependerá da finalidade para a qual você deseja usá-lo.

Um método seria contar o mês e depois corrigir os 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 um TotalMonths propriedade em TimeSpan podia funcionar:ou sejaretorne a contagem de meses completos entre duas datas, ignorando quaisquer meses parciais.Porque é baseado em DateTime.AddMonths() respeita diferentes durações de meses 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 retém o conhecimento das datas reais usadas e, durante meses, elas são importantes.)

O código e os testes são ambos disponível 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 em 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;
}

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

Esta solução calcula entre duas datas os meses entre assumindo 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 1º de março de 2013 será.

Ele foi testado exaustivamente, provavelmente será limpo mais tarde, à medida que o usarmos, e leva duas datas em vez de um intervalo de tempo, o que provavelmente é melhor.Espero que isso ajude mais 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 eu sei, mas pode ajudar alguém.Usei a resposta aceita do @Adam acima, mas verifiquei se a diferença é 1 ou -1 e verifique se é a diferença de um mês completo.Portanto, 21/07/55 e 20/08/55 não seriam um mês completo, mas 21/07/55 e 21/07/55 seriam.

/// <summary>
/// Amended date of birth cannot be greater than or equal to one month either side of original date of birth.
/// </summary>
/// <param name="dateOfBirth">Date of birth user could have amended.</param>
/// <param name="originalDateOfBirth">Original date of birth to compare against.</param>
/// <returns></returns>
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);
}

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

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

    /// <summary>
    /// Calculate the difference in months.
    /// This will round up to count partial months.
    /// </summary>
    /// <param name="lValue"></param>
    /// <param name="rValue"></param>
    /// <returns></returns>
    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
    }

Também precisei de um ano de diferença com a mesma necessidade de anos parciais.Aqui está a solução que encontrei:

    /// <summary>
    /// Calculate the differences in years.
    /// This will round up to catch partial months.
    /// </summary>
    /// <param name="lValue"></param>
    /// <param name="rValue"></param>
    /// <returns></returns>
    public static int YearDifference(DateTime lValue, DateTime rValue)
    {
        return lValue.Year - rValue.Year +
               (lValue.Month > rValue.Month // Partial month, same year
                   ? 1
                   : ((lValue.Month = rValue.Month) 
                     && (lValue.Day > rValue.Day)) // Partial month, same year and month
                   ? 1 : 0);
    }
case IntervalType.Month:
    returnValue = start.AddMonths(-end.Month).Month.ToString();
    break;
case IntervalType.Year:
    returnValue = (start.Year - end.Year).ToString();
    break;

O problema com os meses é que não é realmente uma medida simples - eles não têm tamanho constante.Você precisaria definir suas regras para o que deseja incluir e trabalhar a partir daí.Por exemplo, de 1º de janeiro a 1º de fevereiro - você pode argumentar que há 2 meses envolvidos ou pode dizer que é um mês.Então, que tal de "1º de janeiro às 20:00" a "1º de fevereiro às 00:00" - isso não é um mês inteiro inteiro.Isso é 0?1?e o contrário (1º de janeiro às 00:00 a 1º de fevereiro às 20:00) ...1?2?

Primeiro defina as regras, depois você mesmo terá que codificá-las, infelizmente...

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

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

Esse 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 de fazer isso, já que a definição de "1 mês" muda dependendo do mês, e nenhuma das outras respostas leva isso em consideração!Se você quiser mais informações sobre o problema que não está embutido na estrutura, você pode ler esta postagem: Um objeto de intervalo de tempo real com .Years e .Months (no entanto, não é necessário ler esse post para entender e usar a função abaixo, ela funciona 100%, sem as imprecisões inerentes à aproximação que outros adoram usar - e fique à vontade para substituir a função .ReverseIt pela função integrada . Função reversa que você pode ter em sua estrutura (está aqui apenas para completar).

Observe que você pode obter qualquer número de precisão de datas/horas, segundos e minutos, ou segundos, minutos e dias, em qualquer lugar até anos (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, retornará apenas "2 horas e 1 minuto atrás".Obviamente, as mesmas regras se aplicam se você especificar 1, 2, 3, 4, 5 ou 6 segmentos (máximo de 6 porque segundos, minutos, horas, dias, meses, anos constituem apenas 6 tipos).Ele também corrigirá problemas gramaticais como "minutos" versus "minutos", dependendo se for 1 minuto ou mais, o mesmo para todos os tipos, e a "string" gerada sempre estará gramaticalmente correta.

Aqui estão alguns exemplos de uso:bAllowSegments identifica quantos segmentos mostrar...ou seja:se 3, então a string de retorno seria (como exemplo)... "3 years, 2 months and 13 days" (não incluirá horas, minutos e segundos, pois as 3 principais categorias de tempo são retornadas), se, no entanto, a data for uma data mais recente, como algo de alguns dias atrás, a especificação dos mesmos segmentos (3) retornará "4 days, 1 hour and 13 minutes ago" em vez disso, leva tudo em consideração!

se bAllowSegments for 2, ele retornaria "3 years and 2 months" e se 6 (valor máximo) retornasse "3 years, 2 months, 13 days, 13 hours, 29 minutes and 9 seconds", mas lembre-se de que isso acontecerá NEVER RETURN algo assim "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 segmentos principais e os ignora, mesmo se você especificar 6 segmentos, então não se preocupe :).Claro, se houver um segmento com 0 nele, ele levará isso em consideração ao formar a string e será exibido como "3 days and 4 seconds ago" e ignorando a parte "0 horas"!Aproveite e comente se quiser.

 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

Claro, você precisará de uma função "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 substitui apenas a última ocorrência dessa string ...incluí o meu caso você não tenha um ou não queira implementá-lo, então aqui está, ele funcionará "como está", sem nenhuma modificação necessária.Eu sei que a função reverseit não é mais necessária (existe em .net), mas as funções ReplaceLast e ReverseIt são herdadas dos dias anteriores ao .net, então, desculpe o quão desatualizado pode parecer (ainda funciona 100%, estou usando em há mais de dez anos, posso garantir que estão livres de bugs)...:).saúde.

<Extension()> _ 
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 

<Extension()> _ 
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 

Se você quiser o número exato, não pode usar apenas o Timespan, pois precisa saber com quais meses está lidando e se está lidando com um ano bissexto, como você disse.

Escolha um número aproximado ou mexa nos DateTimes originais

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

Se você conseguir converter a hora de uma data gregoriana para Número do dia juliano, você pode simplesmente criar um operador para fazer comparações do número do dia zulian, que pode ser do tipo double para obter meses, dias, segundos, etc.Confira no link acima um algoritmo para conversão de Gregoriano para Juliano.

Não existe uma maneira integrada de fazer isso com precisão em c# idiomático.Existem algumas soluções alternativas, como este 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 anos são bissextos.

Introduzir o Calendário gregoriano (e outras culturas específicas Calendário implementações).

Embora o Agenda não forneça métodos para calcular diretamente a diferença entre dois momentos no tempo, ele possui métodos 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 3 elementos: o primeiro é o ano, o segundo é o mês e o elemento final é o dia:

public static List<int> 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<int>() { 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 considero precisa:

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 apresentam variações diferentes.Usando esse pensamento, criei um liner que compara as datas para nós.Usando o seguinte processo.

  1. Qualquer número de anos superior a 1 na comparação de anos será multiplicado por 12, não há 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.Se o dia final for maior ou igual, pegamos o mês atual e adicionamos 12 meses, subtraímos o mês do mês inicial; 2º.Se o dia final for menor que o dia inicial, faremos o mesmo que acima, exceto que adicionamos 1 ao mês inicial antes de subtrair
  3. Se o ano final não for maior realizamos o mesmo que 2A/2B, mas sem somar os 12 meses porque não precisamos avaliar em torno do ano.

        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)));
    

Calcule o número de meses entre 2 datas:

$date1 = '2017-01-20';
$date2 = '2019-01-20';

$ts1 = strtotime($date1);
$ts2 = strtotime($date2);

$year1 = date('Y', $ts1);
$year2 = date('Y', $ts2);

$month1 = date('m', $ts1);
$month2 = date('m', $ts2);

echo $joining_months = (($year2 - $year1) * 12) + ($month2 - $month1);
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top