2010-07-20 11 views
7

Ejemplo: dado dos fechas a continuación, acabado es siempre mayor o igual a empezar¿Cómo calcular la diferencia real de meses (año calendario no aproximado) entre dos fechas determinadas en C#?

inicio = 2001 Ene 01

acabado = 2002 Mar 15

Así que desde 2001 01 de enero hasta el final de 2002 Feb

meses = 12 + 2 = 14

Para 2002 Marzo

15/30 = 0,5

tan gran total es 14,5 diferencia meses.

Es muy fácil trabajar a mano, pero ¿cómo puedo codificarlo elegantemente? Por el momento tengo la combinación de mucho más o menos para lograr lo que quiero, pero creo que hay soluciones más simples por ahí.

Actualización: la salida tiene que ser precisa (no aproximación), por ejemplo: si inicio 2001 Ene 01 y terminar 2001 16-abr-La salida debe ser 1 + 1 + 1 = 3 (para Jan, Feb y Mar) y 16/31 = 0.516 meses, por lo que el total es 3.516.

Otro ejemplo sería si comienzo en 2001 5, Jul 2002 y terminará el 10 de Jul, la salida debe ser 11 meses hasta finales de junio de 2002, y (31-5)/31 = 0,839 y 10/31 = 0.323 meses, por lo que el total es 11 + 0.839 + 0.323 = 12.162.

extendí 'código de s y Hightechrider' Josh Stodola código de s:

public static decimal GetMonthsInRange(this IDateRange thisDateRange) 
{ 
    var start = thisDateRange.Start; 
    var finish = thisDateRange.Finish; 

    var monthsApart = Math.Abs(12*(start.Year - finish.Year) + start.Month - finish.Month) - 1; 

    decimal daysInStartMonth = DateTime.DaysInMonth(start.Year, start.Month); 
    decimal daysInFinishMonth = DateTime.DaysInMonth(finish.Year, finish.Month); 

    var daysApartInStartMonth = (daysInStartMonth - start.Day + 1)/daysInStartMonth; 
    var daysApartInFinishMonth = finish.Day/daysInFinishMonth; 

    return monthsApart + daysApartInStartMonth + daysApartInFinishMonth; 
} 
+0

Gracias a aquellos de ustedes que ofrecieron la solución de aproximación, pero necesito una forma elegante de calcular la diferencia de meses por mes/año/día. Es por eso que mi solución actual contiene if else para cada ciclo while. – Jeff

+0

Tus cálculos parecen suponer que el final incluye todo ese día. ¿Es eso lo que realmente quieres? El 15 de marzo como valor 'DateTime' tiene una hora de medianoche, lo que significa que han pasado 14 días en marzo fuera de la posible 31. –

+0

@Hightechrider, el día de inicio es siempre el 00: 00: 00: 1 milisegundo, y el terminar el día siempre es 23: 59: 59: 999. entonces, si el inicio y el final son el mismo día, se cuentan como 1 día. – Jeff

Respuesta

8

He dado una respuesta int antes, y luego me di cuenta de lo que pidió una respuesta más precisa. Estaba cansado, así que lo borré y me fui a la cama. ¡Mucho por eso, no pude conciliar el sueño! Por alguna razón, esta pregunta realmente me molestó, y tuve que resolverlo. Así que aquí tienes ...

static void Main(string[] args) 
{ 
    decimal diff; 

    diff = monthDifference(new DateTime(2001, 1, 1), new DateTime(2002, 3, 15)); 
    Console.WriteLine(diff.ToString("n2")); //14.45 

    diff = monthDifference(new DateTime(2001, 1, 1), new DateTime(2001, 4, 16)); 
    Console.WriteLine(diff.ToString("n2")); //3.50 

    diff = monthDifference(new DateTime(2001, 7, 5), new DateTime(2002, 7, 10)); 
    Console.WriteLine(diff.ToString("n2")); //12.16 

    Console.Read(); 
} 

