Differenza in mesi tra due date


334

Come calcolare la differenza in mesi tra due date in C #?

Esiste un equivalente del DateDiff()metodo di VB in C #. Devo trovare la differenza in mesi tra due date distanti anni. La documentazione dice che posso usare TimeSpancome:

TimeSpan ts = date1 - date2;

ma questo mi dà i dati in giorni. Non voglio dividere questo numero per 30 perché non ogni mese è di 30 giorni e poiché i due valori di operando sono abbastanza separati l'uno dall'altro, temo che dividere per 30 possa darmi un valore sbagliato.

Eventuali suggerimenti?


27
Definisci "differenza in mesi", qual è la differenza in mesi tra "maggio 1.2010" e "giugno 16.2010"? 1.5, 1 o qualcos'altro?
Cheng Chen,

7
Oppure, per sottolineare ulteriormente questo punto, qual è la differenza in mesi tra il 31 dicembre 2010 e il 1 ° gennaio 2011? A seconda del giorno, questa potrebbe essere una differenza di solo 1 secondo; considereresti questo come una differenza di un mese?
stakx - non contribuisce più al

Ecco il semplice e il codice breve nel caso in cui, ancora non poteva ottenere la risposta, vedere questo POST stackoverflow.com/questions/8820603/...
wirol

11
Danny: 1 mese e 15 giorni. stakx: 0 mesi e 1 giorno. Il punto è ottenere il componente del mese . Questo mi sembra abbastanza ovvio ed è una buona domanda.
Kirk Woll,

Risposte:


462

Supponendo che il giorno del mese sia irrilevante (ovvero la differenza tra 2011.1.1 e 2010.12.31 è 1), con data1> data2 che dà un valore positivo e data2> data1 un valore negativo

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

Oppure, supponendo che si desideri un numero approssimativo di "mesi medi" tra le due date, quanto segue dovrebbe funzionare per tutte le differenze di data tranne quelle enormi.

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

Nota, se dovessi utilizzare quest'ultima soluzione, i test unitari dovrebbero indicare l'intervallo di date più ampio con cui l'applicazione è progettata per funzionare e convalidare i risultati del calcolo di conseguenza.


Aggiornamento (grazie a Gary )

Se si utilizza il metodo "mesi medi", un numero leggermente più accurato da utilizzare per il "numero medio di giorni all'anno" è 365.2425 .


3
@Kurru - 365/12 è solo una misura approssimativa della durata media di un mese in giorni. È una misura imprecisa. Per piccoli intervalli di date questa inesattezza può essere tollerata, ma per intervalli di date molto grandi questa inesattezza può diventare significativa.
Adam Ralph,

21
Penso che sia necessario considerare la componente Day. Qualcosa del genere (date1.Year - date2.Year) * 12 + date1.Month - date2.Month + (date1.Day >= date2.Day ? 0 : -1)
DrunkCoder il

2
@DrunkCoder dipende dai requisiti di un determinato sistema. In alcuni casi la tua soluzione potrebbe davvero essere la scelta migliore. Ad esempio, è importante considerare cosa succede quando due date si estendono per un mese di 31 giorni, un mese di 30 giorni, un febbraio di 28 giorni o un febbraio di 29 giorni. Se i risultati della tua formula offrono ciò che il sistema richiede, allora è chiaramente la scelta giusta. In caso contrario, è necessario qualcos'altro.
Adam Ralph,

6
Per rispondere a ciò che ha detto Adam, ho passato anni a scrivere codice per gli attori. Alcuni calcoli sono stati divisi per numero di giorni, arrotondati per 30 per ottenere la cifra mensile . A volte contando i mesi presupponendo che ogni data abbia inizio il primo del mese, conti interi mesi di conseguenza . Non esiste un metodo migliore per calcolare le date. A meno che tu non sia il cliente per cui stai scrivendo il codice, spingi questo verso l'alto della catena e ottenerlo chiarito, possibilmente dal tuo commercialista.
Binary Worrier,

1
365.2425 è un numero leggermente più preciso di giorni in un calendario gregoriano, se è quello che stai usando. Tuttavia, per DateTime.MaxValue (1 gennaio 10000) è solo una differenza di circa 59 giorni. Inoltre, la definizione di un anno può essere molto diversa a seconda della prospettiva en.wikipedia.org/wiki/Year .
Gary,

207

