obliczenie różnicy w miesiącach między dwiema datami

131

W C # / .NET TimeSpanma TotalDays, TotalMinutesitp., Ale nie mogę wymyślić wzoru na całkowitą różnicę miesięcy. Zmienne dni w miesiącu i lata przestępne wciąż mnie wyrzucają. Jak mogę uzyskać TotalMonths ?

Edytuj Przepraszamy, że nie jest to bardziej jasne: wiem, że nie mogę tego uzyskać, TimeSpanale pomyślałem, że użyję TotalDaysi TotalMinutesbyłbym dobrym przykładem, aby wyrazić to, czego szukałem ... z wyjątkiem tego, że próbuję uzyskać całkowitą liczbę miesięcy.

Przykład: 25 grudnia 2009 r. - 6 października 2009 r. = Łącznie 2 miesiące. 6 października do 5 listopada to 0 miesięcy. 6 listopada 1 miesiąc. 6 grudnia, 2 miesiące

Dinah
źródło
2
Czego spodziewasz się w dniach 25 grudnia 2009 - 6 października 2009?
Jeff Moser,
2
Jak zdefiniujesz TimeSpan w miesiącach?
Aliostad
1
@Aliostad - Bez dat możesz zdefiniować miesiąc jako 30 dni i być całkiem dokładnym.
ChaosPandion,
Z jakiegoś powodu został połączony z tym pytaniem przez mod.
Jamiec
Właściwie musisz przeczytać mój post tutaj, który odpowiada na to pytanie i zapewnia zakodowane rozwiązanie, stackoverflow.com/questions/1916358/ ... zignoruj ​​trolle (brianary) i zwróć uwagę na moją rozmowę poprzez komentarze z superkatem. Miesiące, które r na początku i końcu okresu, który nazywamy „Osieroconymi miesiącami”, sprowadza się do tego, jak zdefiniować te osierocone miesiące w kategoriach dni - kiedy już to ustalisz (i jak chcesz to zdefiniować ), reszta to tylko kod (który jest dołączony). Moja pok. opiera się na tym, czego, jak myślę, oczekują moi użytkownicy
Erx_VB.NExT.Coder

Odpowiedzi:

228

Nie uzyskasz tego od a TimeSpan, ponieważ „miesiąc” jest zmienną jednostką miary. Będziesz musiał sam to obliczyć i musisz dowiedzieć się, jak dokładnie chcesz, aby działał.

Na przykład, czy daty powinny podobać się July 5, 2009i August 4, 2009przynosić różnicę jednego miesiąca czy zero miesięcy? Jeśli powiesz, że powinno to dać jeden, to co z July 31, 2009i August 1, 2009? Jest to miesiąc? Czy jest to po prostu różnica Monthwartości dat, czy też jest to bardziej związane z rzeczywistym okresem? Logika określania wszystkich tych reguł jest nietrywialna, więc będziesz musiał określić własne i zaimplementować odpowiedni algorytm.

Jeśli wszystko, czego chcesz, to po prostu różnica w miesiącach - całkowicie pomijając wartości dat - możesz użyć tego:

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

Zwróć uwagę, że zwraca to względną różnicę, co oznacza, że ​​jeśli rValuejest większe niż lValue, wartość zwracana będzie ujemna. Jeśli chcesz absolutnej różnicy, możesz użyć tego:

