Diferencia en meses entre dos fechas

334

¿Cómo calcular la diferencia en meses entre dos fechas en C #?

¿Hay un equivalente del DateDiff()método de VB en C #? Necesito encontrar la diferencia en meses entre dos fechas separadas por años. La documentación dice que puedo usar TimeSpancomo:

TimeSpan ts = date1 - date2;

pero esto me da datos en días. No quiero dividir este número entre 30 porque no todos los meses son 30 días y dado que los dos valores de los operandos están bastante separados, me temo que dividir entre 30 podría darme un valor incorrecto.

¿Alguna sugerencia?

Rauf
fuente
27
Defina "diferencia en meses", ¿cuál es la diferencia en meses entre "1 de mayo de 2010" y "16 de junio de 2010"? 1.5, 1 o algo más?
Cheng Chen el
77
O, para enfatizar más este punto, ¿cuál es la diferencia en meses entre el 31 de diciembre de 2010 y el 1 de enero de 2011? Dependiendo del día, esto podría ser una diferencia de solo 1 segundo; ¿Contaría esto como una diferencia de un mes?
stakx - ya no contribuye el
Aquí está el código simple y corto en caso de que aún no pueda obtener la respuesta, vea este POST stackoverflow.com/questions/8820603/…
wirol
11
Danny: 1 mes y 15 días. stakx: 0 meses y 1 día. El punto es obtener el componente del mes . Esto me parece bastante obvio y es una buena pregunta.
Kirk Woll

Respuestas:

462

Suponiendo que el día del mes es irrelevante (es decir, la diferencia entre 2011.1.1 y 2010.12.31 es 1), con date1> date2 dando un valor positivo y date2> date1 un valor negativo

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

O, suponiendo que desea una cantidad aproximada de "meses promedio" entre las dos fechas, lo siguiente debería funcionar para todas las diferencias de fechas, excepto muy grandes.

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

Tenga en cuenta que si utilizara la última solución, las pruebas unitarias deberían indicar el intervalo de fechas más amplio con el que su aplicación está diseñada para trabajar y validar los resultados del cálculo en consecuencia.


Actualización (con agradecimiento a Gary )

Si usa el método de "meses promedio", un número ligeramente más preciso para usar para el "número promedio de días por año" es 365.2425 .

Adam Ralph
fuente
3
@Kurru - 365/12 es solo una medida aproximada de la duración promedio de un mes en días. Es una medida inexacta. Para rangos de fechas pequeños, esta imprecisión puede tolerarse, pero para rangos de fechas muy grandes, esta inexactitud puede volverse significativa.
Adam Ralph el
21
Creo que es necesario considerar el componente del día. Algo así (date1.Year - date2.Year) * 12 + date1.Month - date2.Month + (date1.Day >= date2.Day ? 0 : -1)
DrunkCoder
2
@DrunkCoder depende de los requisitos de un sistema determinado. En algunos casos, su solución puede ser la mejor opción. Por ejemplo, es importante considerar lo que sucede cuando dos fechas abarcan un mes de 31 días, un mes de 30 días, un febrero de 28 días o un febrero de 29 días. Si los resultados de su fórmula ofrecen lo que el sistema requiere, entonces es claramente la elección correcta. Si no, entonces se requiere algo más.
Adam Ralph el
66
Para apoyar lo que dijo Adam, pasé años escribiendo código para Acturaries. Algunos cálculos se dividieron por el número de días, se redondearon por 30 para obtener la cifra mensual . A veces, contando los meses asumidos, cada fecha comienza el primero del mes, cuente meses enteros en consecuencia . No hay mejor método para calcular fechas. A menos que usted sea ​​el cliente para el que está escribiendo el código, haga retroceder esto en la cadena y aclárelo, posiblemente por su contador de clientes.
Binario Worrier
1
365.2425 es un número de días ligeramente más preciso en un calendario gregoriano, si eso es lo que está utilizando. Sin embargo, por DateTime.MaxValue (1 de enero de 10000) eso es solo una diferencia de 59 días. Además, la definición de un año puede ser muy diferente dependiendo de su perspectiva en.wikipedia.org/wiki/Year .
Gary
207

Aquí hay una solución integral para devolver un DateTimeSpan, similar a un TimeSpan, excepto que incluye todos los componentes de fecha además de los componentes de tiempo.

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

Salidas:

Años: 1
Meses: 5
Días: 27
Horas: 1
Minutos: 36
Segundos: 50
Milisegundos: 0

Por conveniencia, he agrupado la lógica en la DateTimeSpanestructura, pero puede mover el método CompareDatesdonde lo considere conveniente. También tenga en cuenta que no importa qué fecha sea anterior a la otra.

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
fuente
2
@ Kirkoll gracias. Pero, ¿por qué DateTimeSpan devuelve 34días para esta diferencia de fecha y hora en realidad es 35 timeanddate.com/date/…?
Deeptechtons
@Deeptechtons, buena captura. Hubo un par de problemas que me ha llamado la atención, que tienen que ver con la fecha de inicio 31y la fecha "pasa" meses con menos días. He invertido la lógica (de modo que va de temprano a más tarde que viceversa) y ahora acumula los meses sin modificar la fecha actual (y por lo tanto pasando entre meses con menos días) Todavía no estoy completamente seguro de cuál es el resultado ideal debe ser cuando se compara 10/31/2012con 11/30/2012. En este momento el resultado es 1mes.
Kirk Woll
@KirkWoll gracias por la actualización, tal vez tengo algunas más trampas déjenme afirmarlo después de algunas pruebas Buen trabajo :)
Deeptechtons
1
Escribí una respuesta stackoverflow.com/a/17537472/1737957 a una pregunta similar que probó las respuestas propuestas (y descubrí que la mayoría de ellas no funcionan). Esta respuesta es una de las pocas que funciona (según mi conjunto de pruebas). Enlace a github en mi respuesta.
jwg
@KirkWoll: esta respuesta no parece funcionar para casos extremos donde la fecha de inicio tiene un valor de día mayor que el mes de la fecha o donde la fecha de origen es un día bisiesto. Pruebe 2020-02-29a 2021-06-29- devuelve "1a 4m 1d", pero el valor debe ser "1a 4m 0d", ¿verdad?
Enigmatividad
37

Podrías hacerlo

if ( date1.AddMonths(x) > date2 )
Mongus Pong
fuente
Esto es muy simple y funciona perfecto para mí. Me sorprendió gratamente ver que funciona según lo previsto al calcular una fecha del final de 1 mes a una fecha al final del próximo mes que tiene menos días. Por ejemplo ... 1-31-2018 + 1 mes = 28 de febrero de 218
lucky.expert
Esta es una de las mejores soluciones.
barnacle.m
¡Una solución realmente simple y eficiente! La mejor respuesta propuesta.
Cedric Arnould
2
¿Qué pasa si date1 = 2018-10-28 y date2 = 2018-12-21? La respuesta será 2. mientras que la respuesta correcta debería ser 3. Debido al intervalo de fechas es de 3 meses. si contamos solo meses ignorando días. Entonces esta respuesta NO es correcta.
Tommix
Más lógico sería: if ( date1.AddMonths(x).Month == date2.Month )entonces solo usas x + 1 mientras los meses cuentan
Tommix
34

Si desea el número exacto de meses completos, siempre positivo (2000-01-15, 2000-02-14 devuelve 0), teniendo en cuenta que un mes completo es cuando llega el mismo día del mes siguiente (algo así como el cálculo de la edad)

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

Motivo de edición: el código anterior no era correcto en algunos 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
fuente
Solo para evitar confusiones para otras personas, creo que esta solución no es correcta. Usando el caso de prueba: new { From = new DateTime(2015, 12, 31), To = new DateTime(2015, 6, 30), Result = 6 } la prueba fallará ya que el resultado es 5.
Cristian Badila
Se agregó una idea rápida con la solución que propongo aquí
Cristian Badila
No estoy seguro de obtenerlo, mi función devuelve 6 como debería: dotnetfiddle.net/MRZNnC
Guillaume86
Copié el caso de prueba aquí a mano y tiene un error. La especificación no debe ser: new { From = new DateTime(2015, 12, 31), To = new DateTime(2016, 06, 30), Result = 6 }. El "error" se encuentra en el to.Day < from.Daycódigo que no tiene en cuenta que los meses pueden terminar en un "día del mes" diferente. En este caso desde el 31 de diciembre de 2015, hasta el 30 de junio de 2016, habrán transcurrido 6 meses completos (dado que junio tiene 30 días) pero su código devolvería 5.
Cristian Badila
3
Es un comportamiento esperado en mi opinión, bueno, o es el comportamiento que espero al menos. Precisé que un mes completo es cuando llegas el mismo día (o el próximo mes como en este caso).
Guillaume86
22