Ecco una soluzione completa per restituire a DateTimeSpan, simile a a TimeSpan, tranne per il fatto che include tutti i componenti della data oltre ai componenti dell'ora.

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

Uscite:

Anni: 1
Mesi: 5
Giorni: 27
Ore: 1
Minuti: 36
Secondi: 50
Millisecondi: 0

Per comodità, ho raggruppato la logica nella DateTimeSpanstruttura, ma puoi spostare il metodo CompareDatesdove preferisci. Inoltre, non importa quale data preceda l'altra.

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

2
@KirkWoll grazie. Ma perché DateTimeSpan restituisce 34giorni per questa differenza di data e ora è in realtà 35 timeanddate.com/date/…
Deeptechtons,

@Deeptechtons, bella cattura. Ci sono stati un paio di problemi che hai portato alla mia attenzione, sia per quanto riguarda la data di inizio sia 31la data "passa" mesi con meno giorni. Ho invertito la logica (in modo che vada da prima a dopo che viceversa) e ora accumuli i mesi senza modificare la data corrente (e quindi passando attraverso i mesi intermedi con meno giorni) Ancora non sono del tutto sicuro di quale sia il risultato ideale dovrebbe essere rispetto 10/31/2012a 11/30/2012. In questo momento il risultato è 1mese.
Kirk Woll,

@KirkWoll grazie per l'aggiornamento, forse ho qualche altro pensiero per consentirmi di confermarlo dopo alcuni test Buon lavoro :)
Deeptechtons,

1
Ho scritto una risposta stackoverflow.com/a/17537472/1737957 a una domanda simile che ha testato le risposte proposte (e ho scoperto che la maggior parte di esse non funziona). Questa risposta è una delle poche che funziona (secondo la mia suite di test). Link a github sulla mia risposta.
jwg

@KirkWoll: questa risposta non sembra funzionare per i casi limite in cui la data di inizio ha un valore di giorno superiore al mese di oggi o in cui la data di origine è un giorno bisestile. Prova 2020-02-29a 2021-06-29: restituisce "1y 4m 1d", ma il valore dovrebbe essere "1y 4m 0d", giusto?
Enigmativity,

37

Si potrebbe fare

if ( date1.AddMonths(x) > date2 )

Questo è così semplice e funziona perfettamente per me. Sono stato piacevolmente sorpreso di vedere che funziona come previsto quando si calcola una data dalla fine di 1 mese a una data alla fine del mese successivo che ha meno giorni. Ad esempio .. 1-31-2018 + 1 mese = 28 febbraio 218
lucky.expert

Questa è una delle soluzioni migliori.
barnacle.m,

Soluzione davvero semplice ed efficiente! La migliore risposta proposta.
Cedric Arnould,

2
Che cosa succede se date1 = 2018-10-28 e date2 = 2018-12-21? La risposta sarà 2. mentre la risposta corretta dovrebbe essere 3. A causa dell'intervallo di date è di 3 mesi. se contiamo solo mesi ignorando i giorni. Quindi questa risposta NON è corretta.
Tommix

Sarebbe più logico: if ( date1.AddMonths(x).Month == date2.Month )allora basta usare x + 1 per contare i mesi
Tommix

34