public static int MonthDifference(this DateTime lValue, DateTime rValue)
{
    return Math.Abs((lValue.Month - rValue.Month) + 12 * (lValue.Year - rValue.Year));
}
Adam Robinson
źródło
@Dinah to tylko przybliżenie, jeśli chcesz poznać prawdziwe .Miesiąc i .Lata - właśnie zamieściłem odpowiedź na to, co możesz przeczytać. Chociaż, jeśli chodzi o przybliżenia, jest to dobre przybliżenie (rekwizyty dla Adama Robinsona), jednak należy pamiętać, że jeśli używasz któregokolwiek z tych przybliżeń, po prostu nieumyślnie okłamujesz swoich użytkowników.
Erx_VB.NExT.Coder
@ Erx_VB.NExT.Coder: Dzięki za rekwizyty, ale chociaż twoja odpowiedź stwierdza, że ​​żadna z odpowiedzi nie uwzględnia faktu, że miesiąc jest zmienną jednostką miary, wydaje się, że większość z nich tak robi; po prostu nie używają twojego konkretnego przybliżenia. Na przykład już pierwsze zdanie mojej odpowiedzi wskazuje, że jest zmienna. Każda odpowiedź, w tym twoja, jest przybliżeniem , po prostu dlatego, że nie jest to dokładna odpowiedź. Twój wynik „2 miesiące” może oznaczać różne rzeczy dla różnych danych wejściowych, więc jest to przybliżenie.
Adam Robinson,
mój nie jest jednak przybliżeniem, jeśli dzisiaj jest 14 marca, to dwa poprzednie miesiące są obliczane na podstawie tego, że Jan miał 31 dni, a luty 29 dni. teraz masz rację, ponieważ moja metoda nie jest definicją „ogólnego” miesiąca, a Twoja jest! Jednak moje ma zastosowanie tylko wtedy, gdy zgłaszasz takie rzeczy, jak „Ten komentarz został opublikowany x miesięcy i y dni temu”, część „AGO” robi różnicę, ponieważ odnosi się do poprzednich x miesięcy, te poprzednie x miesięcy muszą zostać obliczone na podstawie tego, ile dni było obecnych w tych x miesiącach! link ....
Erx_VB.NExT.Coder
Czy to ma sens? więc jeśli odnosisz się do konkretnych, znanych miesięcy, to moja metoda jest w 100% dokładna i byłbyś przybliżeniem, jednak jeśli odnosisz się ogólnie do miesiąca, to przybliżenie byłoby lepszym pomysłem, i mój byłby po prostu złym pomysłem (nie jest do tego stworzony i nie ma sensu go używać). Oto link do mojego artykułu opisującego problem i zapewniającego rozwiązanie: stackoverflow.com/questions/1916358/…
Erx_VB.NExT.Coder
2
Wydaje się, że jest to ta sama logika, której używa funkcja Sql Server DateDiff (miesiąc, ...). Ma również tę zaletę, że jest niezwykle zwięzły i łatwy do wyjaśnienia i zrozumienia. Wyjaśniłbym to w następujący sposób ... ile stron w kalendarzu musiałbyś przewrócić, aby przejść z jednej daty na drugą?
JoelFan
51

(Zdaję sobie sprawę, że to stare pytanie, ale ...)

Jest to stosunkowo bolesne w przypadku czystej platformy .NET. Poleciłbym moją własną bibliotekę Noda Time , która jest specjalnie zaprojektowana do takich rzeczy:

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

(Istnieją inne opcje, np. Jeśli chcesz liczyć tylko miesiące, nawet w latach, użyjesz Period period = Period.Between(start, end, PeriodUnits.Months);)

Jon Skeet
źródło
Ściągnąłem twoją bibliotekę i skopiowałem kod, który napisałeś powyżej, ale otrzymuję błąd czasu kompilacji. Błąd 1 Operator „-” nie może być zastosowany do operandów typu „NodaTime.LocalDate” i „NodaTime.LocalDate”. Znam ten post od 5 lat, czy od tamtego czasu coś się zmieniło, przez co ten kod nie działa?
Hakan Fıstık
1
@HakamFostok: Przepraszamy - będzie działać, gdy zostanie wydana wersja 2.0, ale do tego czasu musisz używać Period.Between. Zmodyfikowałem kod, aby działał z NodaTime 1.3.1.
Jon Skeet
wielkie dzięki, biblioteka NodaTime zrobiła dokładnie to, co ja chcę. Chciałem obliczyć nie tylko miesiące między dwiema datami, ale także pozostałe dni, i to właśnie zrobił NodaTime, jeszcze raz dziękuję.
Hakan Fıstık
1
@JonSkeet Ta twoja biblioteka to prawdziwie czarna magia. Daktyle mnie gryzą cały czas. Ten fragment kodu zaoszczędził mi mnóstwo czasu.
onefoots będzie
28

Może nie chcesz wiedzieć o ułamkach miesięcznych; A co z tym kodem?


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

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


Rubens Farias
źródło
1
Nie rozumiem * 100. Czy powinno to być * 12?
Ruffles
10