Verifiqué el uso de este método en VB.NET a través de MSDN y parece que tiene muchos usos. No existe tal método incorporado en C #. (Incluso no es una buena idea) puede llamar a VB en C #.

  1. Agregar Microsoft.VisualBasic.dlla su proyecto como referencia
  2. utilizar Microsoft.VisualBasic.DateAndTime.DateDiff en su código
Cheng Chen
fuente
77
¿Por qué crees que no es una buena idea? Intuitivamente, supongo que la biblioteca es 'solo otra biblioteca .NET' para el tiempo de ejecución. Tenga en cuenta que estoy jugando al abogado del diablo aquí, también sería reacio a hacer esto ya que simplemente 'se siente mal' (una especie de trampa), pero me pregunto si hay alguna razón técnica convincente para no hacerlo.
Adam Ralph el
3
@ AdamRalph: No hay ninguna razón para no hacerlo. Esas bibliotecas se implementan en código 100% administrado, por lo que es todo lo mismo que todo lo demás. La única diferencia concebible es que el Microsoft.VisualBasic.dllmódulo debe cargarse, pero el tiempo que lleva hacerlo es insignificante. No hay razón para engañarse a sí mismo de las características útiles y probadas a fondo solo porque ha elegido escribir su programa en C #. (Esto también se aplica a cosas como esta My.Application.SplashScreen).
Cody Gray
3
¿Cambiarías de opinión si supieras que está escrito en C #? Era. Por la misma lógica, usar System.Data y PresentationFramework también es una trampa, partes sustanciales escritas en C ++ / CLI.
Hans Passant, el
3
@AdamRalph: ¿Algún ejemplo particular de ese "equipaje extraño" que se te ocurra? ¿O estás diciendo eso puramente hipotéticamente? Y sí, podría interferir con las mentes de algunos de sus amigos de C # que han estado escribiendo una cantidad épica de código para hacer algo que puede hacer en una línea con la usingdeclaración correcta , pero dudo que haya algún daño grave.
Cody Gray
1
@Cody Gray: de acuerdo, el ejemplo es trivial como ilustras. Es el 'ruido' adicional del código introducido al llamar a un método tan inusual (desde un C # POV) que me gustaría evitar. En un equipo bien organizado, tales cosas de todos modos se recogerían en la revisión del código y se pueden evitar fácilmente. Por cierto, no estoy tratando de atacar VB6 / VB.NET. Describí tales métodos como 'extraños' solo porque, desde un punto de vista de .NET, no hay razón para DateAndTime.Year()existir, dado que DateTimetiene una Yearpropiedad. Solo existe para hacer que VB.NET parezca más como VB6. Como ex programador de VB6, puedo apreciar esto ;-)
Adam Ralph
10

Para obtener la diferencia en meses (inicio y fin inclusive), independientemente de las fechas:

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
fuente
55
Imagina starty endson idénticos. Entonces obtienes un resultado de 1. ¿Cómo es eso correcto? ¿Por qué agregas 1 al resultado? ¿Quién está votando esta respuesta: - /?
Paul
Para fechas idénticas, dará salida como 1. Básicamente, contará todos los meses incluidos los meses de inicio y finalización.
Chirag
3
No me parece la diferencia entre dos elementos. ¿Cuál es la diferencia entre 2 y 2? ¿Es realmente 1? Sugeriría que la diferencia es 0.
Paul
8

Use Noda Time :

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

(fuente de ejemplo)

Edward Brey
fuente
7

Solo necesitaba algo simple para satisfacer, por ejemplo, fechas de empleo en las que solo se ingresa el mes / año, por lo que quería trabajar en años y meses distintos. Esto es lo que uso, aquí solo para utilidad

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
fuente
4

Puede usar la clase DateDiff de la Biblioteca de períodos de tiempo para .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

fuente
2

Aquí está mi contribución para obtener la diferencia en los meses que he encontrado que son 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 ) );

Puede crear otro método llamado DiffYears y aplicar exactamente la misma lógica que la anterior y AddYears en lugar de AddMonths en el ciclo while.

Morgs
fuente
2

Esto funcionó para lo que lo necesitaba. El día del mes no importó en mi caso porque siempre es el último día del mes.

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
fuente
2