Se vuoi il numero esatto di mesi interi, sempre positivo (2000-01-15, 2000-02-14 restituisce 0), considerando che un mese intero è quando raggiungi lo stesso giorno del mese successivo (qualcosa come il calcolo dell'età)

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

Modifica motivo: il vecchio codice non era corretto in alcuni casi come:

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

Al fine di evitare confusione per le altre persone, penso che questa soluzione non sia corretta. Utilizzando il test case: new { From = new DateTime(2015, 12, 31), To = new DateTime(2015, 6, 30), Result = 6 } il test fallirà poiché il risultato è 5.
Cristian Badila,

Aggiunto un breve riassunto con la correzione che propongo qui
Cristian Badila il

Non sono sicuro di ottenerlo, la mia funzione restituisce 6 come dovrebbe: dotnetfiddle.net/MRZNnC
Guillaume86

Ho copiato il caso di test qui a mano e ha un errore. Le specifiche non riuscendo dovrebbe essere: new { From = new DateTime(2015, 12, 31), To = new DateTime(2016, 06, 30), Result = 6 }. Il "bug" sta nel to.Day < from.Daycodice che non tiene conto del fatto che i mesi possono terminare in un "giorno del mese" diverso. In questo caso dal 31 dicembre 2015, fino al 30 giugno 2016, saranno trascorsi 6 mesi completi (dal momento che giugno ha 30 giorni) ma il codice restituirà 5.
Cristian Badila,

3
Secondo me è un comportamento previsto, o almeno è il comportamento che mi aspetto. Ho predetto che un mese completo è quando raggiungi lo stesso giorno (o il mese successivo come in questo caso).
Guillaume86,

22

Ho verificato l'utilizzo di questo metodo in VB.NET tramite MSDN e sembra che abbia molti usi. Non esiste un metodo incorporato in C #. (Anche se non è una buona idea) puoi chiamare VB in C #.

  1. Aggiungi Microsoft.VisualBasic.dllal tuo progetto come riferimento
  2. utilizzare Microsoft.VisualBasic.DateAndTime.DateDiff nel tuo codice

7
Perché pensi che non sia una buona idea? Intuitivamente, immagino che la libreria sia "solo un'altra libreria .NET" per il runtime. Nota, qui sto giocando l'avvocato del diavolo, sarei anche riluttante a farlo dato che "sembra sbagliato" (tipo di imbroglio) ma mi chiedo se ci siano ragioni tecniche convincenti per non farlo.
Adam Ralph,

3
@AdamRalph: nessun motivo per non farlo. Queste librerie sono implementate in un codice gestito al 100%, quindi è uguale a tutto il resto. L'unica differenza immaginabile è che il Microsoft.VisualBasic.dllmodulo deve essere caricato, ma il tempo necessario per farlo è trascurabile. Non c'è motivo di truffarti con funzionalità utili e testate solo perché hai scelto di scrivere il tuo programma in C #. (Questo vale anche per cose del genere My.Application.SplashScreen.)
Cody Gray

3
Cambieresti idea se sapessi che è stato scritto in C #? Era. Secondo la stessa logica, anche l'utilizzo di System.Data e PresentationFramework sta tradendo, parti sostanziali di esso sono scritte in C ++ / CLI.
Hans Passant,

3
@AdamRalph: Qualche esempio particolare di quello "strano bagaglio" che mi viene in mente? O lo stai dicendo puramente ipoteticamente? E sì, potrebbe confondere le menti di alcuni dei tuoi amici di C # che hanno scritto una quantità epica di codice per fare qualcosa che puoi fare in una riga con la usingfrase giusta , ma dubito che ci saranno danni seri.
Cody Grey

1
@Cody Grey: d'accordo, l'esempio è banale come si illustra. È il "rumore" di codice aggiuntivo introdotto chiamando un metodo così insolito (da un COV P) che sarei desideroso di evitare. In un team ben organizzato tali cose verrebbero comunque prese in esame e possono essere facilmente evitate. A proposito, non sto provando ad attaccare VB6 / VB.NET. Ho descritto metodi come "strani" solo perché, da un punto di vista di .NET, non c'è motivo DateAndTime.Year()di esistere, dato che DateTimeha una Yearproprietà. Esiste solo per far apparire VB.NET più simile a VB6. Come ex programmatore VB6, posso apprezzarlo ;-)
Adam Ralph,

10

Per ottenere la differenza in mesi (sia inizio che fine inclusi), indipendentemente dalle date:

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

5
Immagina starte endsono identici. Quindi ottieni un risultato di 1. Come va? Perché aggiungi 1 al risultato? Chi vota a favore di questa risposta: - /?
paul

Per date identiche, fornirà un output come 1. Fondamentalmente, conterà tutti i mesi inclusi i mesi di inizio e fine.
Chirag,

3
non mi sembra la differenza tra due elementi. Qual è la differenza tra 2 e 2? È davvero 1? Suggerirei che la differenza sia 0.
paul

8

Usa 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

(esempio di fonte)


7

Avevo solo bisogno di qualcosa di semplice per soddisfare, ad esempio, le date di lavoro in cui è inserito solo il mese / anno, quindi volevo lavorare in anni e mesi distinti. Questo è quello che uso, qui solo per utilità

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


4

È possibile utilizzare la classe DateDiff della libreria dei periodi di tempo per .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

2

Ecco il mio contributo per ottenere la differenza nei mesi che ho trovato accurato:

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

Puoi creare un altro metodo chiamato DiffYears e applicare esattamente la stessa logica di cui sopra e AddYears invece di AddMonths nel ciclo while.


2

Questo ha funzionato per quello di cui avevo bisogno. Il giorno del mese non ha avuto importanza nel mio caso perché capita sempre che sia l'ultimo giorno del mese.

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

2

Il modo più preciso è questo che restituisce la differenza in mesi per frazione:

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

2

Ecco una soluzione semplice che funziona almeno per me. Probabilmente non è il più veloce, perché utilizza la funzionalità AddMonth di DateTime in un ciclo:

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

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

1

Questo è dalla mia biblioteca, restituirà la differenza di mesi tra due date.

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

1
funziona? Continuo a ricevere 11 su carta per Jan-31-2014eDec-31-2013
Dave Cousineau il

1

Puoi avere una funzione simile a questa.

Ad esempio, dal 27/12/2012 al 2012/12/29 diventa 3 giorni. Allo stesso modo, dal 15/12/2012 al 15/01/2013 diventa 2 mesi, perché fino al 14/01/2013 è 1 mese. dal 15 è iniziato il 2 ° mese.

È possibile rimuovere "=" nella seconda condizione if, se non si desidera includere entrambi i giorni nel calcolo. vale a dire, dal 2012/12/15 al 2013/01/15 è di 1 mese.

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

1

puoi usare la seguente estensione: Codice

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
}

