calcolando la differenza in mesi tra due date


128

In C # /. NET TimeSpanha TotalDays, TotalMinutesecc., Ma non riesco a capire una formula per la differenza di mesi totali. Giorni variabili al mese e anni bisestili continuano a buttarmi via. Come posso ottenere TotalMonths ?

Modifica Mi dispiace per non essere più chiaro: so che non riesco a ottenerlo, TimeSpanma ho pensato di usarlo TotalDayse TotalMinutessarebbe un buon esempio per esprimere ciò che stavo cercando ... tranne che sto cercando di ottenere mesi totali.

Esempio: 25 dic 2009 - 6 ott 2009 = 2 TotalMonths. Dal 6 ottobre al 5 novembre equivalgono a 0 mesi. Il 6 novembre, 1 mese. Il 6 dicembre, 2 mesi


2
Cosa ti aspetti dal 25 dicembre 2009 al 6 ottobre 2009?
Jeff Moser,

2
Come si definisce TimeSpan in mesi?
Aliostad,

1
@Aliostad - Senza date potresti definire un mese come 30 giorni ed essere abbastanza preciso.
ChaosPandion

È stato unito a questa domanda da una mod per qualche motivo.
Jamiec,

In realtà, devi leggere il mio post qui, che risponde a questa domanda e fornisce una soluzione codificata, stackoverflow.com/questions/1916358/… ignora i troll (brianary) e presta attenzione alla mia conversazione tramite commenti con supercat. I mesi che sono all'inizio e alla fine di un periodo di tempo che chiamiamo "Mesi orfani", e la domanda si riduce a come definire questi mesi orfani in termini di giorni - una volta che hai determinato che (e come vuoi definirlo ), il resto è solo codice (che è incluso). La mia def. si basa su ciò che penso che i miei utenti si aspettino
Erx_VB.NExT.Coder

Risposte:


222

Non sarai in grado di ottenerlo da un TimeSpan, perché un "mese" è un'unità di misura variabile. Dovrai calcolarlo tu stesso e dovrai capire esattamente come vuoi che funzioni.

Ad esempio, le date devono essere simili July 5, 2009e August 4, 2009produrre un mese o zero mesi di differenza? Se dici che dovrebbe produrne uno, allora che dire di July 31, 2009e August 1, 2009? È che un mese? È semplicemente la differenza dei Monthvalori per le date o è più correlata a un arco di tempo effettivo? La logica per determinare tutte queste regole non è banale, quindi dovrai determinare le tue e implementare l'algoritmo appropriato.

Se tutto ciò che vuoi è semplicemente una differenza nei mesi - ignorando completamente i valori della data - puoi usare questo:

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

Si noti che ciò restituisce una differenza relativa, il che significa che se rValueè maggiore di lValue, il valore restituito sarà negativo. Se vuoi una differenza assoluta, puoi usare questo:

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

@Dinah questa è solo un'approssimazione, se vuoi conoscere il vero .Month e .Years - ho appena pubblicato una risposta per ciò che puoi leggere. Anche se, per quanto riguarda le approssimazioni, questa è una buona approssimazione (oggetti di scena di Adam Robinson), tuttavia dovresti tenere presente che se usi una di queste approssimazioni, stai mentendo involontariamente ai tuoi utenti.
Erx_VB.NExT.Coder

@ Erx_VB.NExT.Coder: Grazie per gli oggetti di scena, ma mentre la tua risposta afferma che nessuna delle risposte prende in considerazione il fatto che un mese è un'unità di misura variabile, sembra che la maggior parte di loro lo faccia; semplicemente non usano la tua particolare approssimazione. Caso in questione, la prima frase nella mia risposta indica che è variabile. Qualsiasi risposta, compresa la tua, è un'approssimazione , semplicemente perché non è una risposta precisa. Il risultato "2 mesi" può significare cose diverse per input diversi, quindi è un'approssimazione.
Adam Robinson,

la mia non è un'approssimazione, tuttavia, se oggi è il 14 marzo, i due mesi precedenti sono calcolati in base al fatto che Jan ha avuto 31 giorni e 29 giorni. ora, sei corretto in quanto il mio metodo non è la definizione di un mese "generale", e il tuo è! Tuttavia, il mio si applica solo se stai segnalando cose come "Questo commento è stato pubblicato x mesi e y giorni AGO", la parte "AGO" fa la differenza, perché facendo riferimento ai precedenti x mesi, quei precedenti x mesi devono essere calcolati in base a quanti giorni erano presenti in quei x mesi! link ....
Erx_VB.NExT.Coder

