Diferença em meses entre duas datas

334

Como calcular a diferença em meses entre duas datas em c #?

Existe um equivalente do DateDiff()método VB em C #. Preciso encontrar diferença nos meses entre duas datas que têm anos de diferença. A documentação diz que eu posso usar TimeSpancomo:

TimeSpan ts = date1 - date2;

mas isso me fornece dados em dias. Não quero dividir esse número por 30, porque nem todos os meses são 30 dias e, como os dois valores dos operandos são bem separados um do outro, receio que dividir por 30 possa me dar um valor errado.

Alguma sugestão?

Rauf
fonte
27
Defina "diferença em meses", qual é a diferença em meses entre "1 de maio de 1010" e "16 de junho de 2010"? 1,5, 1 ou algo mais?
Cheng Chen
7
Ou, para enfatizar ainda mais esse ponto, qual é a diferença nos meses entre 31 de dezembro de 2010 e 1 de janeiro de 2011? Dependendo do dia, isso pode ser uma diferença de apenas 1 segundo; você contaria isso como diferença de um mês?
stakx - não contribuindo mais com
Aqui está o código simples e curto, caso você ainda não tenha conseguido a resposta, consulte este POST stackoverflow.com/questions/8820603/…
wirol
11
Danny: 1 mês e 15 dias. stakx: 0 meses e 1 dia. O ponto é obter o componente do mês . Isso me parece bastante óbvio e é uma boa pergunta.
Kirk Woll

Respostas:

462

Supondo que o dia do mês seja irrelevante (ou seja, a diferença entre 2011.1.1 e 31.12.2010 é 1), com data1> data2 dando um valor positivo e data2> data1 um valor negativo

((date1.Year - date2.Year) * 12) + date1.Month - date2.Month

Ou, supondo que você queira um número aproximado de 'meses médios' entre as duas datas, o seguinte deve funcionar para todas as diferenças de datas, exceto as muito grandes.

date1.Subtract(date2).Days / (365.25 / 12)

Observe que, se você usar a última solução, seus testes de unidade deverão indicar o período mais amplo com o qual seu aplicativo foi projetado para trabalhar e validar os resultados do cálculo de acordo.


Atualização (com agradecimentos a Gary )

Se você usar o método 'meses médios', um número um pouco mais preciso a ser usado para o 'número médio de dias por ano' é 365,2425 .

Adam Ralph
fonte
3
@Kurru - 365/12 é apenas uma medida aproximada da duração média de um mês em dias. É uma medida imprecisa. Para períodos pequenos, essa imprecisão pode ser tolerada, mas para períodos muito grandes, essa imprecisão pode se tornar significativa.
Adam Ralph
21
Eu acho que é necessário considerar o componente Day. Algo parecido com isto (date1.Year - date2.Year) * 12 + date1.Month - date2.Month + (date1.Day >= date2.Day ? 0 : -1)
DrunkCoder
2
@DrunkCoder depende dos requisitos de um determinado sistema. Em alguns casos, sua solução pode realmente ser a melhor escolha. Por exemplo, é importante considerar o que acontece quando duas datas abrangem um mês de 31 dias, um mês de 30 dias, um fevereiro de 28 dias ou um fevereiro de 29 dias. Se os resultados da sua fórmula fornecerem o que o sistema exige, é claramente a escolha certa. Caso contrário, é necessário algo mais.
Adam Ralph
6
Para dizer o que Adam disse, passei anos escrevendo código para os Acturaries. Alguns cálculos foram divididos por número de dias, arredondados por 30 para obter valores mensais . Às vezes, contando os meses assumidos a cada data começa no primeiro mês, conte meses inteiros de acordo . Não existe um método melhor para calcular datas. A menos que você seja o cliente para o qual está escrevendo o código, empurre-o de volta para a cadeia e esclareça-o, possivelmente pelo contador de clientes.
Preocupação binária
11
365.2425 é o número de dias um pouco mais preciso em um calendário gregoriano, se é isso que você está usando. No entanto, por DateTime.MaxValue (1 de janeiro de 10000), essa diferença é de apenas 59 dias. Além disso, a definição de ano pode ser muito diferente, dependendo da sua perspectiva pt.wikipedia.org/wiki/Year .
Gary
206

Aqui está uma solução abrangente para retornar a DateTimeSpan, semelhante a a TimeSpan, exceto que inclui todos os componentes de data, além dos componentes de hora.

Uso:

void Main()
{
    DateTime compareTo = DateTime.Parse("8/13/2010 8:33:21 AM");
    DateTime now = DateTime.Parse("2/9/2012 10:10:11 AM");
    var dateSpan = DateTimeSpan.CompareDates(compareTo, now);
    Console.WriteLine("Years: " + dateSpan.Years);
    Console.WriteLine("Months: " + dateSpan.Months);
    Console.WriteLine("Days: " + dateSpan.Days);
    Console.WriteLine("Hours: " + dateSpan.Hours);
    Console.WriteLine("Minutes: " + dateSpan.Minutes);
    Console.WriteLine("Seconds: " + dateSpan.Seconds);
    Console.WriteLine("Milliseconds: " + dateSpan.Milliseconds);
}

Saídas:

Anos: 1
Meses: 5
Dias: 27
Horas: 1
Minutos: 36
Segundos: 50
Milissegundos: 0

Por conveniência, agrupei a lógica na DateTimeSpanestrutura, mas você pode mover o método CompareDatessempre que achar melhor. Observe também que não importa que data venha antes da outra.

public struct DateTimeSpan
{
    public int Years { get; }
    public int Months { get; }
    public int Days { get; }
    public int Hours { get; }
    public int Minutes { get; }
    public int Seconds { get; }
    public int Milliseconds { get; }