Implementazione !

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

1

Ecco una soluzione molto più concisa che utilizza VB.Net DateDiff solo per anno, mese, giorno. Puoi anche caricare la libreria DateDiff in C #.

la data1 deve essere <= data2

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

1

Questo è in risposta alla risposta di Kirk Woll. Non ho ancora abbastanza punti reputazione per rispondere a un commento ...

Mi è piaciuta la soluzione di Kirk e l'avrei strappata senza vergogna e usarla nel mio codice, ma quando ho guardato attraverso ho capito che era troppo complicata. Switching e loop non necessari e un costruttore pubblico inutile da utilizzare.

Ecco la mia riscrittura:

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, praticamente lo stesso:

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

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

1

Nel mio caso è necessario calcolare il mese completo dalla data di inizio al giorno precedente a questo giorno del mese successivo o dall'inizio alla fine del mese.


Es: dal 01/01/2018 al 31/1/2018 è un mese completo
Ex2: dal 05/01/2018 al 4/04/2018 è un mese completo

quindi in base a questo ecco la mia soluzione:

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: nel mio caso era necessario calcolare i giorni rimanenti dopo i mesi completi, quindi se non è il tuo caso potresti ignorare il risultato dei giorni o persino cambiare il metodo di ritorno da tupla a intero.


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

Questa soluzione è per il calcolo del noleggio / abbonamento, dove la differenza non significa essere sottrazione, ma è intesa come intervallo tra quelle due date.


1

Ci sono 3 casi: stesso anno, anno precedente e altri anni.

Se il giorno del mese non ha importanza ...

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

1

Ho scritto una funzione per raggiungere questo obiettivo, perché gli altri modi non funzionavano per me.

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

Ti preghiamo di rispondere in inglese (rispetto a qualsiasi lingua inventata ...)
Kleopatra,

Perché non solo startDate.AddMonths (monthCount) .ToShortDateString ()? Questo non risponde alla domanda originale che è stata posta comunque!
TabbyCool

oh, scusa @TabbyCool, questo codice funziona bene nel mio programma! la regola dei programmatori dice: prima il codice funziona e poi l'ottimizzazione! tanx per il tuo commento :)
reza akhlaghi,

1

La mia comprensione della differenza di mesi totale tra 2 date ha una parte integrale e una parte (la data è importante).

La parte integrale è la differenza di mesi interi.

La parte frazionaria, per me, è la differenza della% del giorno (rispetto ai giorni interi del mese) tra i mesi di inizio e di fine.

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 questa estensione, questi sono i risultati:

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

1

Non ci sono molte risposte chiare su questo perché stai sempre assumendo delle cose.

Questa soluzione calcola tra due date i mesi tra il presupposto che si desidera salvare il giorno del mese per il confronto, (nel senso che il giorno del mese viene considerato nel calcolo)

Esempio, se hai una data del 30 gennaio 2012, il 29 febbraio 2012 non sarà un mese, ma lo sarà il 1 ° marzo 2013.

È stato testato abbastanza accuratamente, probabilmente lo pulirà più tardi mentre lo usiamo, ma qui:

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
}

1