La forma más precisa es esta que devuelve la diferencia en meses por fracción:

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
fuente
2

Aquí hay una solución simple que funciona al menos para mí. Sin embargo, probablemente no sea el más rápido porque utiliza la característica AddMonth de DateTime en un bucle:

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
fuente
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
fuente
1

Esto es de mi propia biblioteca, devolverá la diferencia de meses entre dos fechas.

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
fuente
1
¿Esto funciona? Sigo recibiendo 11 en papel para Jan-31-2014yDec-31-2013
Dave Cousineau
1

Puedes tener una función como esta.

Por ejemplo, del 27/12/2012 al 29/12/2012 se convierte en 3 días. Del mismo modo, del 15/12/2012 al 15/01/2013 se convierte en 2 meses, porque hasta el 14/01/2013 es 1 mes. a partir del 15 es el segundo mes que comenzó.

Puede eliminar el "=" en la segunda condición if, si no desea incluir ambos días en el cálculo. es decir, del 15/12/2012 al 15/01/2013 es 1 mes.

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
fuente
1

puede usar la siguiente extensión: Código

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
}

Implementación!

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
fuente
1

Aquí hay una solución mucho más concisa usando VB.Net DateDiff solo para Año, Mes, Día. También puede cargar la biblioteca DateDiff en C #.

date1 debe 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
fuente
1

Esto es en respuesta a la respuesta de Kirk Woll. Todavía no tengo suficientes puntos de reputación para responder a un comentario ...

Me gustó la solución de Kirk e iba a estafarlo descaradamente y usarlo en mi código, pero cuando lo examiné me di cuenta de que era demasiado complicado. Conmutación y bucle innecesarios, y un constructor público que no tiene sentido usar.

Aquí está mi reescritura:

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

Uso1, más o menos lo mismo:

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, similar:

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
fuente
1

En mi caso, es necesario calcular el mes completo desde la fecha de inicio hasta el día anterior a este día en el próximo mes o desde el inicio hasta el final del mes.


Ej: del 1/1/2018 al 31/1/2018 es un mes completo
Ex2: del 5/1/2018 al 4/2/2018 es un mes completo

así que en base a esto, aquí está mi solución:

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: en mi caso fue necesario calcular los días restantes después de los meses completos, por lo que si no es su caso, podría ignorar el resultado de los días o incluso podría cambiar el método de devolución de tupla a entero.

Ahmed
fuente
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 solución es para el cálculo de alquiler / suscripción, donde la diferencia no significa ser una resta, está destinada a ser el intervalo dentro de esas dos fechas.

Sukanta
fuente
1

Hay 3 casos: mismo año, año anterior y otros años.

Si el día del mes no 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é
fuente
1

Escribí una función para lograr esto, porque las otras formas no funcionaban para mí.

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
fuente
Responda en inglés (frente a cualquier idioma inventado ...)
kleopatra
¿Por qué no simplemente startDate.AddMonths (monthCount) .ToShortDateString ()? ¡Esto no responde la pregunta original que se hizo de todos modos!
TabbyCool
oh, lo siento @TabbyCool, ¡este código funciona bien en mi programa! la regla de los programadores dice: ¡primero funciona el código y luego la optimización! tanx para su comentario :)
reza akhlaghi
1

Mi comprensión de la diferencia total de meses entre 2 fechas tiene una parte integral y una fracción (la fecha importa).

La parte integral es la diferencia de meses completos.

La parte fraccional, para mí, es la diferencia del% del día (a los días completos del mes) entre los meses de inicio y finalización.

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

Con esta extensión, esos son los 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
fuente
1

No hay muchas respuestas claras sobre esto porque siempre estás asumiendo cosas.

Esta solución calcula entre dos fechas los meses transcurridos entre la suposición de que desea guardar el día del mes para la comparación (lo que significa que el día del mes se considera en el cálculo)

Ejemplo, si tiene una fecha del 30 de enero de 2012, el 29 de febrero de 2012 no será un mes, pero el 01 de marzo de 2013 sí.

Ha sido probado a fondo, probablemente lo limpiará más tarde a medida que lo usemos, pero aquí:

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
fuente
1

Basado en el excelente trabajo de DateTimeSpan realizado anteriormente, he normalizado un poco el código; Esto parece funcionar bastante bien:

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
fuente
Al comparar con CompareDates(x, y)dónde x={01/02/2019 00:00:00}y y={01/05/2020 00:00:00}luego Monthsme da2
Bassie
1