Napisałem bardzo prostą metodę rozszerzenia na temat DateTimei DateTimeOffsetdo tego. Chciałem, żeby działało dokładnie tak, jak działałaby TotalMonthsnieruchomość TimeSpan: tj. Zwracała liczbę pełnych miesięcy między dwiema datami, ignorując częściowe miesiące. Ponieważ opiera się na DateTime.AddMonths()niej, uwzględnia różne długości miesięcy i zwraca to, co człowiek zrozumiałby jako okres miesięcy.

(Niestety nie można go zaimplementować jako metody rozszerzającej na TimeSpan, ponieważ nie zachowuje to wiedzy o faktycznie używanych datach, a przez miesiące są one ważne).

Kod i testy są dostępne w serwisie GitHub . Kod jest bardzo prosty:

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

I przechodzi wszystkie te jednostkowe przypadki testowe:

// Simple comparison
Assert.AreEqual(1, new DateTime(2014, 1, 1).GetTotalMonthsFrom(new DateTime(2014, 2, 1)));
// Just under 1 month's diff
Assert.AreEqual(0, new DateTime(2014, 1, 1).GetTotalMonthsFrom(new DateTime(2014, 1, 31)));
// Just over 1 month's diff
Assert.AreEqual(1, new DateTime(2014, 1, 1).GetTotalMonthsFrom(new DateTime(2014, 2, 2)));
// 31 Jan to 28 Feb
Assert.AreEqual(1, new DateTime(2014, 1, 31).GetTotalMonthsFrom(new DateTime(2014, 2, 28)));
// Leap year 29 Feb to 29 Mar
Assert.AreEqual(1, new DateTime(2012, 2, 29).GetTotalMonthsFrom(new DateTime(2012, 3, 29)));
// Whole year minus a day
Assert.AreEqual(11, new DateTime(2012, 1, 1).GetTotalMonthsFrom(new DateTime(2012, 12, 31)));
// Whole year
Assert.AreEqual(12, new DateTime(2012, 1, 1).GetTotalMonthsFrom(new DateTime(2013, 1, 1)));
// 29 Feb (leap) to 28 Feb (non-leap)
Assert.AreEqual(12, new DateTime(2012, 2, 29).GetTotalMonthsFrom(new DateTime(2013, 2, 28)));
// 100 years
Assert.AreEqual(1200, new DateTime(2000, 1, 1).GetTotalMonthsFrom(new DateTime(2100, 1, 1)));
// Same date
Assert.AreEqual(0, new DateTime(2014, 8, 5).GetTotalMonthsFrom(new DateTime(2014, 8, 5)));
// Past date
Assert.AreEqual(6, new DateTime(2012, 1, 1).GetTotalMonthsFrom(new DateTime(2011, 6, 10)));
Mark Whitaker
źródło
4
Rustykalne, ale najlepsze rozwiązanie. Kopie i wklejone. Dziękuję
Daniel Dolz
9

Na początek będziesz musiał zdefiniować, co masz na myśli przez TotalMonths.
Prosta definicja to 30,4 dnia miesiąca (365,25 / 12).

Poza tym każda definicja zawierająca ułamki wydaje się bezużyteczna, a bardziej powszechna wartość całkowita (całe miesiące między datami) również zależy od niestandardowych reguł biznesowych.

Henk Holterman
źródło
8

Musisz rozwiązać to samodzielnie z datami. To, jak poradzisz sobie z końcowymi dniami, będzie zależeć od tego, do czego chcesz go użyć.

Jedną z metod byłoby policzenie miesiąca, a następnie poprawienie liczby dni na końcu. Coś jak:

   DateTime start = new DateTime(2003, 12, 25);
   DateTime end = new DateTime(2009, 10, 6);
   int compMonth = (end.Month + end.Year * 12) - (start.Month + start.Year * 12);
   double daysInEndMonth = (end - end.AddMonths(1)).Days;
   double months = compMonth + (start.Day - end.Day) / daysInEndMonth;
JDunkerley
źródło
Niezły kod, chociaż 1 błąd: zamiast tego: (jak 28 lutego + 1 miesiąc == 28 marca) :-) // decimal daysInEndMonth = (end - end.AddMonths (1)). Days; Proponuję: decimal daysInEndMonth = DateTime.DaysInMonth (end.Year, end.Month) * -1;
bezieur
3