Ha senso? quindi se ti riferisci a particolari mesi noti, allora il mio metodo è accurato al 100% e tu saresti un'approssimazione, tuttavia, se ti riferisci a un mese in generale, l'approssimazione sarebbe un'idea migliore, e la mia sarebbe solo una cattiva idea (non è fatta per questo e non avrebbe senso usarla). Ecco il link al mio articolo che descrive il problema e fornire una soluzione: stackoverflow.com/questions/1916358/...
Erx_VB.NExT.Coder

2
Questa sembra essere la stessa logica utilizzata dalla funzione DateDiff del server SQL (mese, ...). Ha anche il vantaggio di essere estremamente conciso e facile da spiegare e capire. Lo spiegherei come segue ... quante pagine del calendario dovresti girare per passare da una data all'altra?
JoelFan,

51

(Mi rendo conto che questa è una vecchia domanda, ma ...)

Questo è relativamente doloroso da fare in .NET puro. Consiglierei la mia libreria Noda Time , che è particolarmente progettata per cose come questa:

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

(Esistono altre opzioni, ad es. Se desideri solo un conteggio di mesi anche attraverso anni, dovresti utilizzare Period period = Period.Between(start, end, PeriodUnits.Months);)


Ho scaricato la tua libreria e ho copiato il codice che hai scritto sopra, ma sto ricevendo un errore di compilazione. Operatore errore 1 "-" non può essere applicato agli operandi di tipo "NodaTime.LocalDate" e "NodaTime.LocalDate". Conosco questo post da 5 anni, qualcosa è cambiato da quel momento, il che rende questo codice non funzionante?
Hakan Fıstık,

1
@HakamFostok: Siamo spiacenti, funzionerà quando viene rilasciato 2.0, ma fino ad allora è necessario utilizzare Period.Between. Ho modificato il codice in modo che funzioni con NodaTime 1.3.1.
Jon Skeet,

grazie mille la libreria NodaTime ha fatto esattamente quello che volevo fare. Volevo calcolare non solo i mesi tra due date, ma anche i giorni rimanenti, e questo è esattamente ciò che NodaTime ha fatto, grazie ancora.
Hakan Fıstık,

1
@JonSkeet Quella tua libreria è davvero una magia nera. Le date mi mordono tutto il tempo. Quel frammento di codice mi ha fatto risparmiare un sacco di tempo.
onefootswill il

28

Forse non vuoi sapere delle frazioni di mese; Che mi dici di questo codice?


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"



1
Non capisco il * 100. Dovrebbe essere * 12?
Ruffles,

9

Dovrai definire cosa intendi per TotalMonths per cominciare.
Una semplice definizione indica un mese a 30,4 giorni (365,25 / 12).

Inoltre, qualsiasi definizione comprese le frazioni sembra inutile e il valore intero più comune (interi mesi tra le date) dipende anche da regole commerciali non standard.


9

Ho scritto un metodo di estensione molto semplice DateTimee DateTimeOffsetper farlo. Volevo che funzionasse esattamente come farebbe una TotalMonthsproprietà TimeSpan: vale a dire restituire il conteggio dei mesi completi tra due date, ignorando qualsiasi mese parziale. Perché è basato su di DateTime.AddMonths()esso rispetta le diverse lunghezze dei mesi e restituisce ciò che un essere umano comprenderebbe come un periodo di mesi.

(Purtroppo non è possibile implementarlo come metodo di estensione su TimeSpan perché non mantiene la conoscenza delle date effettive utilizzate e per mesi sono importanti.)

Il codice e i test sono entrambi disponibili su GitHub . Il codice è molto semplice:

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

E passa tutti questi casi di test unitari:

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

3
Soluzione rustica, ma migliore. Copie e incollati. Grazie
Daniel Dolz,

8

È necessario elaborarlo da soli al di fuori dei tempi. Il modo in cui gestirai i giorni di stub alla fine dipenderà da cosa vuoi usarlo.

Un metodo sarebbe quello di contare il mese e quindi correggere i giorni alla fine. Qualcosa di simile a:

   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;

Bel codice, sebbene, 1 bug: invece: (come 28 febbraio + 1 mese == 28 marzo) :-) // decimal daysInEndMonth = (end - end.AddMonths (1)). Giorni; Suggerisco: giorni decimaliInEndMonth = DateTime.DaysInMonth (end.Year, end.Month) * -1;
Bezieur,

3

Lo farei così:

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

4
Questo è certamente un algoritmo, ma potrebbe essere notevolmente semplificatoreturn (dtOther.Month - dtThis.Month) + 12 * (dtOther.Year - dtThis.Year);
Adam Robinson,

1
Due problemi: si parte da 2 date, non da un intervallo di tempo. In secondo luogo, si calcola tra il 1 ° di entrambi i mesi, che è una definizione molto discutibile. Anche se a volte potrebbe essere giusto.
Henk Holterman,