    public DateTimeSpan(int years, int months, int days, int hours, int minutes, int seconds, int milliseconds)
    {
        Years = years;
        Months = months;
        Days = days;
        Hours = hours;
        Minutes = minutes;
        Seconds = seconds;
        Milliseconds = milliseconds;
    }

    enum Phase { Years, Months, Days, Done }

    public static DateTimeSpan CompareDates(DateTime date1, DateTime date2)
    {
        if (date2 < date1)
        {
            var sub = date1;
            date1 = date2;
            date2 = sub;
        }

        DateTime current = date1;
        int years = 0;
        int months = 0;
        int days = 0;

        Phase phase = Phase.Years;
        DateTimeSpan span = new DateTimeSpan();
        int officialDay = current.Day;

        while (phase != Phase.Done)
        {
            switch (phase)
            {
                case Phase.Years:
                    if (current.AddYears(years + 1) > date2)
                    {
                        phase = Phase.Months;
                        current = current.AddYears(years);
                    }
                    else
                    {
                        years++;
                    }
                    break;
                case Phase.Months:
                    if (current.AddMonths(months + 1) > date2)
                    {
                        phase = Phase.Days;
                        current = current.AddMonths(months);
                        if (current.Day < officialDay && officialDay <= DateTime.DaysInMonth(current.Year, current.Month))
                            current = current.AddDays(officialDay - current.Day);
                    }
                    else
                    {
                        months++;
                    }
                    break;
                case Phase.Days:
                    if (current.AddDays(days + 1) > date2)
                    {
                        current = current.AddDays(days);
                        var timespan = date2 - current;
                        span = new DateTimeSpan(years, months, days, timespan.Hours, timespan.Minutes, timespan.Seconds, timespan.Milliseconds);
                        phase = Phase.Done;
                    }
                    else
                    {
                        days++;
                    }
                    break;
            }
        }

        return span;
    }
}
Kirk Woll
fonte
2
@KirkWoll thanks. Mas por que é que DateTimeSpan retorna 34dias para esta data diferença de tempo na verdade é 35 timeanddate.com/date/...
Deeptechtons
@Deeptechtons, nice catch. Você trouxe à minha atenção alguns problemas, tanto relacionados à data de início 31quanto à data "passa" meses com menos dias. Inverti a lógica (de modo que ela vá do início para o posterior e vice-versa) e agora acumule os meses sem modificar a data atual (e, portanto, passando entre os meses com menos dias). Ainda não tenho certeza do resultado ideal. deve ser ao comparar 10/31/2012com 11/30/2012. No momento, o resultado é 1mês.
precisa
@KirkWoll obrigado pela atualização, talvez eu tenho mais algumas pegadinhas deixe-me afirmar que depois de alguns testes bom trabalho :)
Deeptechtons
11
Eu escrevi uma resposta stackoverflow.com/a/17537472/1737957 para uma pergunta semelhante que testou as respostas propostas (e descobriu que a maioria delas não funciona). Esta resposta é uma das poucas que funciona (de acordo com o meu conjunto de testes). Link para o github na minha resposta.
precisa saber é
@KirkWoll - Esta resposta parece não funcionar para casos extremos onde a data inicial possui um valor de dia maior que o mês atual ou onde a data de origem é um dia bissexto. Tente 2020-02-29para 2021-06-29- ele retorna "1a 4m 1d", mas o valor deve ser "1a 4m 0d", certo?
Enigmatividade
37

Você poderia fazer

if ( date1.AddMonths(x) > date2 )
Mongus Pong
fonte
Isso é tão simples e funciona perfeito para mim. Fiquei agradavelmente surpreso ao ver que ele funciona como pretendido ao calcular uma data do final de 1 mês para uma data no final do mês seguinte, com menos dias. Por exemplo .. 1-31-2018 + 1 mês =
28/02
Esta é uma das melhores soluções.
barnacle.m
Solução realmente simples e eficiente! A melhor resposta proposta.
Cedric Arnould,
2
E se date1 = 28-10-2018 e date2 = 21-12-2018? A resposta será 2. enquanto a resposta correta deve ser 3. O intervalo de datas é de 3 meses. se contarmos apenas meses ignorando dias. Portanto, esta resposta NÃO está correta.
Tommix 8/03/19
Mais lógico seria: if ( date1.AddMonths(x).Month == date2.Month )então você apenas usa x + 1 como meses contados
Tommix
34

Se você deseja o número exato de meses completos, sempre positivo (2000-01-15, 2000-02-14 retorna 0), considerando que um mês inteiro é quando você alcança o mesmo dia no mês seguinte (algo como o cálculo da idade)

public static int GetMonthsBetween(DateTime from, DateTime to)
{
    if (from > to) return GetMonthsBetween(to, from);

    var monthDiff = Math.Abs((to.Year * 12 + (to.Month - 1)) - (from.Year * 12 + (from.Month - 1)));

    if (from.AddMonths(monthDiff) > to || to.Day < from.Day)
    {
        return monthDiff - 1;
    }
    else
    {
        return monthDiff;
    }
}

Editar motivo: o código antigo não estava correto em alguns casos, como:

new { From = new DateTime(1900, 8, 31), To = new DateTime(1901, 8, 30), Result = 11 },

Test cases I used to test the function:

var tests = new[]
{
    new { From = new DateTime(1900, 1, 1), To = new DateTime(1900, 1, 1), Result = 0 },
    new { From = new DateTime(1900, 1, 1), To = new DateTime(1900, 1, 2), Result = 0 },
    new { From = new DateTime(1900, 1, 2), To = new DateTime(1900, 1, 1), Result = 0 },
    new { From = new DateTime(1900, 1, 1), To = new DateTime(1900, 2, 1), Result = 1 },
    new { From = new DateTime(1900, 2, 1), To = new DateTime(1900, 1, 1), Result = 1 },
    new { From = new DateTime(1900, 1, 31), To = new DateTime(1900, 2, 1), Result = 0 },
    new { From = new DateTime(1900, 8, 31), To = new DateTime(1900, 9, 30), Result = 0 },
    new { From = new DateTime(1900, 8, 31), To = new DateTime(1900, 10, 1), Result = 1 },
    new { From = new DateTime(1900, 1, 1), To = new DateTime(1901, 1, 1), Result = 12 },
    new { From = new DateTime(1900, 1, 1), To = new DateTime(1911, 1, 1), Result = 132 },
    new { From = new DateTime(1900, 8, 31), To = new DateTime(1901, 8, 30), Result = 11 },
};
Guillaume86
fonte
Apenas para evitar confusão para outras pessoas, acho que essa solução não está correta. Usando o caso de teste: new { From = new DateTime(2015, 12, 31), To = new DateTime(2015, 6, 30), Result = 6 } o teste falhará, pois o resultado é 5. #
Cristian Badila
Adicionada uma
descrição
Eu não estou certo que eu entendo, minha função retorna 6 como deveria: dotnetfiddle.net/MRZNnC
Guillaume86
Copiei o caso de teste aqui à mão e há um erro. A especificação não deve ser: new { From = new DateTime(2015, 12, 31), To = new DateTime(2016, 06, 30), Result = 6 }. O "bug" está no to.Day < from.Daycódigo que não leva em consideração que os meses podem terminar em um "dia do mês" diferente. Nesse caso, de 31 de dezembro de 2015 a 30 de junho de 2016, 6 meses completos se passaram (já que junho tem 30 dias), mas seu código retornaria 5.
Cristian Badila
3
É um comportamento esperado na minha opinião, bem, ou é o comportamento que eu espero, pelo menos. Precisei que um mês completo é quando você chega ao mesmo dia (ou no próximo mês, como neste caso).
Guillaume86
22

Eu verifiquei o uso desse método no VB.NET via MSDN e parece que ele tem muitos usos. Não existe um método interno em C #. (Mesmo que não seja uma boa ideia), você pode chamar VBs em C #.

  1. Adicione Microsoft.VisualBasic.dllao seu projeto como referência
  2. use Microsoft.VisualBasic.DateAndTime.DateDiff no seu código
