calcular la diferencia en meses entre dos fechas

128

En C # /. NET TimeSpantiene TotalDays, TotalMinutesetc., pero no puedo encontrar una fórmula para la diferencia total de meses. Los días variables por mes y los años bisiestos no dejan de sorprenderme. ¿Cómo puedo obtener TotalMonths ?

Editar Perdón por no ser más claro: sé que en realidad no puedo obtener esto, TimeSpanpero pensé usarlo TotalDaysy TotalMinutessería un buen ejemplo para expresar lo que estaba buscando ... excepto que estoy tratando de obtener Meses totales.

Ejemplo: 25 de diciembre de 2009 - 6 de octubre de 2009 = 2 meses totales. Del 6 de octubre al 5 de noviembre es igual a 0 meses. El 6 de noviembre, 1 mes. El 6 de diciembre, 2 meses

Dinah
fuente
2
¿Qué esperas para el 25 de diciembre de 2009 - 6 de octubre de 2009?
Jeff Moser
2
¿Cómo define TimeSpan en meses?
Aliostad
1
@Aliostad: sin fechas, podría definir un mes como 30 días y ser bastante preciso.
ChaosPandion
Se fusionó con esta pregunta por un mod por alguna razón.
Jamiec
En realidad, necesitas leer mi publicación aquí, que responde a esta pregunta y proporciona una solución codificada, stackoverflow.com/questions/1916358/… ignora a los trolls (brianary) y presta atención a mi conversación a través de comentarios con supercat. Los meses que están al principio y al final de un período de tiempo que llamamos "Meses huérfanos", y la pregunta se reduce a cómo definir estos meses huérfanos en términos de días, una vez que haya determinado eso (y cómo quiere definirlo) ), el resto es solo código (que está incluido). Mi def. se basa en lo que creo que esperarán mis usuarios
Erx_VB.NExT.Coder

Respuestas:

222

No podrá obtener eso de a TimeSpan, porque un "mes" es una unidad de medida variable. Tendrás que calcularlo tú mismo, y tendrás que descubrir cómo quieres que funcione exactamente.

Por ejemplo, ¿deberían las fechas dar July 5, 2009y August 4, 2009producir una diferencia de un mes o cero meses? Si dice que debería producir uno, ¿qué pasa con July 31, 2009y August 1, 2009? ¿ Eso es un mes? ¿Es simplemente la diferencia de los Monthvalores para las fechas, o está más relacionado con un período de tiempo real? La lógica para determinar todas estas reglas no es trivial, por lo que tendrá que determinar las suyas propias e implementar el algoritmo adecuado.

Si todo lo que desea es simplemente una diferencia en los meses, sin tener en cuenta los valores de fecha, puede usar esto:

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

Tenga en cuenta que esto devuelve una diferencia relativa, lo que significa que si rValuees mayor que lValue, entonces el valor de retorno será negativo. Si quieres una diferencia absoluta, puedes usar esto:

public static int MonthDifference(this DateTime lValue, DateTime rValue)
{
    return Math.Abs((lValue.Month - rValue.Month) + 12 * (lValue.Year - rValue.Year));
}
Adam Robinson
fuente
@Dinah esto es solo una aproximación, si quieres saber la verdad. Mes y .Años - Acabo de publicar una respuesta para lo que puedes leer. Aunque, en lo que respecta a las aproximaciones, esta es una buena aproximación (apoyos a Adam Robinson), sin embargo, debe tener en cuenta que si usa alguna de estas aproximaciones, está mintiendo involuntariamente a sus usuarios.
Erx_VB.NExT.Coder
@ Erx_VB.NExT.Coder: Gracias por los accesorios, pero si bien su respuesta indica que ninguna de las respuestas toma en cuenta el hecho de que un mes es una unidad de medida variable, parece que la mayoría lo hace; simplemente no usan tu aproximación particular. Caso en cuestión, la primera oración en mi respuesta indica que es variable. Cualquier respuesta, incluida la suya, es una aproximación , simplemente porque no es una respuesta precisa. Su resultado de "2 meses" puede significar diferentes cosas para diferentes entradas, por lo que es una aproximación.
Adam Robinson
Sin embargo, el mío no es una aproximación, si hoy es el 14 de marzo, entonces los dos meses anteriores se calculan en función del hecho de que enero tenía 31 días en él y febrero ha transcurrido 29 días. ahora, tienes razón en que mi método no es la definición de un mes "general", ¡y el tuyo sí! Sin embargo, el mío solo se aplica si está informando cosas como "Este comentario fue publicado hace x meses y y días", la parte "AGO" hace la diferencia, porque se refiere a los x meses anteriores, esos x meses anteriores deben calcularse ¡según cuántos días estuvieron presentes en esos x meses! link ....
Erx_VB.NExT.Coder
¿Tiene sentido? así que si te refieres a meses particulares conocidos, entonces mi método es 100% exacto y serías una aproximación, sin embargo, si te refieres a un mes en general, tu aproximación sería una mejor idea, y la mía sería una mala idea (no está hecha para eso y no tendría sentido usarla). Aquí está el enlace a mi artículo que describe el problema y proporciona una solución: stackoverflow.com/questions/1916358/…
Erx_VB.NExT.Coder
2
Esta parece ser la misma lógica utilizada por la función DateDiff (mes, ...) del servidor SQL. También tiene la ventaja de ser extremadamente conciso y fácil de explicar y comprender. Lo explicaría de la siguiente manera ... ¿cuántas páginas en el calendario tendrías que pasar para pasar de una fecha a otra?
JoelFan
51