@Henk: Sì, certo che non è sempre giusto, ecco perché ho detto che è così che lo farei, non come nessuno dovrebbe farlo. L'OP non ha specificato come calcolare il risultato. @Adam: Wow, ho pensato ancora una volta troppo complicato ... questo succede troppo spesso a me. Grazie per il commento, hai ovviamente ragione, la tua versione è molto migliore. Lo userò d'ora in poi.
Maximilian Mayerl,

@Adam: perché non lo invii come risposta effettiva ?! Questo è il più compatto finora. Molto lucido.
Dinah,

@Dinah: non volevo supporre che fosse quello che volevi davvero. In tal caso, ho modificato la mia risposta precedente per includere questo approccio.
Adam Robinson,

3

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 e accetta due date invece di un Timespan, che probabilmente è meglio. Spero che questo aiuti chiunque altro.

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
}

3

La risposta accettata funziona perfettamente quando vuoi mesi interi.

Avevo bisogno di mesi parziali. Questa è la soluzione che mi è venuta in mente per mesi parziali:

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

Avevo anche bisogno di una differenza di anno con lo stesso bisogno di anni parziali. Ecco la soluzione che mi è venuta in mente:

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

Avevi un bug logico nella tua YearDifferencefunzione quando lValue.Month < rValue.Month- l'ho risolto ora, potresti voler rivedere ...
Stobor

2

Vecchia domanda che conosco, ma potrebbe aiutare qualcuno. Ho usato la risposta accettata da @Adam sopra, ma poi ho verificato se la differenza è 1 o -1, quindi controlla per vedere se è la differenza di un mese intero di calendario. Quindi il 21/07/55 e il 20/08/55 non sarebbero un mese intero, ma il 21/07/55 e il 21/07/55 lo sarebbero.

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

2
case IntervalType.Month:
    returnValue = start.AddMonths(-end.Month).Month.ToString();
    break;
case IntervalType.Year:
    returnValue = (start.Year - end.Year).ToString();
    break;

2
Una descrizione da associare al codice sarebbe utile anche per altri lettori.
Boeckm,

Sì, per favore, aggiungi qualche commento.
Amar,

1

Il problema con i mesi è che non è davvero una misura semplice - non sono dimensioni costanti. Dovresti definire le tue regole per ciò che vuoi includere e lavorare da lì. Ad esempio, dal 1 ° gennaio al 1 ° febbraio, si potrebbe sostenere che vi sono coinvolti 2 mesi o che si potrebbe dire che è un mese. E che dire di "1 gennaio 20:00" a "1 febbraio 00:00", che non è un intero mese intero. È 0? 1? e viceversa (dal 1 ° gennaio 00:00 al 1 ° febbraio 20:00) ... 1? 2?

Prima di tutto definisci le regole, poi dovrai codificarlo tu stesso, temo ...


1

Se vuoi avere un risultato 1tra 28th Febe 1st March:

DateTime date1, date2;
int monthSpan = (date2.Year - date1.Year) * 12 + date2.Month - date1.Month

Questa sembra essere la stessa logica utilizzata dalla funzione DateDiff del server SQL (mese, ...). Ha anche il vantaggio di essere estremamente conciso e facile da spiegare e capire. Lo spiegherei come segue ... quante pagine del calendario dovresti girare per passare da una data all'altra?
JoelFan,

1

Questa libreria calcola la differenza di mesi, considerando tutte le parti di 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

1