Esta simple función estática calcula la fracción de meses entre dos fechas, por ejemplo

  • 1.1. a 31.1. = 1.0
  • 1.4. a 15.4. = 0.5
  • 16.4 a 30.4. = 0.5
  • 1.3. a 1.4. = 1 + 1/30

La función supone que la primera fecha es más pequeña que la segunda fecha. Para lidiar con intervalos de tiempo negativos, se puede modificar la función fácilmente mediante la introducción de un signo y un intercambio variable al principio.

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);
 }
Miguel
fuente
0

Poder calcular la diferencia entre 2 fechas en meses es algo perfectamente lógico y es necesario en muchas aplicaciones comerciales. Los varios codificadores aquí que han proporcionado comentarios tales como: ¿cuál es la diferencia en meses entre el "1 de mayo de 2010" y el "16 de junio de 2010, cuál es la diferencia en los meses entre el 31 de diciembre de 2010 y el 1 de enero de 2011? Los conceptos básicos de las aplicaciones empresariales.

Aquí está la respuesta a los 2 comentarios anteriores: el número de meses entre el 1 de mayo de 2010 y el 16 de junio de 2010 es de 1 mes, el número de meses entre el 31 de diciembre de 2010 y el 1 de enero de 2011 es 0. Es sería muy tonto calcularlos como 1,5 meses y 1 segundo, como lo han sugerido los codificadores anteriores.

Las personas que han trabajado en tarjetas de crédito, procesamiento de hipotecas, procesamiento de impuestos, procesamiento de alquileres, cálculos de intereses mensuales y una gran variedad de otras soluciones comerciales estarían de acuerdo.

El problema es que dicha función no está incluida en C # o VB.NET para el caso. Datediff solo tiene en cuenta los años o el componente del mes, por lo que en realidad es inútil.

Aquí hay algunos ejemplos de la vida real de dónde necesita y puede calcular correctamente los meses:

Viviste en un alquiler a corto plazo del 18 de febrero al 23 de agosto. ¿Cuántos meses estuviste allí? La respuesta es simple: 6 meses

Tiene una cuenta bancaria donde se calculan y pagan los intereses al final de cada mes. Usted deposita dinero el 10 de junio y lo saca el 29 de octubre (mismo año). ¿Por cuántos meses le interesan? Respuesta muy simple: 4 meses (de nuevo, los días adicionales no importan)

En las aplicaciones comerciales, la mayoría de las veces, cuando necesita calcular meses, es porque necesita saber meses 'completos' en función de cómo los humanos calculan el tiempo; no basado en algunos pensamientos abstractos / irrelevantes.

Tom
fuente
55
Esta es una de las razones por las cuales la contabilidad no es matemática. En contabilidad, el resultado depende de la forma en que lo calcules. Conozco tus puntos y sé la "visión comercial común" sobre esto, pero esta explicación es claramente errónea. Entre 2012.11.30 y 2012.12.01 hay 0, o 1/30, o 1/31, o 1 o 2 meses, dependiendo de lo que solicitó . ¿Las fechas fueron exclusivas o inclusivas? ¿Solicitó el número de meses cruzados, tocados o pasados? ¿Quería redondear, redondear o exacto?
quetzalcoatl
3
Ahora explíquele a un hombre de negocios o un contador y le darán una mirada perpleja. Siempre es "tan obvio para ellos que, por supuesto, se referían a X e Y y Z, ¿cómo podría pensar de manera diferente?" Ahora consiga a varias personas de negocios e intente que se pongan de acuerdo sobre el tema. Es más probable que los contadores estén de acuerdo, porque en algún momento usarán las matemáticas para verificar con qué opciones podrían resumir accidentalmente el mismo período dos veces, etc. Incluso sus ejemplos de cálculos son discutibles y dependen de la región, o simplemente inválidos como suponen. reglas comerciales adicionales como ignorar días adicionales.
quetzalcoatl
2
-1 Está asumiendo que todo el software es una "aplicación comercial". El propósito del código en cuestión no se menciona. También asume que todas las "aplicaciones comerciales" tienen las mismas reglas, lo que definitivamente no es cierto.
Jesse Webb
0

Estructura de Kirks expandida con ToString (formato) y Duración (ms largos)

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