(Me doy cuenta de que esta es una vieja pregunta, pero ...)

Esto es relativamente doloroso de hacer en .NET puro. Recomiendo mi propia biblioteca Noda Time , que está especialmente diseñada para cosas como esta:

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

(Hay otras opciones, p. Ej., Si solo desea un recuento de meses, incluso entre años, lo usaría Period period = Period.Between(start, end, PeriodUnits.Months);)

Jon Skeet
fuente
Descargué su biblioteca y copié el código que escribió anteriormente, pero recibo un error de tiempo de compilación. Error 1 Operador '-' no se puede aplicar a operandos de tipo 'NodaTime.LocalDate' y 'NodaTime.LocalDate'. Conozco esta publicación de 5 años, ¿cambió algo desde ese momento, lo que hace que este código no funcione?
Hakan Fıstık
1
@HakamFostok: Lo siento, funcionará cuando se lance 2.0, pero hasta entonces debes usarlo Period.Between. He editado el código para que funcione con NodaTime 1.3.1.
Jon Skeet
muchas gracias, la biblioteca NodaTime hizo exactamente lo que quiero hacer. Quería calcular no solo los meses entre dos fechas, sino también los días restantes, y esto es lo que NodaTime ha hecho exactamente, gracias de nuevo.
Hakan Fıstık
1
@ JonSkeet Esa biblioteca tuya es realmente magia negra. Las fechas me muerden todo el tiempo. Ese fragmento de código me ahorró una gran cantidad de tiempo.
onefootswill
28

Tal vez no quieras saber sobre fracciones de mes; ¿Qué hay de este código?


public static class DateTimeExtensions
{
    public static int TotalMonths(this DateTime start, DateTime end)
    {
        return (start.Year * 12 + start.Month) - (end.Year * 12 + end.Month);
    }
}

//  Console.WriteLine(
//     DateTime.Now.TotalMonths(
//         DateTime.Now.AddMonths(-1))); // prints "1"


Rubens Farias
fuente
1
No entiendo el * 100. ¿Debería ser * 12?
Ruffles
9

Para comenzar, deberá definir qué quiere decir con TotalMonths.
Una definición simple pone un mes en 30.4 días (365.25 / 12).

Más allá de eso, cualquier definición que incluya fracciones parece inútil, y el valor entero más común (meses enteros entre fechas) también depende de reglas comerciales no estándar.

Henk Holterman
fuente
9

He escrito un método de extensión muy simple en DateTimey DateTimeOffsetpara hacer esto. Quería que funcionara exactamente como funcionaría una TotalMonthspropiedad TimeSpan: es decir, devolver el recuento de meses completos entre dos fechas, ignorando cualquier mes parcial. Porque se basa en DateTime.AddMonths()que respeta diferentes duraciones de mes y devuelve lo que un humano entendería como un período de meses.

(Desafortunadamente, no puede implementarlo como un método de extensión en TimeSpan porque eso no retiene el conocimiento de las fechas reales utilizadas, y durante meses son importantes).

El código y las pruebas están disponibles en GitHub . El código es muy simple:

public static int GetTotalMonthsFrom(this DateTime dt1, DateTime dt2)
{
    DateTime earlyDate = (dt1 > dt2) ? dt2.Date : dt1.Date;
    DateTime lateDate = (dt1 > dt2) ? dt1.Date : dt2.Date;

    // Start with 1 month's difference and keep incrementing
    // until we overshoot the late date
    int monthsDiff = 1;
    while (earlyDate.AddMonths(monthsDiff) <= lateDate)
    {
        monthsDiff++;
    }

    return monthsDiff - 1;
}

Y pasa todos estos casos de prueba de unidad:

// Simple comparison
Assert.AreEqual(1, new DateTime(2014, 1, 1).GetTotalMonthsFrom(new DateTime(2014, 2, 1)));
// Just under 1 month's diff
Assert.AreEqual(0, new DateTime(2014, 1, 1).GetTotalMonthsFrom(new DateTime(2014, 1, 31)));
// Just over 1 month's diff
Assert.AreEqual(1, new DateTime(2014, 1, 1).GetTotalMonthsFrom(new DateTime(2014, 2, 2)));
// 31 Jan to 28 Feb
Assert.AreEqual(1, new DateTime(2014, 1, 31).GetTotalMonthsFrom(new DateTime(2014, 2, 28)));
// Leap year 29 Feb to 29 Mar
Assert.AreEqual(1, new DateTime(2012, 2, 29).GetTotalMonthsFrom(new DateTime(2012, 3, 29)));
// Whole year minus a day
Assert.AreEqual(11, new DateTime(2012, 1, 1).GetTotalMonthsFrom(new DateTime(2012, 12, 31)));
// Whole year
Assert.AreEqual(12, new DateTime(2012, 1, 1).GetTotalMonthsFrom(new DateTime(2013, 1, 1)));
// 29 Feb (leap) to 28 Feb (non-leap)
Assert.AreEqual(12, new DateTime(2012, 2, 29).GetTotalMonthsFrom(new DateTime(2013, 2, 28)));
// 100 years
Assert.AreEqual(1200, new DateTime(2000, 1, 1).GetTotalMonthsFrom(new DateTime(2100, 1, 1)));
// Same date
Assert.AreEqual(0, new DateTime(2014, 8, 5).GetTotalMonthsFrom(new DateTime(2014, 8, 5)));
// Past date
Assert.AreEqual(6, new DateTime(2012, 1, 1).GetTotalMonthsFrom(new DateTime(2011, 6, 10)));
Mark Whitaker
fuente
3
Rústico, pero la mejor solución. Copias y pegadas. Gracias
Daniel Dolz
8

Necesita resolverlo usted mismo fuera de la fecha y hora. La forma en que lidie con los días de finalización dependerá de para qué quiera usarla.

Un método sería contar el mes y luego corregir por días al final. Algo como:

   DateTime start = new DateTime(2003, 12, 25);
   DateTime end = new DateTime(2009, 10, 6);
   int compMonth = (end.Month + end.Year * 12) - (start.Month + start.Year * 12);
   double daysInEndMonth = (end - end.AddMonths(1)).Days;
   double months = compMonth + (start.Day - end.Day) / daysInEndMonth;
JDunkerley
fuente
Buen código, aunque, 1 error: en su lugar: (como 28 de febrero + 1 mes == 28 de marzo) :-) // decimal daysInEndMonth = (end - end.AddMonths (1)). Days; Sugiero: decimal daysInEndMonth = DateTime.DaysInMonth (end.Year, end.Month) * -1;
bezieur
3

Lo haría así:

static int TotelMonthDifference(this DateTime dtThis, DateTime dtOther)
{
    int intReturn = 0;

    dtThis = dtThis.Date.AddDays(-(dtThis.Day-1));
    dtOther = dtOther.Date.AddDays(-(dtOther.Day-1));

    while (dtOther.Date > dtThis.Date)
    {
        intReturn++;     
        dtThis = dtThis.AddMonths(1);
    }

    return intReturn;
}
Maximiliano Mayerl
fuente
44
Eso es sin duda uno algoritmo, pero se podría simplificar enormemente areturn (dtOther.Month - dtThis.Month) + 12 * (dtOther.Year - dtThis.Year);
Adam Robinson
1
Dos problemas: está comenzando desde 2 fechas, no un intervalo de tiempo. En segundo lugar, calcula entre el 1 de ambos meses, esa es una definición muy cuestionable. Aunque podría ser correcto a veces.
Henk Holterman, el
@ Henk: Sí, por supuesto, eso no siempre es correcto, por eso dije que así es como lo haría, no cómo alguien debería hacerlo. El OP no especificó cómo se debe calcular el resultado. @ Adam: Vaya, pensé que era demasiado complicado una vez más ... eso me sucede con demasiada frecuencia. Gracias por el comentario, obviamente tienes razón, tu versión es mucho mejor. Usaré esto de ahora en adelante.
Maximilian Mayerl el
@ Adam: ¿por qué no envías esto como una respuesta real? Este es el más compacto hasta ahora. Muy hábil.
Dinah
@Dinah: No quería asumir que eso es lo que realmente querías. Si es así, he editado mi respuesta anterior para incluir este enfoque.
Adam Robinson el
3

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í.