Zrobiłbym to tak:

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;
}
Maximilian Mayerl
źródło
4
To z pewnością jeden algorytm, ale można go znacznie uprościć doreturn (dtOther.Month - dtThis.Month) + 12 * (dtOther.Year - dtThis.Year);
Adam Robinson
1
Dwa problemy: zaczynasz od 2 dat, a nie TimeSpan. Po drugie, obliczasz między pierwszym z obu miesięcy, to bardzo wątpliwa definicja. Chociaż czasami może to mieć rację.
Henk Holterman
@Henk: Tak, oczywiście, że to nie zawsze jest w porządku, dlatego powiedziałem, że tak bym to robił, a nie jak ktokolwiek powinien to robić. W PO nie określono, w jaki sposób należy obliczyć wynik. @Adam: Wow, znowu pomyślałem, że to zbyt skomplikowane ... to zdarza mi się zbyt często. Dzięki za komentarz, oczywiście masz rację, Twoja wersja jest dużo lepsza. Będę tego używać od teraz.
Maximilian Mayerl
@Adam: dlaczego nie podasz tego jako rzeczywistej odpowiedzi ?! Jak dotąd jest to najbardziej kompaktowe. Bardzo zgrabny.
Dinah
@Dinah: Nie chciałem zakładać, że tego właśnie chciałeś. Jeśli tak, zredagowałem poprzednią odpowiedź, aby uwzględnić to podejście.
Adam Robinson
3

Nie ma wielu jasnych odpowiedzi na ten temat, ponieważ zawsze coś zakładasz.

To rozwiązanie oblicza między dwiema datami miesiące między założeniem, że chcesz zapisać dzień miesiąca do porównania (co oznacza, że ​​w obliczeniach brany jest pod uwagę dzień miesiąca)

Na przykład, jeśli masz datę 30 stycznia 2012 r., 29 lutego 2012 r. Nie będzie miesiącem, ale 1 marca 2013 r. Będzie.

Został przetestowany dość dokładnie, prawdopodobnie wyczyści go później, gdy go użyjemy, i przyjmuje dwie daty zamiast przedziału czasowego, co prawdopodobnie jest lepsze. Mam nadzieję, że to pomoże komukolwiek innemu.

private static int TotalMonthDifference(DateTime dtThis, DateTime dtOther)
{
    int intReturn = 0;
    bool sameMonth = false;

    if (dtOther.Date < dtThis.Date) //used for an error catch in program, returns -1
        intReturn--;

    int dayOfMonth = dtThis.Day; //captures the month of day for when it adds a month and doesn't have that many days
    int daysinMonth = 0; //used to caputre how many days are in the month

    while (dtOther.Date > dtThis.Date) //while Other date is still under the other
    {
        dtThis = dtThis.AddMonths(1); //as we loop, we just keep adding a month for testing
        daysinMonth = DateTime.DaysInMonth(dtThis.Year, dtThis.Month); //grabs the days in the current tested month

        if (dtThis.Day != dayOfMonth) //Example 30 Jan 2013 will go to 28 Feb when a month is added, so when it goes to march it will be 28th and not 30th
        {
            if (daysinMonth < dayOfMonth) // uses day in month max if can't set back to day of month
                dtThis.AddDays(daysinMonth - dtThis.Day);
            else
                dtThis.AddDays(dayOfMonth - dtThis.Day);
        }
        if (((dtOther.Year == dtThis.Year) && (dtOther.Month == dtThis.Month))) //If the loop puts it in the same month and year
        {
            if (dtOther.Day >= dayOfMonth) //check to see if it is the same day or later to add one to month
                intReturn++;
            sameMonth = true; //sets this to cancel out of the normal counting of month
        }
        if ((!sameMonth)&&(dtOther.Date > dtThis.Date))//so as long as it didn't reach the same month (or if i started in the same month, one month ahead, add a month)
            intReturn++;
    }
    return intReturn; //return month
}
GreatNate
źródło
3

Przyjęta odpowiedź działa idealnie, gdy chcesz pełnych miesięcy.

Potrzebowałem niepełnych miesięcy. Oto rozwiązanie, które wymyśliłem na niepełne miesiące:

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