Sulla base dell'ottimo lavoro di DateTimeSpan fatto sopra, ho normalizzato un po 'il codice; questo sembra funzionare abbastanza bene:

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

Quando si confronta con CompareDates(x, y)dove x={01/02/2019 00:00:00}e y={01/05/2020 00:00:00}poi Monthsmi dà2
Bassie

1

Questa semplice funzione statica calcola la frazione di mesi tra due periodi di tempo, ad es

  • 1.1. al 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 funzione presuppone che la prima data sia inferiore alla seconda data. Per gestire intervalli di tempo negativi, è possibile modificare facilmente la funzione introducendo all'inizio un segno e uno scambio variabile.

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

0

Essere in grado di calcolare la differenza tra 2 date in mesi è una cosa perfettamente logica da fare ed è necessaria in molte applicazioni aziendali. I vari programmatori qui che hanno fornito commenti come - qual è la differenza in mesi tra "Maggio 1.2010" e "Giugno 16,2010, qual è la differenza in mesi tra il 31 dicembre 2010 e il 1 ° gennaio 2011? - non sono riusciti a capire le basi delle applicazioni aziendali.

Ecco la risposta ai 2 commenti precedenti - Il numero di mesi tra 1 maggio 2010 e 16 giugno 2010 è 1 mese, il numero di mesi tra 31 dicembre 2010 e 1 gennaio 2011 è 0. It sarebbe molto sciocco calcolarli come 1,5 mesi e 1 secondo, come suggerito dai programmatori di cui sopra.

Le persone che hanno lavorato su carta di credito, elaborazione dei mutui, elaborazione delle imposte, elaborazione degli affitti, calcoli mensili degli interessi e una vasta gamma di altre soluzioni commerciali sarebbero d'accordo.

Il problema è che tale funzione non è inclusa in C # o VB.NET per quella materia. Datediff prende in considerazione solo gli anni o la componente del mese, quindi in realtà è inutile.

Ecco alcuni esempi di vita reale su dove è necessario e correttamente calcolare i mesi:

Hai vissuto in un affitto a breve termine dal 18 febbraio al 23 agosto. Quanti mesi sei rimasto lì? La risposta è semplice: 6 mesi

Hai un conto bancario in cui gli interessi vengono calcolati e pagati alla fine di ogni mese. Depositi il ​​10 giugno e lo estrai il 29 ottobre (stesso anno). Per quanti mesi ti interessano? Risposta molto semplice: 4 mesi (anche in questo caso i giorni extra non contano)

Nelle applicazioni aziendali, la maggior parte delle volte, quando è necessario calcolare mesi, è perché è necessario conoscere mesi "completi" in base al modo in cui gli umani calcolano il tempo; non basato su alcuni pensieri astratti / irrilevanti.


5
Questo è uno dei motivi per cui la contabilità non è matematica. Nella contabilità il risultato dipende dal modo in cui lo calcoli .. Conosco i tuoi punti e conosco la "visione aziendale comune" su questo, ma questa spiegazione è chiaramente sbagliata. Tra il 2012/11:30 e il 2012/12/01 ci sono 0, oppure 1/30, oppure 1/31, oppure 1 o 2 mesi, a seconda di cosa hai chiesto . Le date erano esclusive o inclusive? Hai chiesto il numero di mesi trascorsi, toccati o passati? Volevi arrotondare, arrotondare o esatto?
quetzalcoatl,

3
Ora spiegalo a un uomo d'affari o un commercialista e ti daranno uno sguardo perplesso. È sempre "così ovvio per loro che ovviamente intendevano X e Y e Z, come hai potuto pensare diversamente?" Ora chiama diversi uomini d'affari e cerca di convincerli a concordare sull'argomento. I contabili hanno maggiori probabilità di essere d'accordo, perché a un certo punto useranno la matematica per verificare con quali opzioni potrebbero riassumere accidentalmente lo stesso periodo due volte, ecc. Anche i tuoi esempi di calcoli sono discutibili e dipendenti dalla regione, o chiaramente non validi come ipotizzano regole di business extra come ignorare i giorni in più.
quetzalcoatl,

2
-1 Stai presupponendo che tutto il software sia una "applicazione aziendale". Lo scopo del codice in questione non è menzionato. Supponi anche che tutte le "applicazioni aziendali" abbiano le stesse regole, il che non è assolutamente vero.
Jesse Webb,

0

Struttura di Kirks espansa con ToString (formato) e Durata (ms lunghi)

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

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));
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.