Se ha probado a fondo, probablemente lo limpiará más tarde a medida que lo usemos, y toma dos fechas en lugar de un intervalo de tiempo, lo que probablemente sea mejor. Espero que esto ayude a alguien más.

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
3

La respuesta aceptada funciona perfectamente cuando quieres meses completos.

Necesitaba meses parciales. Esta es la solución que se me ocurrió durante meses parciales:

    /// <summary>
    /// Calculate the difference in months.
    /// This will round up to count partial months.
    /// </summary>
    /// <param name="lValue"></param>
    /// <param name="rValue"></param>
    /// <returns></returns>
    public static int MonthDifference(DateTime lValue, DateTime rValue)
    {
        var yearDifferenceInMonths = (lValue.Year - rValue.Year) * 12;
        var monthDifference = lValue.Month - rValue.Month;

        return yearDifferenceInMonths + monthDifference + 
            (lValue.Day > rValue.Day
                ? 1 : 0); // If end day is greater than start day, add 1 to round up the partial month
    }

También necesitaba un año de diferencia con la misma necesidad de años parciales. Aquí está la solución que se me ocurrió:

    /// <summary>
    /// Calculate the differences in years.
    /// This will round up to catch partial months.
    /// </summary>
    /// <param name="lValue"></param>
    /// <param name="rValue"></param>
    /// <returns></returns>
    public static int YearDifference(DateTime lValue, DateTime rValue)
    {
        return lValue.Year - rValue.Year +
               (lValue.Month > rValue.Month // Partial month, same year
                   ? 1
                   : ((lValue.Month = rValue.Month) 
                     && (lValue.Day > rValue.Day)) // Partial month, same year and month
                   ? 1 : 0);
    }
endyourif
fuente
Tuviste un error lógico en tu YearDifferencefunción cuando lValue.Month < rValue.Month, lo arreglé ahora, es posible que quieras revisar ...
Stobor
2

Antigua pregunta que conozco, pero podría ayudar a alguien. Utilicé la respuesta aceptada de @Adam arriba, pero luego verifiqué si la diferencia es 1 o -1 y luego verifiqué si es la diferencia de un mes calendario completo. Entonces, 21/07/55 y 20/08/55 no sería un mes completo, pero sí lo sería el 21/07/55 y el 21/07/55.

/// <summary>
/// Amended date of birth cannot be greater than or equal to one month either side of original date of birth.
/// </summary>
/// <param name="dateOfBirth">Date of birth user could have amended.</param>
/// <param name="originalDateOfBirth">Original date of birth to compare against.</param>
/// <returns></returns>
public JsonResult ValidateDateOfBirth(string dateOfBirth, string originalDateOfBirth)
{
    DateTime dob, originalDob;
    bool isValid = false;

    if (DateTime.TryParse(dateOfBirth, out dob) && DateTime.TryParse(originalDateOfBirth, out originalDob))
    {
        int diff = ((dob.Month - originalDob.Month) + 12 * (dob.Year - originalDob.Year));

        switch (diff)
        {
            case 0:
                // We're on the same month, so ok.
                isValid = true;
                break;
            case -1:
                // The month is the previous month, so check if the date makes it a calendar month out.
                isValid = (dob.Day > originalDob.Day);
                break;
            case 1:
                // The month is the next month, so check if the date makes it a calendar month out.
                isValid = (dob.Day < originalDob.Day);
                break;
            default:
                // Either zero or greater than 1 month difference, so not ok.
                isValid = false;
                break;
        }
        if (!isValid)
            return Json("Date of Birth cannot be greater than one month either side of the date we hold.", JsonRequestBehavior.AllowGet);
    }
    else
    {
        return Json("Date of Birth is invalid.", JsonRequestBehavior.AllowGet);
    }
    return Json(true, JsonRequestBehavior.AllowGet);
}
nrg
fuente
2
case IntervalType.Month:
    returnValue = start.AddMonths(-end.Month).Month.ToString();
    break;