Cheng Chen
fonte
7
Por que você acha que não é uma boa ideia? Intuitivamente, eu acho que a biblioteca é 'apenas outra biblioteca .NET' para o tempo de execução. Note, eu estou interpretando o advogado do diabo aqui, eu também ficaria relutante em fazer isso, já que "parece errado" (tipo de trapaça), mas me pergunto se há alguma razão técnica convincente para não fazer isso.
Adam Ralph
3
@ AdamRalph: Não há nenhuma razão para não fazê-lo. Essas bibliotecas são implementadas em código 100% gerenciado, portanto, é tudo igual a tudo o resto. A única diferença concebível é que o Microsoft.VisualBasic.dllmódulo precisa ser carregado, mas o tempo necessário para isso é insignificante. Não há razão para se enganar com os recursos completamente testados e úteis, apenas porque você optou por escrever seu programa em C #. (Isso vale para coisas como My.Application.SplashScreenbem.)
Cody Grey
3
Você mudaria de idéia se soubesse que estava escrito em C #? Isso foi. Pela mesma lógica, o uso de System.Data e PresentationFramework também é trapaça, partes substanciais dele são escritas em C ++ / CLI.
Hans Passant
3
@ AdamRalph: Algum exemplo específico dessa "bagagem estranha" que vem à mente? Ou você está dizendo isso puramente hipoteticamente? E sim, isso pode atrapalhar a mente de alguns de seus colegas de C # que escreveram uma quantidade épica de código para fazer algo que você pode fazer em uma linha com a usingafirmação correta , mas duvido que haja algum dano sério.
Cody Gray
11
@ Gray Gray: concordou, o exemplo é trivial como você ilustra. É o 'ruído' do código extra introduzido chamando um método tão incomum (de um C # POV) que eu gostaria de evitar. Em uma equipe bem organizada, essas coisas seriam incluídas na revisão de código e podem ser facilmente evitadas. BTW - Não estou tentando atacar o VB6 / VB.NET. Eu descrevi esses métodos como 'estranhos' apenas porque, de um POV .NET, não há razão para DateAndTime.Year()existir, já que DateTimepossui uma Yearpropriedade. Existe apenas para fazer o VB.NET parecer mais com o VB6. Como ex-programador VB6, eu posso apreciar este ;-)
Adam Ralph
10

Para obter diferença em meses (inclusive no início e no final), independentemente das datas:

DateTime start = new DateTime(2013, 1, 1);
DateTime end = new DateTime(2014, 2, 1);
var diffMonths = (end.Month + end.Year * 12) - (start.Month + start.Year * 12);
Chirag
fonte
5
Imagine starte endé idêntico. Então você obtém o resultado 1. Como isso está correto? Por que você adiciona 1 ao resultado? Quem está votando positivamente nesta resposta: - /?
paul
Para datas idênticas, o resultado será 1. Basicamente, contará todos os meses, inclusive os meses de início e de término.
Chirag
3
não parece a diferença entre dois itens para mim. Qual é a diferença entre 2 e 2? É realmente 1? Gostaria de sugerir a diferença é 0.
paul
8

Use o tempo de Noda :

LocalDate start = new LocalDate(2013, 1, 5);
LocalDate end = new LocalDate(2014, 6, 1);
Period period = Period.Between(start, end, PeriodUnits.Months);
Console.WriteLine(period.Months); // 16

(fonte de exemplo)

Edward Brey
fonte
7

Eu só precisava de algo simples para atender, por exemplo, as datas de emprego em que apenas o mês / ano é inserido, de modo que desejamos anos e meses distintos trabalhados. É isso que eu uso, aqui apenas para utilidade

public static YearsMonths YearMonthDiff(DateTime startDate, DateTime endDate) {
    int monthDiff = ((endDate.Year * 12) + endDate.Month) - ((startDate.Year * 12) + startDate.Month) + 1;
    int years = (int)Math.Floor((decimal) (monthDiff / 12));
    int months = monthDiff % 12;
    return new YearsMonths {
        TotalMonths = monthDiff,
            Years = years,
            Months = months
    };
}

.NET Fiddle

jenson-button-event
fonte
4

Você pode usar a classe DateDiff da Time Period Library for .NET :

// ----------------------------------------------------------------------
public void DateDiffSample()
{
  DateTime date1 = new DateTime( 2009, 11, 8, 7, 13, 59 );
  DateTime date2 = new DateTime( 2011, 3, 20, 19, 55, 28 );
  DateDiff dateDiff = new DateDiff( date1, date2 );

  // differences
  Console.WriteLine( "DateDiff.Months: {0}", dateDiff.Months );
  // > DateDiff.Months: 16

  // elapsed
  Console.WriteLine( "DateDiff.ElapsedMonths: {0}", dateDiff.ElapsedMonths );
  // > DateDiff.ElapsedMonths: 4

  // description
  Console.WriteLine( "DateDiff.GetDescription(6): {0}", dateDiff.GetDescription( 6 ) );
  // > DateDiff.GetDescription(6): 1 Year 4 Months 12 Days 12 Hours 41 Mins 29 Secs
} // DateDiffSample

fonte
2

Aqui está minha contribuição para obter a diferença nos meses que eu achei precisos:

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.

Morgs
fonte
2

Isso funcionou para o que eu precisava. O dia do mês não importava no meu caso, porque sempre é o último dia do mês.

public static int MonthDiff(DateTime d1, DateTime d2){
    int retVal = 0;

    if (d1.Month<d2.Month)
    {
        retVal = (d1.Month + 12) - d2.Month;
        retVal += ((d1.Year - 1) - d2.Year)*12;
    }
    else
    {
        retVal = d1.Month - d2.Month;
        retVal += (d1.Year - d2.Year)*12;
    }
    //// Calculate the number of years represented and multiply by 12
    //// Substract the month number from the total
    //// Substract the difference of the second month and 12 from the total
    //retVal = (d1.Year - d2.Year) * 12;
    //retVal = retVal - d1.Month;
    //retVal = retVal - (12 - d2.Month);

    return retVal;
}
Elmer
fonte
2

A maneira mais precisa é essa que retorna a diferença em meses por fração:

private double ReturnDiffereceBetweenTwoDatesInMonths(DateTime startDateTime, DateTime endDateTime)
{
    double result = 0;
    double days = 0;
    DateTime currentDateTime = startDateTime;
    while (endDateTime > currentDateTime.AddMonths(1))
    {
        result ++;

        currentDateTime = currentDateTime.AddMonths(1);
    }

    if (endDateTime > currentDateTime)
    {
        days = endDateTime.Subtract(currentDateTime).TotalDays;

    }
    return result + days/endDateTime.GetMonthDays;
}
Saeed Mahmoudi
fonte
2

Aqui está uma solução simples que funciona pelo menos para mim. Provavelmente não é o mais rápido porque usa o recurso AddMonth do DateTime em um loop:

public static int GetMonthsDiff(DateTime start, DateTime end)
{
    if (start > end)
        return GetMonthsDiff(end, start);

    int months = 0;
    do
    {
        start = start.AddMonths(1);
        if (start > end)
            return months;

        months++;
    }
    while (true);
}
Simon Mourier
fonte
1
Public Class ClassDateOperation
    Private prop_DifferenceInDay As Integer
    Private prop_DifferenceInMonth As Integer
    Private prop_DifferenceInYear As Integer


    Public Function DayMonthYearFromTwoDate(ByVal DateStart As Date, ByVal DateEnd As Date) As ClassDateOperation
        Dim differenceInDay As Integer
        Dim differenceInMonth As Integer
        Dim differenceInYear As Integer
        Dim myDate As Date

        DateEnd = DateEnd.AddDays(1)

        differenceInYear = DateEnd.Year - DateStart.Year

        If DateStart.Month <= DateEnd.Month Then
            differenceInMonth = DateEnd.Month - DateStart.Month
        Else
            differenceInYear -= 1
            differenceInMonth = (12 - DateStart.Month) + DateEnd.Month
        End If


        If DateStart.Day <= DateEnd.Day Then
            differenceInDay = DateEnd.Day - DateStart.Day
        Else

            myDate = CDate("01/" & DateStart.AddMonths(1).Month & "/" & DateStart.Year).AddDays(-1)
            If differenceInMonth <> 0 Then
                differenceInMonth -= 1
            Else
                differenceInMonth = 11
                differenceInYear -= 1
            End If

            differenceInDay = myDate.Day - DateStart.Day + DateEnd.Day

        End If

        prop_DifferenceInDay = differenceInDay
        prop_DifferenceInMonth = differenceInMonth
        prop_DifferenceInYear = differenceInYear

        Return Me
    End Function

    Public ReadOnly Property DifferenceInDay() As Integer
        Get
            Return prop_DifferenceInDay
        End Get
    End Property

    Public ReadOnly Property DifferenceInMonth As Integer
        Get
            Return prop_DifferenceInMonth
        End Get
    End Property

    Public ReadOnly Property DifferenceInYear As Integer
        Get
            Return prop_DifferenceInYear
        End Get
    End Property

End Class
Mohammad Ali
fonte
1

Isto é da minha própria biblioteca, retornará a diferença de meses entre duas datas.

public static int MonthDiff(DateTime d1, DateTime d2)
{
    int retVal = 0;

    // Calculate the number of years represented and multiply by 12
    // Substract the month number from the total
    // Substract the difference of the second month and 12 from the total
    retVal = (d1.Year - d2.Year) * 12;
    retVal = retVal - d1.Month;
    retVal = retVal - (12 - d2.Month);

    return retVal;
}
Wayne
fonte
11
Isto funciona? Eu continuo recebendo 11 no papel para Jan-31-2014eDec-31-2013
Dave Cousineau
1

Você pode ter uma função parecida com esta.

Por exemplo, de 27/12/2012 a 29/12/2012 passa a 3 dias. Da mesma forma, de 15/12/2012 a 15/01/2013 passa a 2 meses, porque até 14/01/2013 é 1 mês. a partir do dia 15 começa o segundo mês.

Você pode remover o "=" na segunda condição if, se não desejar incluir os dois dias no cálculo. ou seja, de 15/12/2012 a 15/01/2013 é de 1 mês.

public int GetMonths(DateTime startDate, DateTime endDate)
{
    if (startDate > endDate)
    {
        throw new Exception("Start Date is greater than the End Date");
    }

    int months = ((endDate.Year * 12) + endDate.Month) - ((startDate.Year * 12) + startDate.Month);

    if (endDate.Day >= startDate.Day)
    {
        months++;
    }

    return months;
}
Firnas
fonte
1

você pode usar a seguinte extensão: Code

public static class Ext
{
    #region Public Methods

    public static int GetAge(this DateTime @this)
    {
        var today = DateTime.Today;
        return ((((today.Year - @this.Year) * 100) + (today.Month - @this.Month)) * 100 + today.Day - @this.Day) / 10000;
    }

    public static int DiffMonths(this DateTime @from, DateTime @to)
    {
        return (((((@to.Year - @from.Year) * 12) + (@to.Month - @from.Month)) * 100 + @to.Day - @from.Day) / 100);
    }

    public static int DiffYears(this DateTime @from, DateTime @to)
    {
        return ((((@to.Year - @from.Year) * 100) + (@to.Month - @from.Month)) * 100 + @to.Day - @from.Day) / 10000;
    }

    #endregion Public Methods
}

Implementação!

int Age;
int years;
int Months;
//Replace your own date
var d1 = new DateTime(2000, 10, 22);
var d2 = new DateTime(2003, 10, 20);
//Age
Age = d1.GetAge();
Age = d2.GetAge();
//positive
years = d1.DiffYears(d2);
Months = d1.DiffMonths(d2);
//negative
years = d2.DiffYears(d1);
Months = d2.DiffMonths(d1);
//Or
Months = Ext.DiffMonths(d1, d2);
years = Ext.DiffYears(d1, d2); 
Waleed AK
fonte
1

Aqui está uma solução muito mais concisa usando o VB.Net DateDiff apenas por ano, mês, dia. Você também pode carregar a biblioteca DateDiff em C #.

date1 deve ser <= date2

VB.NET

Dim date1 = Now.AddDays(-2000)
Dim date2 = Now
Dim diffYears = DateDiff(DateInterval.Year, date1, date2) - If(date1.DayOfYear > date2.DayOfYear, 1, 0)
Dim diffMonths = DateDiff(DateInterval.Month, date1, date2) - diffYears * 12 - If(date1.Day > date2.Day, 1, 0)
Dim diffDays = If(date2.Day >= date1.Day, date2.Day - date1.Day, date2.Day + (Date.DaysInMonth(date1.Year, date1.Month) - date1.Day))

C #

DateTime date1 = Now.AddDays(-2000);
DateTime date2 = Now;
int diffYears = DateDiff(DateInterval.Year, date1, date2) - date1.DayOfYear > date2.DayOfYear ? 1 : 0;
int diffMonths = DateDiff(DateInterval.Month, date1, date2) - diffYears * 12 - date1.Day > date2.Day ? 1 : 0;
int diffDays = date2.Day >= date1.Day ? date2.Day - date1.Day : date2.Day + (System.DateTime.DaysInMonth(date1.Year, date1.Month) - date1.Day);
Brent
fonte
1

Isso é uma resposta à resposta de Kirk Woll. Ainda não tenho pontos de reputação suficientes para responder a um comentário ...

Eu gostei da solução de Kirk e iria descaradamente descarná-la e usá-la no meu código, mas, quando olhei, percebi que era muito complicado. Comutação e loop desnecessários e um construtor público que não faz sentido usar.

Aqui está a minha reescrita:

public class DateTimeSpan {
    private DateTime _date1;
    private DateTime _date2;
    private int _years;
    private int _months;
    private int _days;
    private int _hours;
    private int _minutes;
    private int _seconds;
    private int _milliseconds;

    public int Years { get { return _years; } }
    public int Months { get { return _months; } }
    public int Days { get { return _days; } }
    public int Hours { get { return _hours; } }
    public int Minutes { get { return _minutes; } }
    public int Seconds { get { return _seconds; } }
    public int Milliseconds { get { return _milliseconds; } }

    public DateTimeSpan(DateTime date1, DateTime date2) {
        _date1 = (date1 > date2) ? date1 : date2;
        _date2 = (date2 < date1) ? date2 : date1;

        _years = _date1.Year - _date2.Year;
        _months = (_years * 12) + _date1.Month - _date2.Month;
        TimeSpan t = (_date2 - _date1);
        _days = t.Days;
        _hours = t.Hours;
        _minutes = t.Minutes;
        _seconds = t.Seconds;
        _milliseconds = t.Milliseconds;

    }

    public static DateTimeSpan CompareDates(DateTime date1, DateTime date2) {
        return new DateTimeSpan(date1, date2);
    }
}

Usage1, praticamente o mesmo:

void Main()
{
    DateTime compareTo = DateTime.Parse("8/13/2010 8:33:21 AM");
    DateTime now = DateTime.Parse("2/9/2012 10:10:11 AM");
    var dateSpan = new DateTimeSpan(compareTo, now);
    Console.WriteLine("Years: " + dateSpan.Years);
    Console.WriteLine("Months: " + dateSpan.Months);
    Console.WriteLine("Days: " + dateSpan.Days);
    Console.WriteLine("Hours: " + dateSpan.Hours);
    Console.WriteLine("Minutes: " + dateSpan.Minutes);
    Console.WriteLine("Seconds: " + dateSpan.Seconds);
    Console.WriteLine("Milliseconds: " + dateSpan.Milliseconds);
}

Uso2, semelhante:

void Main()
{
    DateTime compareTo = DateTime.Parse("8/13/2010 8:33:21 AM");
    DateTime now = DateTime.Parse("2/9/2012 10:10:11 AM");
    Console.WriteLine("Years: " + DateTimeSpan.CompareDates(compareTo, now).Years);
    Console.WriteLine("Months: " + DateTimeSpan.CompareDates(compareTo, now).Months);
    Console.WriteLine("Days: " + DateTimeSpan.CompareDates(compareTo, now).Days);
    Console.WriteLine("Hours: " + DateTimeSpan.CompareDates(compareTo, now).Hours);
    Console.WriteLine("Minutes: " + DateTimeSpan.CompareDates(compareTo, now).Minutes);
    Console.WriteLine("Seconds: " + DateTimeSpan.CompareDates(compareTo, now).Seconds);
    Console.WriteLine("Milliseconds: " + DateTimeSpan.CompareDates(compareTo, now).Milliseconds);
}
John A
fonte
1

No meu caso, é necessário calcular o mês completo a partir da data de início até o dia anterior a esse dia no próximo mês ou do início ao fim do mês.


Ex: de 01/01/2018 a 31/01/2018 é um mês completo
Ex2: de 01/05/2018 a 02/04/2018 é um mês completo

Então, com base nisso, aqui está minha solução:

public static DateTime GetMonthEnd(DateTime StartDate, int MonthsCount = 1)
{
    return StartDate.AddMonths(MonthsCount).AddDays(-1);
}
public static Tuple<int, int> CalcPeriod(DateTime StartDate, DateTime EndDate)
{
    int MonthsCount = 0;
    Tuple<int, int> Period;
    while (true)
    {
        if (GetMonthEnd(StartDate) > EndDate)
            break;
        else
        {
            MonthsCount += 1;
            StartDate = StartDate.AddMonths(1);
        }
    }
    int RemainingDays = (EndDate - StartDate).Days + 1;
    Period = new Tuple<int, int>(MonthsCount, RemainingDays);
    return Period;
}

Uso:

Tuple<int, int> Period = CalcPeriod(FromDate, ToDate);

Nota: no meu caso, era necessário calcular os dias restantes após os meses completos, portanto, se não for o seu caso, você poderá ignorar o resultado dos dias ou até mesmo alterar o método de retorno de tupla para inteiro.

Ahmed
fonte
1
public static int PayableMonthsInDuration(DateTime StartDate, DateTime EndDate)
{
    int sy = StartDate.Year; int sm = StartDate.Month; int count = 0;
    do
    {
        count++;if ((sy == EndDate.Year) && (sm >= EndDate.Month)) { break; }
        sm++;if (sm == 13) { sm = 1; sy++; }
    } while ((EndDate.Year >= sy) || (EndDate.Month >= sm));
    return (count);
}

Esta solução é para o cálculo de aluguel / assinatura, onde a diferença não significa subtração, deve ser o período dentro dessas duas datas.

Sukanta
fonte
1

Existem três casos: mesmo ano, ano anterior e outros anos.

Se o dia do mês não importa ...

public int GetTotalNumberOfMonths(DateTime start, DateTime end)
{
    // work with dates in the right order
    if (start > end)
    {
        var swapper = start;
        start = end;
        end = swapper;
    }

    switch (end.Year - start.Year)
    {
        case 0: // Same year
            return end.Month - start.Month;

        case 1: // last year
            return (12 - start.Month) + end.Month;

        default:
            return 12 * (3 - (end.Year - start.Year)) + (12 - start.Month) + end.Month;
    }
}
Patrice Calvé
fonte
1

Eu escrevi uma função para fazer isso, porque as outras maneiras não estavam funcionando para mim.

public string getEndDate (DateTime startDate,decimal monthCount)
{
    int y = startDate.Year;
    int m = startDate.Month;

    for (decimal  i = monthCount; i > 1; i--)
    {
        m++;
        if (m == 12)
        { y++;
            m = 1;
        }
    }
    return string.Format("{0}-{1}-{2}", y.ToString(), m.ToString(), startDate.Day.ToString());
}
reza akhlaghi
fonte
Por favor, responda em Inglês (vs qualquer língua inventada ...)
kleopatra
Por que não apenas startDate.AddMonths (monthCount) .ToShortDateString ()? Isso não responde à pergunta original que foi feita de qualquer maneira!
usar o seguinte código
ah, desculpe @TabbyCool, esse código funciona bem no meu programa! A regra dos programadores diz: primeiro o código funciona e depois a otimização! tanx for ur comment :)
reza akhlaghi
1