Potrzebowałem też różnicy roku z taką samą potrzebą na lata niepełne. Oto rozwiązanie, które wymyśliłem:

    /// <summary>
    /// Calculate the differences in years.
    /// This will round up to catch partial months.
    /// </summary>
    /// <param name="lValue"></param>
    /// <param name="rValue"></param>
    /// <returns></returns>
    public static int YearDifference(DateTime lValue, DateTime rValue)
    {
        return lValue.Year - rValue.Year +
               (lValue.Month > rValue.Month // Partial month, same year
                   ? 1
                   : ((lValue.Month = rValue.Month) 
                     && (lValue.Day > rValue.Day)) // Partial month, same year and month
                   ? 1 : 0);
    }
endyourif
źródło
Miałeś błąd logiki w swojej YearDifferencefunkcji, gdy lValue.Month < rValue.Month- Naprawiłem to teraz, możesz chcieć przejrzeć ...
Stobor
2

Znam stare pytanie, ale może komuś pomóc. Użyłem powyżej zaakceptowanej odpowiedzi @Adam, ale potem sprawdziłem, czy różnica wynosi 1 czy -1, a następnie sprawdź, czy jest to różnica pełnego miesiąca kalendarzowego. Zatem 21/07/55 i 20/08/55 nie byłyby pełnym miesiącem, ale 21/07/55 i 21/07/55 tak.

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

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

        switch (diff)
        {
            case 0:
                // We're on the same month, so ok.
                isValid = true;
                break;
            case -1:
                // The month is the previous month, so check if the date makes it a calendar month out.
                isValid = (dob.Day > originalDob.Day);
                break;
            case 1:
                // The month is the next month, so check if the date makes it a calendar month out.
                isValid = (dob.Day < originalDob.Day);
                break;
            default:
                // Either zero or greater than 1 month difference, so not ok.
                isValid = false;
                break;
        }
        if (!isValid)
            return Json("Date of Birth cannot be greater than one month either side of the date we hold.", JsonRequestBehavior.AllowGet);
    }
    else
    {
        return Json("Date of Birth is invalid.", JsonRequestBehavior.AllowGet);
    }
    return Json(true, JsonRequestBehavior.AllowGet);
}
nrg
źródło
2
case IntervalType.Month:
    returnValue = start.AddMonths(-end.Month).Month.ToString();
    break;
case IntervalType.Year:
    returnValue = (start.Year - end.Year).ToString();
    break;
Everton
źródło
3
Opis dołączony do kodu byłby również korzystny dla innych czytelników.
Boeckm
tak, proszę dodać komentarz.
Amar
1

Problem z miesiącami polega na tym, że nie jest to prosta miara - nie są one stałą wielkością. Musisz zdefiniować swoje zasady dotyczące tego, co chcesz uwzględnić, i zacząć od tego. Na przykład od 1 stycznia do 1 lutego - można argumentować, że są tam 2 miesiące lub można powiedzieć, że jest to jeden miesiąc. A co z „1 stycznia 20:00” do „1 lutego 00:00” - to nie jest cały miesiąc. Czy to 0? 1? a co na odwrót (1 stycznia 00:00 do 1 lutego 20:00) ... 1? 2?

Najpierw zdefiniuj zasady, potem będziesz musiał sam to zakodować, obawiam się ...

Marc Gravell
źródło
1

Jeśli chcesz uzyskać wynik 1od 28th Febdo 1st March:

DateTime date1, date2;
int monthSpan = (date2.Year - date1.Year) * 12 + date2.Month - date1.Month
Snowbear
źródło
Wydaje się, że jest to ta sama logika, której używa funkcja Sql Server DateDiff (miesiąc, ...). Ma również tę zaletę, że jest niezwykle zwięzły i łatwy do wyjaśnienia i zrozumienia. Wyjaśniłbym to w następujący sposób ... ile stron w kalendarzu musiałbyś przewrócić, aby przejść z jednej daty na drugą?
JoelFan
1

Ta biblioteka oblicza różnicę miesięcy, biorąc pod uwagę wszystkie części 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

źródło
1