case IntervalType.Year:
    returnValue = (start.Year - end.Year).ToString();
    break;
Everton
fuente
2
Una descripción que vaya con el código también sería beneficiosa para otros lectores.
Boeckm el
Sí, por favor agrega algún comentario.
Amar
1

El problema con los meses es que no es realmente una medida simple, no son de tamaño constante. Tendría que definir sus reglas para lo que desea incluir y trabajar desde allí. Por ejemplo, del 1 de enero al 1 de febrero: podría argumentar que hay 2 meses involucrados allí, o podría decir que es un mes. Entonces, ¿qué hay del "1 de enero a las 20:00" hasta el "1 de febrero a las 00:00"? Eso no es todo un mes completo. ¿Eso es 0? 1? ¿Qué pasa al revés (1 enero 00:00 a 1 febrero 20:00) ... 1? 2?

Primero defina las reglas, luego tendrá que codificarlo usted mismo, me temo ...

Marc Gravell
fuente
1

Si quieres tener un resultado 1entre 28th Feby 1st March:

DateTime date1, date2;
int monthSpan = (date2.Year - date1.Year) * 12 + date2.Month - date1.Month
Snowbear
fuente
Esta parece ser la misma lógica utilizada por la función DateDiff (mes, ...) del servidor SQL. También tiene la ventaja de ser extremadamente conciso y fácil de explicar y comprender. Lo explicaría de la siguiente manera ... ¿cuántas páginas en el calendario tendrías que pasar para pasar de una fecha a otra?
JoelFan
1

Esta biblioteca calcula la diferencia de meses, considerando todas las partes de DateTime:

// ----------------------------------------------------------------------
public void DateDiffSample()
{
  DateTime date1 = new DateTime( 2009, 11, 8, 7, 13, 59 );
  Console.WriteLine( "Date1: {0}", date1 );
  // > Date1: 08.11.2009 07:13:59
  DateTime date2 = new DateTime( 2011, 3, 20, 19, 55, 28 );
  Console.WriteLine( "Date2: {0}", date2 );
  // > Date2: 20.03.2011 19:55:28

  DateDiff dateDiff = new DateDiff( date1, date2 );

  // differences
  Console.WriteLine( "DateDiff.Years: {0}", dateDiff.Years );
  // > DateDiff.Years: 1
  Console.WriteLine( "DateDiff.Quarters: {0}", dateDiff.Quarters );
  // > DateDiff.Quarters: 5
  Console.WriteLine( "DateDiff.Months: {0}", dateDiff.Months );
  // > DateDiff.Months: 16
  Console.WriteLine( "DateDiff.Weeks: {0}", dateDiff.Weeks );
  // > DateDiff.Weeks: 70
  Console.WriteLine( "DateDiff.Days: {0}", dateDiff.Days );
  // > DateDiff.Days: 497
  Console.WriteLine( "DateDiff.Weekdays: {0}", dateDiff.Weekdays );
  // > DateDiff.Weekdays: 71
  Console.WriteLine( "DateDiff.Hours: {0}", dateDiff.Hours );
  // > DateDiff.Hours: 11940
  Console.WriteLine( "DateDiff.Minutes: {0}", dateDiff.Minutes );
  // > DateDiff.Minutes: 716441
  Console.WriteLine( "DateDiff.Seconds: {0}", dateDiff.Seconds );
  // > DateDiff.Seconds: 42986489

  // elapsed
  Console.WriteLine( "DateDiff.ElapsedYears: {0}", dateDiff.ElapsedYears );
  // > DateDiff.ElapsedYears: 1
  Console.WriteLine( "DateDiff.ElapsedMonths: {0}", dateDiff.ElapsedMonths );
  // > DateDiff.ElapsedMonths: 4
  Console.WriteLine( "DateDiff.ElapsedDays: {0}", dateDiff.ElapsedDays );
  // > DateDiff.ElapsedDays: 12
  Console.WriteLine( "DateDiff.ElapsedHours: {0}", dateDiff.ElapsedHours );
  // > DateDiff.ElapsedHours: 12
  Console.WriteLine( "DateDiff.ElapsedMinutes: {0}", dateDiff.ElapsedMinutes );
  // > DateDiff.ElapsedMinutes: 41
  Console.WriteLine( "DateDiff.ElapsedSeconds: {0}", dateDiff.ElapsedSeconds );
  // > DateDiff.ElapsedSeconds: 29
} // DateDiffSample