static decimal monthDifference(DateTime d1, DateTime d2) 
{ 
    if (d1 > d2) 
    { 
     DateTime hold = d1; 
     d1 = d2; 
     d2 = hold; 
    } 

    int monthsApart = Math.Abs(12 * (d1.Year-d2.Year) + d1.Month - d2.Month) - 1; 
    decimal daysInMonth1 = DateTime.DaysInMonth(d1.Year, d1.Month); 
    decimal daysInMonth2 = DateTime.DaysInMonth(d2.Year, d2.Month); 

    decimal dayPercentage = ((daysInMonth1 - d1.Day)/daysInMonth1) 
          + (d2.Day/daysInMonth2); 
    return monthsApart + dayPercentage; 
} 

Ahora tendré dulces sueños. Goodnight :)

+0

gracias por su respuesta. Inicialmente parece perfecto hasta que realicé una serie de pruebas. Noté que asumiste que d1 siempre es un mes calendario completo. Por lo tanto, esto no funcionará en casos como: comience el 2001 15 de abril y finalice el 2001 15 de junio. Su código devuelve 2.5, pero de hecho son 2 meses y 1 meses completos, lo que debería devolver 2 meses de diferencia. – Jeff

+0

No pude encontrar ninguna solución "simple", así que completé todo el ciclo mes a mes desde el mes de inicio hasta el final y resuelvo cada mes si es un mes completo o parcial, luego suman el total. Pero no sé si existe alguna solución más simple. Pero aún no funcionará para casos como el 10 de julio de 2001 y el 9 de julio de 2002, de hecho, debería regresar 12 meses, pero el código devuelve 11 puntos, así que tuve que tratar esos casos individualmente. Muy doloroso. – Jeff

+0

@Jeffrey Su fórmula es bastante única –

1

Una forma de hacer esto es que se verá todo un poco es:

private static int monthDifference(DateTime startDate, DateTime endDate) 
{ 
    int monthsApart = 12 * (startDate.Year - endDate.Year) + startDate.Month - endDate.Month; 
    return Math.Abs(monthsApart); 
} 

Sin embargo, quiere "meses parciales" que esto no da. Pero, ¿qué sentido tiene comparar manzanas (enero/marzo/mayo/julio/agosto/octubre/diciembre) con naranjas (abril/junio/septiembre/noviembre) o incluso plátanos que a veces son cocos (febrero)?

An alternative es importar Microsoft.VisualBasic y hacer esto:

DateTime FromDate; 
    DateTime ToDate; 
    FromDate = DateTime.Parse("2001 Jan 01"); 
    ToDate = DateTime.Parse("2002 Mar 15"); 

    string s = DateAndTime.DateDiff (DateInterval.Month, FromDate,ToDate, FirstDayOfWeek.System, FirstWeekOfYear.System).ToString(); 

Sin embargo una vez más:

El valor de retorno para DateInterval.Mes se calcula sólo a partir de las piezas de año y mes de los argumentos

[Source]

-1

Esto debería llegar a donde tiene que ir:

DateTime start = new DateTime(2001, 1, 1); 
DateTime finish = new DateTime(2002, 3, 15); 
double diff = (finish - start).TotalDays/30; 
+0

Esto no considera la variación de días en cada mes. –

1

Dependiendo de cómo es exactamente lo que quiere que su lógica para trabajar, esto al menos le daría una aproximación decente:

// 365 days per year + 1 day per leap year = 1461 days every 4 years 
// But years divisible by 100 are not leap years 
// So 1461 days every 4 years - 1 day per 100th year = 36524 days every 100 years 
// 12 months per year = 1200 months every 100 years 
const double DaysPerMonth = 36524.0/1200.0; 

double GetMonthsDifference(DateTime start, DateTime finish) 
{ 
    double days = (finish - start).TotalDays; 
    return days/DaysPerMonth; 
} 
+0

Es matemáticamente inteligente, pero no creo que funcione de manera realista porque todavía son suposiciones y aproximaciones. – Jeff