Meu entendimento da diferença total de meses entre duas datas tem uma parte integral e uma fracionária (a data importa).

A parte integrante é a diferença de meses completos.

A parte fracionária, para mim, é a diferença da% do dia (para os dias completos do mês) entre os meses inicial e final.

public static class DateTimeExtensions
{
    public static double TotalMonthsDifference(this DateTime from, DateTime to)
    {
        //Compute full months difference between dates
        var fullMonthsDiff = (to.Year - from.Year)*12 + to.Month - from.Month;

        //Compute difference between the % of day to full days of each month
        var fractionMonthsDiff = ((double)(to.Day-1) / (DateTime.DaysInMonth(to.Year, to.Month)-1)) -
            ((double)(from.Day-1)/ (DateTime.DaysInMonth(from.Year, from.Month)-1));

        return fullMonthsDiff + fractionMonthsDiff;
    }
}

Com esta extensão, esses são os resultados:

2/29/2000 TotalMonthsDifference 2/28/2001 => 12
2/28/2000 TotalMonthsDifference 2/28/2001 => 12.035714285714286
01/01/2000 TotalMonthsDifference 01/16/2000 => 0.5
01/31/2000 TotalMonthsDifference 01/01/2000 => -1.0
01/31/2000 TotalMonthsDifference 02/29/2000 => 1.0
01/31/2000 TotalMonthsDifference 02/28/2000 => 0.9642857142857143
01/31/2001 TotalMonthsDifference 02/28/2001 => 1.0
George Mavritsakis
fonte
1

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