Poniżej znajduje się najdokładniejszy sposób, w jaki możesz to zrobić, ponieważ definicja „1 miesiąca” zmienia się w zależności od tego, który to miesiąc, a inne odpowiedzi nie uwzględniają tego! Jeśli chcesz uzyskać więcej informacji na temat problemu, który nie jest wbudowany w framework, możesz przeczytać ten post: A Real Timespan Object With .Years & .Months (jednak przeczytanie tego posta nie jest konieczne, aby zrozumieć i używać poniższej funkcji, działa w 100%, bez nieodłącznych niedokładności przybliżeń, których inni lubią używać - i nie krępuj się, aby zastąpić funkcję .ReverseIt wbudowaną funkcją .Reverse, którą możesz mieć w swoim frameworku (jest tutaj tylko dla kompletności).

Należy pamiętać, że można uzyskać dowolną liczbę dokładności dat / godzin, sekund i minut lub sekund, minut i dni, w dowolnym miejscu do lat (co zawierałoby 6 części / segmentów). Jeśli określisz dwie górne i ma ponad rok, zwróci wartość „1 rok i 3 miesiące temu” i nie zwróci pozostałych, ponieważ zażądałeś dwóch segmentów. jeśli ma tylko kilka godzin, zwróci tylko „2 godziny i 1 minutę temu”. Oczywiście te same zasady obowiązują, jeśli określisz 1, 2, 3, 4, 5 lub 6 segmentów (maksimum to 6, ponieważ sekundy, minuty, godziny, dni, miesiące, lata tworzą tylko 6 typów). Poprawi również problemy gramatyczne, takie jak „minuty” w porównaniu z „minutami”, w zależności od tego, czy jest to 1 minuta czy więcej, tak samo dla wszystkich typów, a wygenerowany „ciąg znaków” będzie zawsze poprawny gramatycznie.

Oto kilka przykładów użycia: bAllowSegments określa, ile segmentów ma być pokazanych ... tj .: jeśli 3, to zwracany ciąg będzie (jako przykład) ... "3 years, 2 months and 13 days"(nie będzie zawierał godzin, minut i sekund jako 3 pierwszych godzin kategorie są zwracane), jeśli jednak data była nowszą datą, na przykład kilka dni temu, określenie tych samych segmentów (3) zostanie zwrócone "4 days, 1 hour and 13 minutes ago", więc bierze wszystko pod uwagę!

jeśli bAllowSegments jest równe 2, zwróci, "3 years and 2 months"a jeśli 6 (wartość maksymalna) zwróci "3 years, 2 months, 13 days, 13 hours, 29 minutes and 9 seconds", ale pamiętaj , że będzie to NEVER RETURNcoś takiego, "0 years, 0 months, 0 days, 3 hours, 2 minutes and 13 seconds ago"ponieważ rozumie, że nie ma danych daty w 3 pierwszych segmentach i je ignoruje, nawet jeśli określisz 6 segmentów więc nie martw się :). Oczywiście, jeśli istnieje segment zawierający 0, weźmie to pod uwagę podczas tworzenia ciągu i wyświetli się jako "3 days and 4 seconds ago"i ignorując część „0 godzin”! Ciesz się i komentuj, jeśli chcesz.

 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

Oczywiście będziesz potrzebować funkcji "ReplaceLast", która pobiera ciąg źródłowy i argument określający, co ma zostać zastąpione, oraz inny argument określający, czym chcesz go zastąpić, i zastępuje tylko ostatnie wystąpienie tego ciągu ... dołączyłem mój, jeśli go nie masz lub nie chcesz go wdrażać, więc oto on, będzie działał „tak jak jest” bez konieczności modyfikacji. Wiem, że funkcja reverseit nie jest już potrzebna (istnieje w .net), ale funkcja ReplaceLast i ReverseIt zostały przeniesione z dni pre-.net, więc proszę wybaczyć, jak może wyglądać datowanie (nadal działa w 100%, używałem od ponad dziesięciu lat, gwarantuje, że są wolne od błędów) ... :). Twoje zdrowie.

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

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

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

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

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

End Function 
Erx_VB.NExT.Coder
źródło
0

Jeśli chcesz uzyskać dokładną liczbę, nie możesz tylko na podstawie przedziału czasu, ponieważ musisz wiedzieć, z którymi miesiącami masz do czynienia i czy masz do czynienia z rokiem przestępnym, jak powiedziałeś.