+0

Jeffrey tiene razón. Quiere que la diferencia entre el 1 de febrero y el 1 de marzo sea exactamente de 1 mes, pero la llama menos de 1 mes. – Gabe

+0

@Jeffrey: Por eso dije "aproximación decente" (Publiqué esta respuesta antes de aclarar exactamente qué quería que fuera su resultado). Hubiera sido una buena opción si quisiera, por ejemplo, redondear el resultado a los 0.5 meses más cercanos. Actualizaría mi sugerencia, pero parece que ahora tienes muchas otras ideas. –

-1

el marco como un objeto TimeSpan que es el resultado de restar dos fechas.

la sustracción ya está considerando la opción de varios de febrero (28/29 días al mes) por lo que en mi opinión esta es la mejor práctica después de que lo tienes puede dar formato a la forma que más le guste

 DateTime dates1 = new DateTime(2010, 1, 1); 
     DateTime dates2 = new DateTime(2010, 3, 15); 
     var span = dates1.Subtract(dates2); 
     span.ToString("your format here"); 
+0

El número de días en el mes es necesario para calcular meses parciales. Una vez que las fechas se restan a un TimeSpan, se pierde la cantidad de días del mes. –

2

Lo que queremos es probablemente algo parecido a esto ... que sigue más o menos su explicación en cuanto a cómo calcularlo:

var startofd1 = d1.AddDays(-d1.Day + 1); 
var startOfNextMonthAfterd1 = startofd1.AddMonths(1);  // back to start of month and then to next month 
int daysInFirstMonth = (startOfNextMonthAfterd1 - startofd1).Days; 
double fraction1 = (double)(daysInFirstMonth - (d1.Day - 1))/daysInFirstMonth;  // fractional part of first month remaining 

var startofd2 = d2.AddDays(-d2.Day + 1); 
var startOfNextMonthAfterd2 = startofd2.AddMonths(1);  // back to start of month and then to next month 
int daysInFinalMonth = (startOfNextMonthAfterd2 - startofd2).Days; 
double fraction2 = (double)(d2.Day - 1)/daysInFinalMonth;  // fractional part of last month 

// now find whole months in between 
int monthsInBetween = (startofd2.Year - startOfNextMonthAfterd1.Year) * 12 + (startofd2.Month - startOfNextMonthAfterd1.Month); 

return monthsInBetween + fraction1 + fraction2; 

NB Esto no se ha probado muy bien, pero se nota cómo manejar problemas como este encontrando fechas bien conocidas al principio de los meses alrededor de los valores del problema y luego trabajando en ellos.

Mientras bucles para los cálculos de fecha y hora son siempre una mala idea: ver http://www.zuneboards.com/forums/zune-news/38143-cause-zune-30-leapyear-problem-isolated.html

+0

¿Funcionaría esto si el inicio y el final son dentro del mismo mes? – Jeff

+0

Parece que en una breve prueba (monthsInBetween va a -1) ... encuentra un ejemplo donde no funciona y voy a echar un vistazo. Como dije "Esto no se ha probado muy bien", pero debería darle un buen comienzo en una forma no repetitiva de calcularlo con precisión. –

+0

Estás en lo correcto con el -1. Actualicé la pregunta para incluir el método correcto (creo). Ahora no estoy seguro de quién responder para aceptar. – Jeff

1

Sólo mejoró la respuesta de Josh

static decimal monthDifference(DateTime d1, DateTime d2) 
    { 
     if (d1 > d2) 
     { 
      DateTime hold = d1; 
      d1 = d2; 
      d2 = hold; 
     } 

     decimal monthsApart = Math.Abs((12 * (d1.Year - d2.Year)) + d2.Month - d1.Month - 1); 


     decimal daysinStartingMonth = DateTime.DaysInMonth(d1.Year, d1.Month); 
     monthsApart = monthsApart + (1-((d1.Day - 1)/daysinStartingMonth)); 

     // Replace (d1.Day - 1) with d1.Day incase you DONT want to have both inclusive difference. 



     decimal daysinEndingMonth = DateTime.DaysInMonth(d2.Year, d2.Month); 
     monthsApart = monthsApart + (d2.Day/daysinEndingMonth); 


     return monthsApart; 
    } 