Esta solução calcula entre duas datas nos meses entre a suposição de 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)

Por exemplo, se você tem 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á.

Foi testado minuciosamente, provavelmente o limpará mais tarde, enquanto o usamos, mas aqui:

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
}
GreatNate
fonte
1

Com base no excelente trabalho DateTimeSpan feito acima, normalizei um pouco o código; isso parece funcionar muito bem:

public class DateTimeSpan
{
  private DateTimeSpan() { }

  private DateTimeSpan(int years, int months, int days, int hours, int minutes, int seconds, int milliseconds)
  {
    Years = years;
    Months = months;
    Days = days;
    Hours = hours;
    Minutes = minutes;
    Seconds = seconds;
    Milliseconds = milliseconds;
  }

  public int Years { get; private set; } = 0;
  public int Months { get; private set; } = 0;
  public int Days { get; private set; } = 0;
  public int Hours { get; private set; } = 0;
  public int Minutes { get; private set; } = 0;
  public int Seconds { get; private set; } = 0;
  public int Milliseconds { get; private set; } = 0;

  public static DateTimeSpan CompareDates(DateTime StartDate, DateTime EndDate)
  {
    if (StartDate.Equals(EndDate)) return new DateTimeSpan();
    DateTimeSpan R = new DateTimeSpan();
    bool Later;
    if (Later = StartDate > EndDate)
    {
      DateTime D = StartDate;
      StartDate = EndDate;
      EndDate = D;
    }

    // Calculate Date Stuff
    for (DateTime D = StartDate.AddYears(1); D < EndDate; D = D.AddYears(1), R.Years++) ;
    if (R.Years > 0) StartDate = StartDate.AddYears(R.Years);
    for (DateTime D = StartDate.AddMonths(1); D < EndDate; D = D.AddMonths(1), R.Months++) ;
    if (R.Months > 0) StartDate = StartDate.AddMonths(R.Months);
    for (DateTime D = StartDate.AddDays(1); D < EndDate; D = D.AddDays(1), R.Days++) ;
    if (R.Days > 0) StartDate = StartDate.AddDays(R.Days);

    // Calculate Time Stuff
    TimeSpan T1 = EndDate - StartDate;
    R.Hours = T1.Hours;
    R.Minutes = T1.Minutes;
    R.Seconds = T1.Seconds;
    R.Milliseconds = T1.Milliseconds;

    // Return answer. Negate values if the Start Date was later than the End Date
    if (Later)
      return new DateTimeSpan(-R.Years, -R.Months, -R.Days, -R.Hours, -R.Minutes, -R.Seconds, -R.Milliseconds);
    return R;
  }
}
Dan Sutton
fonte
Ao comparar com CompareDates(x, y)onde x={01/02/2019 00:00:00}e y={01/05/2020 00:00:00}, em seguida, Monthsdá-me2
Bassie
1