fuente
1

A continuación se muestra la forma más precisa de hacerlo, ya que la definición de "1 mes" cambia según el mes que sea, ¡y ninguna de las otras respuestas tiene esto en cuenta! Si desea obtener más información sobre el problema que no está integrado en el marco, puede leer esta publicación: Un objeto de intervalo de tiempo real con .Años y meses (sin embargo, leer esa publicación no es necesario para comprender y usar la función a continuación, funciona al 100%, sin las inexactitudes inherentes de la aproximación que a otros les encanta usar, y siéntase libre de reemplazar la función .ReverseIt con la función incorporada .Reverse que puede tener en su marco (está aquí para completar).

Tenga en cuenta que puede obtener cualquier número de fechas / horas de precisión, segundos y minutos, o segundos, minutos y días, en cualquier lugar hasta años (que contendría 6 partes / segmentos). Si especifica los dos primeros y tiene más de un año, devolverá "hace 1 año y 3 meses" y no devolverá el resto porque ha solicitado dos segmentos. si solo tiene unas pocas horas, solo devolverá "hace 2 horas y 1 minuto". Por supuesto, se aplican las mismas reglas si especifica 1, 2, 3, 4, 5 o 6 segmentos (máximo a 6 porque segundos, minutos, horas, días, meses, años solo hacen 6 tipos). También corregirá problemas de gramática como "minutos" vs "minuto" dependiendo de si es 1 minuto o más, lo mismo para todos los tipos, y la "cadena" generada siempre será gramaticalmente correcta.

Estos son algunos ejemplos de uso: bAllowSegments identifica cuántos segmentos mostrar ... es decir: si es 3, entonces la cadena de retorno sería (como ejemplo) ... "3 years, 2 months and 13 days"(no incluirá horas, minutos y segundos como los 3 primeros tiempos se devuelven las categorías), si, sin embargo, la fecha era una fecha más reciente, como algo hace unos días, especificando que los mismos segmentos (3) volverán en su "4 days, 1 hour and 13 minutes ago"lugar, ¡así que tiene todo en cuenta!

si bAllowSegments es 2, volvería "3 years and 2 months"y si volvería 6 (valor máximo) "3 years, 2 months, 13 days, 13 hours, 29 minutes and 9 seconds", pero recuerde que será NEVER RETURNalgo así, "0 years, 0 months, 0 days, 3 hours, 2 minutes and 13 seconds ago"ya que entiende que no hay datos de fecha en los 3 segmentos principales y los ignora, incluso si especifica 6 segmentos , así que no te preocupes :). Por supuesto, si hay un segmento con 0, lo tendrá en cuenta al formar la cadena, y se mostrará "3 days and 4 seconds ago"e ignorará la parte "0 horas". Disfruta y comenta si quieres.

 Public Function RealTimeUntilNow(ByVal dt As DateTime, Optional ByVal bAllowSegments As Byte = 2) As String
  ' bAllowSegments identifies how many segments to show... ie: if 3, then return string would be (as an example)...
  ' "3 years, 2 months and 13 days" the top 3 time categories are returned, if bAllowSegments is 2 it would return
  ' "3 years and 2 months" and if 6 (maximum value) would return "3 years, 2 months, 13 days, 13 hours, 29 minutes and 9 seconds"
  Dim rYears, rMonths, rDays, rHours, rMinutes, rSeconds As Int16
  Dim dtNow = DateTime.Now
  Dim daysInBaseMonth = Date.DaysInMonth(dt.Year, dt.Month)

  rYears = dtNow.Year - dt.Year
  rMonths = dtNow.Month - dt.Month
  If rMonths < 0 Then rMonths += 12 : rYears -= 1 ' add 1 year to months, and remove 1 year from years.
  rDays = dtNow.Day - dt.Day
  If rDays < 0 Then rDays += daysInBaseMonth : rMonths -= 1
  rHours = dtNow.Hour - dt.Hour
  If rHours < 0 Then rHours += 24 : rDays -= 1
  rMinutes = dtNow.Minute - dt.Minute
  If rMinutes < 0 Then rMinutes += 60 : rHours -= 1
  rSeconds = dtNow.Second - dt.Second
  If rSeconds < 0 Then rSeconds += 60 : rMinutes -= 1

  ' this is the display functionality
  Dim sb As StringBuilder = New StringBuilder()
  Dim iSegmentsAdded As Int16 = 0

  If rYears > 0 Then sb.Append(rYears) : sb.Append(" year" & If(rYears <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rMonths > 0 Then sb.AppendFormat(rMonths) : sb.Append(" month" & If(rMonths <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rDays > 0 Then sb.Append(rDays) : sb.Append(" day" & If(rDays <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rHours > 0 Then sb.Append(rHours) : sb.Append(" hour" & If(rHours <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rMinutes > 0 Then sb.Append(rMinutes) : sb.Append(" minute" & If(rMinutes <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rSeconds > 0 Then sb.Append(rSeconds) : sb.Append(" second" & If(rSeconds <> 1, "s", "") & "") : iSegmentsAdded += 1

parseAndReturn:

  ' if the string is entirely empty, that means it was just posted so its less than a second ago, and an empty string getting passed will cause an error
  ' so we construct our own meaningful string which will still fit into the "Posted * ago " syntax...

  If sb.ToString = "" Then sb.Append("less than 1 second")

  Return ReplaceLast(sb.ToString.TrimEnd(" ", ",").ToString, ",", " and")

 End Function

Por supuesto, necesitará una función "Reemplazar por última vez", que toma una cadena de origen y un argumento que especifica lo que necesita ser reemplazado, y otro argumento que especifica con qué desea reemplazarlo, y solo reemplaza la última aparición de esa cadena ... He incluido el mío si no tiene uno o no desea implementarlo, por lo que aquí está, funcionará "tal cual" sin necesidad de modificaciones. Sé que la función reverseit ya no es necesaria (existe en .net) pero la función ReplaceLast y ReverseIt se transfieren de los días anteriores a.net, así que disculpe qué tan anticuada puede verse (todavía funciona al 100% aunque em durante más de diez años, puedo garantizar que están libres de errores) ... :). salud.

<Extension()> _ 
Public Function ReplaceLast(ByVal sReplacable As String, ByVal sReplaceWhat As String, ByVal sReplaceWith As String) As String 
    ' let empty string arguments run, incase we dont know if we are sending and empty string or not. 
    sReplacable = sReplacable.ReverseIt 
    sReplacable = Replace(sReplacable, sReplaceWhat.ReverseIt, sReplaceWith.ReverseIt, , 1) ' only does first item on reversed version! 
    Return sReplacable.ReverseIt.ToString 
End Function 

<Extension()> _ 
Public Function ReverseIt(ByVal strS As String, Optional ByVal n As Integer = -1) As String 
    Dim strTempX As String = "", intI As Integer 

    If n > strS.Length Or n = -1 Then n = strS.Length 

    For intI = n To 1 Step -1 
        strTempX = strTempX + Mid(strS, intI, 1) 
    Next intI 

    ReverseIt = strTempX + Right(strS, Len(strS) - n) 

End Function 
Erx_VB.NExT.Coder
fuente
0

Si desea el número exacto, no puede hacerlo solo con el intervalo de tiempo, ya que necesita saber qué meses está tratando y si está tratando con un año bisiesto, como dijo.

Vaya por un número aproximado, o haga un poco de inquietud con los DateTimes originales.

Rik
fuente
0

No hay una forma integrada de hacer esto con precisión en idiomatic-c #. Sin embargo , hay algunas soluciones, como este ejemplo de CodeProject que la gente ha codificado.

Mate
fuente
0

Si se trata de meses y años, necesita algo que sepa cuántos días tiene cada mes y qué años son bisiestos.

Ingrese el calendario gregoriano (y otras implementaciones de calendario específicas de la cultura ).

Si bien Calendar no proporciona métodos para calcular directamente la diferencia entre dos puntos en el tiempo, sí tiene métodos como

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

El método devuelve una lista que contiene 3 elementos: primero es año, segundo es mes y el elemento final es día:

public static List<int> GetDurationInEnglish(DateTime from, DateTime to)
    {
        try
        {
            if (from > to)
                return null;

            var fY = from.Year;
            var fM = from.Month;
            var fD = DateTime.DaysInMonth(fY, fM);

            var tY = to.Year;
            var tM = to.Month;
            var tD = DateTime.DaysInMonth(tY, tM);

            int dY = 0;
            int dM = 0;
            int dD = 0;

            if (fD > tD)
            {
                tM--;

                if (tM <= 0)
                {
                    tY--;
                    tM = 12;
                    tD += DateTime.DaysInMonth(tY, tM);
                }
                else
                {
                    tD += DateTime.DaysInMonth(tY, tM);
                }
            }
            dD = tD - fD;

            if (fM > tM)
            {
                tY--;

                tM += 12;
            }
            dM = tM - fM;

            dY = tY - fY;

            return new List<int>() { dY, dM, dD };
        }
        catch (Exception exception)
        {
            //todo: log exception with parameters in db

            return null;
        }
    }
Alireza
fuente
0

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 anteriormente y AddYears en lugar de AddMonths en el ciclo while.

Morgs
fuente
0

Muy tarde al juego, pero imagino que esto puede ser útil para alguien. La mayoría de las personas tienden a medir mes a mes por fecha, excluyendo el hecho de que los meses vienen en diferentes variaciones. Usando ese marco de pensamiento, creé un trazador de líneas que compara las fechas para nosotros. Usando el siguiente proceso.

  1. Cualquier número de años de más de 1 cuando se compara el año se multiplicará por 12, no hay ningún caso en el que esto sea igual a menos de 1 año completo.
  2. Si el año final es mayor, debemos evaluar si el día actual es mayor o igual que el día anterior 2A. Si el día final es mayor o igual, tomamos el mes actual y luego sumamos 12 meses, restamos el mes del mes inicial 2B. Si el día final es menor que el día de inicio, realizamos lo mismo que antes, excepto que agregamos 1 al mes de inicio antes de restar
  3. Si el año final no es mayor, realizamos lo mismo que 2A / 2B, pero sin agregar los 12 meses porque no necesitamos evaluar durante todo el año.

        DateTime date = new DateTime(2003, 11, 25);
        DateTime today = new DateTime(2004, 12, 26);
        var time = (today.Year - date.Year > 1 ? (today.Year - date.Year - 1) * 12 : 0) +  (today.Year > date.Year ? (today.Day >= date.Day ? today.Month + 12 - date.Month : today.Month + 12 - (date.Month + 1)) : (today.Day >= date.Day ? today.Month - date.Month : today.Month - (date.Month + 1)));
El tendón de la corva
fuente
¿Muerte por ternario?
SpaceBison
0

Mi opinión sobre esta respuesta también utiliza un método de extensión , pero puede devolver un resultado positivo o negativo.

public static int MonthsBefore(this DateTime dt1, DateTime dt2)
{
    (DateTime early, DateTime late, bool dt2After) = dt2 > dt1 ? (dt1,dt2,true) : (dt2,dt1,false);
    DateTime tmp; // Save the result so we don't repeat work
    int months = 1;
    while ((tmp = early.AddMonths(1)) <= late)
    {
        early = tmp;
        months++;
    }
    return (months-1)*(dt2After ? 1 : -1);
}

Un par de pruebas:

// Just under 1 month's diff
Assert.AreEqual(0, new DateTime(2014, 1, 1).MonthsBefore(new DateTime(2014, 1, 31)));
// Just over 1 month's diff
Assert.AreEqual(1, new DateTime(2014, 1, 1).MonthsBefore(new DateTime(2014, 2, 2)));    
// Past date returns NEGATIVE
Assert.AreEqual(-6, new DateTime(2012, 1, 1).MonthsBefore(new DateTime(2011, 6, 10)));
ZX9
fuente
0

Combinando dos de las respuestas anteriores, otro método de extensión es:

public static int ElapsedMonths(this DateTime date1, DateTime date2)
{
    DateTime earlierDate = (date1 > date2) ? date2 : date1;
    DateTime laterDate = (date1 > date2) ? date1 : date2;
    var eMonths = (laterDate.Month - earlierDate.Month) + 12 * (laterDate.Year - earlierDate.Year) - 
                                            ((earlierDate.Day > laterDate.Day) ? 1 : 0);
    return eMonths;
}

Gracias a @AdamRobinson y @MarkWhittaker

Peter Smith
fuente
-1

Calcular no de meses entre 2 fechas:

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

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

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

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

echo $joining_months = (($year2 - $year1) * 12) + ($month2 - $month1);
Kamlesh
fuente
1
Esto es PHP, no C #.
AFract