-1
private Double GetTotalMonths(DateTime future, DateTime past) 
    { 
     Double totalMonths = 0.0; 

     while ((future - past).TotalDays > 28) 
     { 
      past = past.AddMonths(1); 
      totalMonths += 1; 
     } 

     var daysInCurrent = DateTime.DaysInMonth(future.Year, future.Month); 
     var remaining = future.Day - past.Day; 

     totalMonths += ((Double)remaining/(Double)daysInCurrent); 
     return totalMonths; 
    } 
0

La respuesta funciona perfectamente y mientras que la concisión del código hace que sea muy pequeño tuve que dividir todo en funciones más pequeñas con variables nombradas para que realmente pudiera entender lo que estaba pasando ... Así que, básicamente, acabo de mencionar el código de Josh Stodola y Hightechrider mencionado en el comentario de Jeff y lo hice más pequeño con comentarios explicando lo que estaba pasando y por qué se están realizando los cálculos, y es de esperar que esto puede ayudar a alguien más:

[Test] 
    public void Calculate_Total_Months_Difference_Between_Two_Dates() 
    { 
     var startDate = DateTime.Parse("10/8/1996"); 

     var finishDate = DateTime.Parse("9/8/2012"); // this should be now: 


     int numberOfMonthsBetweenStartAndFinishYears = getNumberOfMonthsBetweenStartAndFinishYears(startDate, finishDate); 


     int absMonthsApartMinusOne = getAbsMonthsApartMinusOne(startDate, finishDate, numberOfMonthsBetweenStartAndFinishYears); 


     decimal daysLeftToCompleteStartMonthPercentage = getDaysLeftToCompleteInStartMonthPercentage(startDate); 


     decimal daysCompletedSoFarInFinishMonthPercentage = getDaysCompletedSoFarInFinishMonthPercentage(finishDate); 

     // .77 + .26 = 1.04 
     decimal totalDaysDifferenceInStartAndFinishMonthsPercentage = daysLeftToCompleteStartMonthPercentage + daysCompletedSoFarInFinishMonthPercentage; 


     // 13 + 1.04 = 14.04 months difference. 
     decimal totalMonthsDifference = absMonthsApartMinusOne + totalDaysDifferenceInStartAndFinishMonthsPercentage; 

     //return totalMonths; 

    } 

    private static int getNumberOfMonthsBetweenStartAndFinishYears(DateTime startDate, DateTime finishDate) 
    { 
     int yearsApart = startDate.Year - finishDate.Year; 

     const int INT_TotalMonthsInAYear = 12; 

     // 12 * -1 = -12 
     int numberOfMonthsBetweenYears = INT_TotalMonthsInAYear * yearsApart; 

     return numberOfMonthsBetweenYears; 
    } 

    private static int getAbsMonthsApartMinusOne(DateTime startDate, DateTime finishDate, int numberOfMonthsBetweenStartAndFinishYears) 
    { 
     // This may be negative i.e. 7 - 9 = -2 
     int numberOfMonthsBetweenStartAndFinishMonths = startDate.Month - finishDate.Month; 

     // Absolute Value Of Total Months In Years Plus The Simple Months Difference Which May Be Negative So We Use Abs Function 
     int absDiffInMonths = Math.Abs(numberOfMonthsBetweenStartAndFinishYears + numberOfMonthsBetweenStartAndFinishMonths); 

     // Subtract one here because we are going to use a perecentage difference based on the number of days left in the start month 
     // and adding together the number of days that we've made it so far in the finish month. 
     int absMonthsApartMinusOne = absDiffInMonths - 1; 

     return absMonthsApartMinusOne; 
    } 

    /// <summary> 
    /// For example for 7/8/2012 there are 24 days left in the month so about .77 percentage of month is left. 
    /// </summary> 
    private static decimal getDaysLeftToCompleteInStartMonthPercentage(DateTime startDate) 
    { 
     // startDate = "7/8/2012" 

     // 31 
     decimal daysInStartMonth = DateTime.DaysInMonth(startDate.Year, startDate.Month); 

     // 31 - 8 = 23 
     decimal totalDaysInStartMonthMinusStartDay = daysInStartMonth - startDate.Day; 

     // add one to mark the day as being completed. 23 + 1 = 24 
     decimal daysLeftInStartMonth = totalDaysInStartMonthMinusStartDay + 1; 

     // 24/31 = .77 days left to go in the month 
     decimal daysLeftToCompleteInStartMonthPercentage = daysLeftInStartMonth/daysInStartMonth; 

     return daysLeftToCompleteInStartMonthPercentage; 
    } 

    /// <summary> 
    /// For example if the finish date were 9/8/2012 we've completed 8 days so far or .24 percent of the month 
    /// </summary> 
    private static decimal getDaysCompletedSoFarInFinishMonthPercentage(DateTime finishDate) 
    { 
     // for septebmer = 30 days in month. 
     decimal daysInFinishMonth = DateTime.DaysInMonth(finishDate.Year, finishDate.Month); 

     // 8 days divided by 30 = .26 days completed so far in finish month. 
     decimal daysCompletedSoFarInFinishMonthPercentage = finishDate.Day/daysInFinishMonth; 

     return daysCompletedSoFarInFinishMonthPercentage; 
    } 