Esta função estática simples calcula a fração de meses entre duas datas, por exemplo

  • 1.1 a 31.1. = 1,0
  • 1.4 para 15.4. = 0,5
  • 16.4 para 30,4. = 0,5
  • 1.3 para 1.4. = 1 + 1/30

A função assume que a primeira data é menor que a segunda data. Para lidar com intervalos de tempo negativos, pode-se modificar a função facilmente, introduzindo um sinal e uma troca de variável no início.

public static double GetDeltaMonths(DateTime t0, DateTime t1)
{
     DateTime t = t0;
     double months = 0;
     while(t<=t1)
     {
         int daysInMonth = DateTime.DaysInMonth(t.Year, t.Month);
         DateTime endOfMonth = new DateTime(t.Year, t.Month, daysInMonth);
         int cutDay = endOfMonth <= t1 ? daysInMonth : t1.Day;
         months += (cutDay - t.Day + 1) / (double) daysInMonth;
         t = new DateTime(t.Year, t.Month, 1).AddMonths(1);
     }
     return Math.Round(months,2);
 }
Michael
fonte
0

Conseguir calcular a diferença entre duas datas em meses é uma coisa perfeitamente lógica a se fazer e é necessária em muitos aplicativos de negócios. Os vários codificadores aqui que fizeram comentários como - qual é a diferença nos meses entre "1 de maio de 2010" e "16 de junho de 2010, qual é a diferença nos meses entre 31 de dezembro de 2010 e 1 de janeiro de 2011? - não conseguiram entender os princípios básicos dos aplicativos de negócios.

Aqui está a resposta para os 2 comentários acima - O número de meses entre 1 de maio de 2010 e 16 de junho de 2010 é de 1 mês, o número de meses entre 31 de dezembro de 2010 e 1 de janeiro de 2011 é 0. seria muito tolo calculá-los como 1,5 meses e 1 segundo, como sugeriram os codificadores acima.