Albo podaj przybliżoną liczbę, albo spróbuj trochę posłuchać oryginalnych DateTimes

Rik
źródło
0

Nie ma wbudowanego sposobu, aby zrobić to dokładnie w idiomatycznym c #. Istnieją pewne obejścia, takie jak ten przykład CodeProject, który ludzie kodowali.

Matt
źródło
0

Jeśli masz do czynienia z miesiącami i latami, potrzebujesz czegoś, co wie, ile dni ma każdy miesiąc i które lata są latami przestępnymi.

Wejdź do kalendarza gregoriańskiego (i innych implementacji kalendarza specyficznych dla kultury ).

Chociaż Kalendarz nie udostępnia metod bezpośredniego obliczania różnicy między dwoma punktami w czasie, ma takie metody, jak

DateTime AddWeeks(DateTime time, int weeks)
DateTime AddMonths(DateTime time, int months)
DateTime AddYears(DateTime time, int years)
mattk
źródło
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;
SUMIT
źródło
0

Metoda zwraca listę zawierającą 3 elementy, pierwszy to rok, drugi to miesiąc, a element końcowy to dzień:

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

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

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

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

            if (fD > tD)
            {
                tM--;

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

            if (fM > tM)
            {
                tY--;

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

            dY = tY - fY;

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

            return null;
        }
    }
Alireza
źródło
0

Oto mój wkład w uzyskanie różnicy w miesiącach, które uznałem za dokładne:

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

Stosowanie:

Int32 months = DateTime.Now.DiffMonths( DateTime.Now.AddYears( 5 ) );

Możesz utworzyć inną metodę o nazwie DiffYears i zastosować dokładnie taką samą logikę jak powyżej i AddYears zamiast AddMonths w pętli while.

Morgs
źródło
0

Spóźniłem się do gry, ale myślę, że może to komuś pomóc. Większość ludzi ma tendencję do dokonywania pomiarów z miesiąca na miesiąc, z wyłączeniem faktu, że miesiące występują w różnych wariantach. Korzystając z tego schematu myślowego, stworzyłem jedną linijkę, która porównuje dla nas daty. Stosując następujący proces.

  1. Każde # lat powyżej 1 podczas porównywania roku zostanie pomnożone przez 12, nie ma przypadku, w którym może to być mniej niż 1 pełny rok.
  2. Jeśli rok końcowy jest dłuższy, musimy ocenić, czy bieżący dzień jest większy lub równy dniu poprzedniego 2A. Jeśli dzień końcowy jest większy lub równy, bierzemy bieżący miesiąc, a następnie dodajemy 12 miesięcy, odejmując miesiąc miesiąca początkowego 2B. Jeśli dzień zakończenia jest mniejszy niż dzień rozpoczęcia, wykonujemy to samo, co powyżej, z wyjątkiem tego, że dodajemy 1 do miesiąca początkowego przed odjęciem
  3. Jeśli rok końcowy nie jest większy, wykonujemy to samo, co 2A / 2B, ale bez dodawania 12 miesięcy, ponieważ nie musimy oceniać po roku.

        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)));
    
Ścięgno
źródło
Śmierć przez potrójną śmierć?
SpaceBison,
0

Moje podejście do tej odpowiedzi również wykorzystuje metodę rozszerzenia , ale może ona zwrócić wynik pozytywny lub negatywny.

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

Kilka testów:

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

Łącząc dwie powyższe odpowiedzi, inną metodą rozszerzenia jest:

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

Dzięki @AdamRobinson i @MarkWhittaker

Peter Smith
źródło
0

Oto jak to zrobiliśmy:

int DifferenceInMonth(DateTime startDate, DateTime endDate) {
    long ToTicks(DateTime date) => new DateTimeOffset(date).Ticks;
    var daysPerMonth = 30.4;
    return (int)Math.Round((ToTicks(endDate) - ToTicks(startDate)) / TimeSpan.TicksPerDay / daysPerMonth);
}
LineloDude
źródło
-1

Oblicz liczbę miesięcy między 2 datami:

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

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

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

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

echo $joining_months = (($year2 - $year1) * 12) + ($month2 - $month1);
Kamlesh
źródło
2
To jest PHP, a nie C #.
AFract