0

Esta solución calcula meses enteros y después se añade el mes parcial basada en el final del período de tiempo. De esta forma, siempre calcula los meses completos entre las fechas 'día del mes y luego calcula el mes parcial en función de la cantidad de días restantes.

public decimal getMonthDiff(DateTime date1, DateTime date2) { 
    // Make parameters agnostic 
    var earlyDate = (date1 < date2 ? date1 : date2); 
    var laterDate = (date1 > date2 ? date1 : date2); 

    // Calculate the change in full months 
    decimal months = ((laterDate.Year - earlyDate.Year) * 12) + (laterDate.Month - earlyDate.Month) - 1; 

    // Add partial months based on the later date 
    if (earlyDate.Day <= laterDate.Day) { 
     decimal laterMonthDays = DateTime.DaysInMonth(laterDate.Year, laterDate.Month); 
     decimal laterPartialMonth = ((laterDate.Day - earlyDate.Day)/laterMonthDays); 
     months += laterPartialMonth + 1; 
    } else { 
     var laterLastMonth = laterDate.AddMonths(-1); 
     decimal laterLastMonthDays = DateTime.DaysInMonth(laterLastMonth.Year, laterLastMonth.Month); 
     decimal laterPartialMonth = ((laterLastMonthDays - earlyDate.Day + laterDate.Day)/laterLastMonthDays); 
     months += laterPartialMonth; 
    } 
    return months; 
} 
0

El cálculo a continuación es el que está de acuerdo con la forma en que la autoridad fiscal holandesa desea meses calculados. Esto significa que cuando el día de inicio es, por ejemplo, el 22 de feb, el 23 de marzo debe resultar en algo superior a 1 y no solo en algo como 0.98.

private decimal GetMonthDiffBetter(DateTime date1, DateTime date2) 
    { 
     DateTime start = date1 < date2 ? date1 : date2; 
     DateTime end = date1 < date2 ? date2 : date1; 

     int totalYearMonths = (end.Year - start.Year) * 12; 
     int restMonths = end.Month - start.Month; 
     int totalMonths = totalYearMonths + restMonths; 

     decimal monthPart = (decimal)end.Day/(decimal)start.Day; 
     return totalMonths - 1 + monthPart; 
    }` 
Cuestiones relacionadas