Di seguito è riportato il modo più accurato in cui puoi farlo, poiché la definizione di "1 mese" cambia a seconda del mese in cui si trova e nessuna delle altre risposte ne tiene conto! Se desideri ulteriori informazioni sul problema che non è integrato nel framework, puoi leggere questo post: Un vero oggetto a tempo con .Years & .Months (tuttavia, leggere quel post non è necessario per comprendere e utilizzare la funzione di seguito, funziona al 100%, senza le imprecisioni intrinseche dell'approssimazione che altri amano usare - e sentiti libero di sostituire la funzione .ReverseIt con la funzione .Reverse integrata che potresti avere sul tuo framework (è qui solo per completezza).

Si noti che è possibile ottenere un numero qualsiasi di precisione di date / orari, secondi e minuti o secondi, minuti e giorni, ovunque fino a anni (che conterrebbe 6 parti / segmenti). Se specifichi i primi due e ha più di un anno, restituirà "1 anno e 3 mesi fa" e non restituirà il resto perché hai richiesto due segmenti. se ha solo poche ore, restituirà solo "2 ore e 1 minuto fa". Naturalmente, si applicano le stesse regole se si specificano 1, 2, 3, 4, 5 o 6 segmets (massimo a 6 perché secondi, minuti, ore, giorni, mesi, anni ne fanno solo 6 tipi). Correggerà anche problemi di grammatica come "minuti" vs "minuto" a seconda che sia di 1 minuto o più, lo stesso per tutti i tipi e la "stringa" generata sarà sempre grammaticalmente corretta.

Ecco alcuni esempi di utilizzo: bAllowSegments identifica il numero di segmenti da mostrare ... ovvero: se 3, la stringa di ritorno sarebbe (ad esempio) ... "3 years, 2 months and 13 days"(non includerà ore, minuti e secondi come i primi 3 tempi vengono restituite le categorie), tuttavia, se la data era una data più recente, come qualcosa di qualche giorno fa, specificando invece gli stessi segmenti (3) verrà restituita "4 days, 1 hour and 13 minutes ago", quindi tiene conto di tutto!

se bAllowSegments è 2 restituisce "3 years and 2 months"e se restituisce 6 (valore massimo) "3 years, 2 months, 13 days, 13 hours, 29 minutes and 9 seconds", ma si ricorda che farà NEVER RETURNqualcosa del genere "0 years, 0 months, 0 days, 3 hours, 2 minutes and 13 seconds ago"in quanto comprende che non ci sono dati relativi alla data nei primi 3 segmenti e li ignora, anche se si specificano 6 segmenti , quindi non preoccuparti :). Ovviamente, se c'è un segmento con 0, ne terrà conto quando si forma la stringa e verrà visualizzato come "3 days and 4 seconds ago"e ignorando la parte "0 ore"! Divertiti e per favore commenta se vuoi.

 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

Ovviamente, avrai bisogno di una funzione "ReplaceLast", che accetta una stringa di origine e un argomento che specifica ciò che deve essere sostituito, e un altro argomento che specifica con cosa vuoi sostituirlo, e sostituisce solo l'ultima occorrenza di quella stringa ... ho incluso il mio se non ne hai uno o non vuoi implementarlo, quindi eccolo, funzionerà "così com'è" senza modifiche necessarie. So che la funzione di inversione non è più necessaria (esiste in .net) ma le funzioni ReplaceLast e ReverseIt sono riportate dai giorni pre.net, quindi scusa la data in cui potrebbe apparire (funziona ancora al 100%, usando em per oltre dieci anni, possono garantire che sono privi di bug) ... :). Saluti.

<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 

0

Se vuoi il numero esatto, non puoi solo dal Timespan, dal momento che devi sapere quali mesi hai a che fare e se hai a che fare con un anno bisestile, come hai detto.

Scegli un numero approssimativo o esegui un po 'di confusione con i DateTime originali




0

Se hai a che fare con mesi e anni hai bisogno di qualcosa che sappia quanti giorni ha ogni mese e quali anni sono gli anni bisestili.

Immettere il calendario gregoriano (e altre implementazioni del calendario specifiche della cultura ).

Sebbene Calendar non fornisca metodi per calcolare direttamente la differenza tra due punti nel tempo, ha metodi come

DateTime AddWeeks(DateTime time, int weeks)
DateTime AddMonths(DateTime time, int months)
DateTime AddYears(DateTime time, int years)

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;

0

Il metodo restituisce un elenco che contiene 3 elementi: primo è l'anno, secondo è il mese e fine è il giorno:

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

0

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.


0

Molto tardi al gioco, ma immagino che questo possa essere utile a qualcuno. La maggior parte delle persone tende a misurare mese per mese per data, escluso il fatto che i mesi presentano variazioni diverse. Usando quella cornice di pensiero ho creato un unico liner che confronta le date per noi. Utilizzando il seguente processo.

  1. Qualsiasi numero di anni oltre 1 quando si confronta l'anno verrà moltiplicato per 12, non vi è alcun caso in cui questo può essere uguale a meno di 1 anno intero.
  2. Se l'anno di fine è maggiore, dobbiamo valutare se il giorno corrente è maggiore o uguale al giorno precedente 2A. Se il giorno di fine è maggiore o uguale prendiamo il mese corrente e quindi aggiungiamo 12 mesi sottraendo il mese del mese iniziale 2B. Se il giorno di fine è inferiore al giorno di inizio, eseguiamo lo stesso sopra, tranne per il fatto che aggiungiamo 1 al mese di inizio prima di sottrarre
  3. Se l'anno di fine non è maggiore, ci comportiamo allo stesso modo di 2A / 2B, ma senza aggiungere i 12 mesi perché non è necessario valutare l'anno.

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

Morte per ternario?
SpaceBison,

0

La mia opinione su questa risposta utilizza anche un metodo di estensione , ma può restituire un risultato 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 paio di test:

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

0

Combinando due delle risposte sopra, un altro metodo di estensione è:

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

Grazie a @AdamRobinson e @MarkWhittaker


-1

Calcola il numero di mesi tra 2 date:

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

1
Questo è PHP, non C #.
AFract
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.