As pessoas que trabalharam com cartão de crédito, processamento de hipotecas, processamento de impostos, processamento de aluguel, cálculos de juros mensais e uma vasta variedade de outras soluções comerciais concordariam.

O problema é que essa função não está incluída no C # ou no VB.NET. Datediff leva apenas em consideração anos ou o componente do mês, portanto, é realmente inútil.

Aqui estão alguns exemplos da vida real de onde você precisa e pode calcular corretamente os meses:

Você morou em um aluguel de curta duração de 18 de fevereiro a 23 de agosto. Quantos meses você ficou lá? A resposta é simples - 6 meses

Você tem uma conta bancária em que os juros são calculados e pagos no final de cada mês. Você deposita o dinheiro em 10 de junho e o retira em 29 de outubro (mesmo ano). Por quantos meses você se interessa? Resposta muito simples - 4 meses (novamente os dias extras não importam)

Nos aplicativos de negócios, na maioria das vezes, quando você precisa calcular meses, é porque precisa conhecer meses "completos" com base em como os humanos calculam o tempo; não se baseia em pensamentos abstratos / irrelevantes.

Tom
fonte
5
Essa é uma das razões pelas quais contabilidade não é matemática. Na contabilidade, o resultado depende da maneira como você o calcula. Conheço seus pontos de vista e a "visão comercial comum" sobre isso, mas essa explicação está claramente errada. Entre 30.11.2012 e 01.12.2012, existem 0 ou 1/30 ou 1/31 ou 1 ou 2 meses, dependendo do que você solicitou . As datas foram exclusivas ou inclusivas? Você pediu o número de meses cruzados, tocados ou passados? Deseja arredondar, arredondar para baixo ou exato?
quetzalcoatl
3
Agora, explique-o a um profissional de negócios ou a um contador e eles lhe darão uma aparência confusa. É sempre "tão óbvio para eles que, é claro, significava X, Y e Z, como você poderia pensar de maneira diferente?" Agora pegue várias pessoas de negócios e tente fazê-las concordar com o assunto. É mais provável que os contadores concordem, porque em algum momento eles usarão a matemática para verificar com quais opções eles podem resumir acidentalmente o mesmo período duas vezes, etc. regras comerciais adicionais, como ignorar dias extras.
quetzalcoatl
2
-1 Você está assumindo que todo software é um "aplicativo de negócios". O objetivo do código em questão não é mencionado. Você também assume que todos os "aplicativos de negócios" têm as mesmas regras, o que definitivamente não é verdade.
Jesse Webb
0

Estrutura expandida de Kirks com ToString (formato) e Duração (ms longos)

 public struct DateTimeSpan
{
    private readonly int years;
    private readonly int months;
    private readonly int days;
    private readonly int hours;
    private readonly int minutes;
    private readonly int seconds;
    private readonly int milliseconds;

    public DateTimeSpan(int years, int months, int days, int hours, int minutes, int seconds, int milliseconds)
    {
        this.years = years;
        this.months = months;
        this.days = days;
        this.hours = hours;
        this.minutes = minutes;
        this.seconds = seconds;
        this.milliseconds = milliseconds;
    }

    public int Years { get { return years; } }
    public int Months { get { return months; } }
    public int Days { get { return days; } }
    public int Hours { get { return hours; } }
    public int Minutes { get { return minutes; } }
    public int Seconds { get { return seconds; } }
    public int Milliseconds { get { return milliseconds; } }

    enum Phase { Years, Months, Days, Done }


    public string ToString(string format)
    {
        format = format.Replace("YYYY", Years.ToString());
        format = format.Replace("MM", Months.ToString());
        format = format.Replace("DD", Days.ToString());
        format = format.Replace("hh", Hours.ToString());
        format = format.Replace("mm", Minutes.ToString());
        format = format.Replace("ss", Seconds.ToString());
        format = format.Replace("ms", Milliseconds.ToString());
        return format;
    }


    public static DateTimeSpan Duration(long ms)
    {
        DateTime dt = new DateTime();
        return CompareDates(dt, dt.AddMilliseconds(ms));
    }


    public static DateTimeSpan CompareDates(DateTime date1, DateTime date2)
    {
        if (date2 < date1)
        {
            var sub = date1;
            date1 = date2;
            date2 = sub;
        }

        DateTime current = date1;
        int years = 0;
        int months = 0;
        int days = 0;

        Phase phase = Phase.Years;
        DateTimeSpan span = new DateTimeSpan();

        while (phase != Phase.Done)
        {
            switch (phase)
            {
                case Phase.Years:
                    if (current.AddYears(years + 1) > date2)
                    {
                        phase = Phase.Months;
                        current = current.AddYears(years);
                    }
                    else
                    {
                        years++;
                    }
                    break;
                case Phase.Months:
                    if (current.AddMonths(months + 1) > date2)
                    {
                        phase = Phase.Days;
                        current = current.AddMonths(months);
                    }
                    else
                    {
                        months++;
                    }
                    break;
                case Phase.Days:
                    if (current.AddDays(days + 1) > date2)
                    {
                        current = current.AddDays(days);
                        var timespan = date2 - current;
                        span = new DateTimeSpan(years, months, days, timespan.Hours, timespan.Minutes, timespan.Seconds, timespan.Milliseconds);
                        phase = Phase.Done;
                    }
                    else
                    {
                        days++;
                    }
                    break;
            }
        }

        return span;
    }
}
Ivan
fonte
0
  var dt1 = (DateTime.Now.Year * 12) + DateTime.Now.Month;
  var dt2 = (DateTime.Now.AddMonths(-13).Year * 12) + DateTime.Now.AddMonths(-13).Month;
  Console.WriteLine(dt1);
  Console.WriteLine(dt2);
  Console.WriteLine((dt1 - dt2));
Paulo
fonte