[c#] Wie erhält man den Unterschied zwischen zwei Daten in Jahr / Monat / Woche / Tag?


9 Answers

Zum Teil als Vorbereitung für den Versuch, diese Frage richtig zu beantworten (und vielleicht sogar definitiv ...), zum Teil um zu untersuchen, wie sehr man Code, der auf SO geklebt wird, und zum Teil als Übung um Fehler zu finden, habe ich eine Menge erstellt Unit-Tests für diese Frage, und sie auf viele vorgeschlagene Lösungen von dieser Seite und ein paar Duplikate angewendet.

Die Ergebnisse sind schlüssig: Kein einziger der Code-Beiträge beantwortet die Frage exakt. Update: Ich habe jetzt vier richtige Lösungen für diese Frage, einschließlich meiner eigenen, siehe Updates unten.

Code getestet

Von dieser Frage habe ich Code von den folgenden Benutzern getestet: Mohammed Ijas Nasirudeen, Ruffin, Malu MN, Dave, pk., Jani, LC.

Dies waren alle Antworten, die alle drei Jahre, Monate und Tage in ihrem Code enthalten. Beachten Sie, dass zwei von ihnen, Dave und Jani, die Gesamtzahl der Tage und Monate angegeben haben und nicht die Gesamtzahl der Monate, die nach dem Zählen der Jahre übrig geblieben sind, und die Gesamtzahl der verbleibenden Tage nach dem Zählen der Monate. Ich denke, die Antworten stimmen nicht mit dem, was das OP zu haben scheint, aber die Unit-Tests sagen Ihnen in diesen Fällen offensichtlich nicht viel. (Beachten Sie, dass dies in Janis Fall mein Fehler war und sein Code tatsächlich korrekt war - siehe Update 4 unten)

Die Antworten von Jon Skeet, Aghalaseimani, Mukesh Kumar, Richard, Colin, Sheir, nur ich sah, Chalkey und Andy, waren unvollständig. Dies bedeutet nicht, dass die Antworten nicht gut waren, tatsächlich sind einige von ihnen nützliche Beiträge zu einer Lösung. Es bedeutet nur, dass es keinen Code gab, der zwei DateTime s DateTime und 3 int s zurückgab, die ich richtig testen konnte. Vier davon sprechen jedoch von TimeSpan . Wie viele Leute erwähnt haben, gibt TimeSpan keine TimeSpan zurück, die größer als Tage sind.

Die anderen Antworten, die ich getestet habe, waren von

  • Frage 3054715 - LukeH, ho1 und das. ___curious_geek
  • Frage 6260372 - Chuck Rosntance und Jani (gleiche Antwort wie diese Frage)
  • Frage 9 (!) - Dylan Hayes, Jon und Rajeshwaran SP

Diese .___ curious_geeks Antwort ist Code auf einer Seite, mit der er verlinkt ist, was er meiner Meinung nach nicht geschrieben hat. Janis Antwort ist die einzige, die eine externe Bibliothek, Time Period Library für .Net verwendet.

Alle anderen Antworten auf all diese Fragen schienen unvollständig zu sein. Frage 9 bezieht sich auf das Alter in Jahren, und die drei Antworten sind diejenigen, die die kurzen und berechneten Jahre, Monate und Tage überstiegen. Wenn jemand weitere Duplikate dieser Frage findet, lassen Sie es mich bitte wissen.

Wie ich getestet habe

Ganz einfach: Ich habe eine Schnittstelle erstellt

public interface IDateDifference
{
  void SetDates(DateTime start, DateTime end);
  int GetYears();
  int GetMonths();
  int GetDays();

}

Für jede Antwort habe ich eine Klasse geschrieben, die diese Schnittstelle implementiert und den kopierten und eingefügten Code als Grundlage verwendet. Natürlich musste ich Funktionen mit verschiedenen Signaturen anpassen, aber ich habe versucht, die minimalen Änderungen vorzunehmen, um den gesamten Logikcode zu erhalten.

Ich habe eine Reihe von NUnit-Tests in einer abstrakten generischen Klasse geschrieben

[TestFixture]
public abstract class DateDifferenceTests<DDC> where DDC : IDateDifference, new()

und fügte eine leere abgeleitete Klasse hinzu

public class Rajeshwaran_S_P_Test : DateDifferenceTests<Rajeshwaran_S_P>
{
}

in die Quelldatei für jede IDateDifference Klasse.

NUnit ist clever genug, um den Rest zu erledigen.

Die Tests

Ein paar davon wurden im Voraus geschrieben und der Rest wurde geschrieben, um scheinbar funktionierende Implementierungen zu durchbrechen.

[TestFixture]
public abstract class DateDifferenceTests<DDC> where DDC : IDateDifference, new()
{
  protected IDateDifference ddClass;

  [SetUp]
  public void Init()
  {
    ddClass = new DDC();
  }

  [Test]
  public void BasicTest()
  {
    ddClass.SetDates(new DateTime(2012, 12, 1), new DateTime(2012, 12, 25));
    CheckResults(0, 0, 24);
  }

  [Test]
  public void AlmostTwoYearsTest()
  {
    ddClass.SetDates(new DateTime(2010, 8, 29), new DateTime(2012, 8, 14));
    CheckResults(1, 11, 16);
  }

  [Test]
  public void AlmostThreeYearsTest()
  {
    ddClass.SetDates(new DateTime(2009, 7, 29), new DateTime(2012, 7, 14));
    CheckResults(2, 11, 15);
  }

  [Test]
  public void BornOnALeapYearTest()
  {
    ddClass.SetDates(new DateTime(2008, 2, 29), new DateTime(2009, 2, 28));
    CheckControversialResults(0, 11, 30, 1, 0, 0);
  }

  [Test]
  public void BornOnALeapYearTest2()
  {
    ddClass.SetDates(new DateTime(2008, 2, 29), new DateTime(2009, 3, 1));
    CheckControversialResults(1, 0, 0, 1, 0, 1);
  }


  [Test]
  public void LongMonthToLongMonth()
  {
    ddClass.SetDates(new DateTime(2010, 1, 31), new DateTime(2010, 3, 31));
    CheckResults(0, 2, 0);
  }

  [Test]
  public void LongMonthToLongMonthPenultimateDay()
  {
    ddClass.SetDates(new DateTime(2009, 1, 31), new DateTime(2009, 3, 30));
    CheckResults(0, 1, 30);
  }

  [Test]
  public void LongMonthToShortMonth()
  {
    ddClass.SetDates(new DateTime(2009, 8, 31), new DateTime(2009, 9, 30));
    CheckControversialResults(0, 1, 0, 0, 0, 30);
  }

  [Test]
  public void LongMonthToPartWayThruShortMonth()
  {
    ddClass.SetDates(new DateTime(2009, 8, 31), new DateTime(2009, 9, 10));
    CheckResults(0, 0, 10);
  }

  private void CheckResults(int years, int months, int days)
  {
    Assert.AreEqual(years, ddClass.GetYears());
    Assert.AreEqual(months, ddClass.GetMonths());
    Assert.AreEqual(days, ddClass.GetDays());
  }

  private void CheckControversialResults(int years, int months, int days,
    int yearsAlt, int monthsAlt, int daysAlt)
  {
    // gives the right output but unhelpful messages
    bool success = ((ddClass.GetYears() == years
                     && ddClass.GetMonths() == months
                     && ddClass.GetDays() == days)
                    ||
                    (ddClass.GetYears() == yearsAlt
                     && ddClass.GetMonths() == monthsAlt
                     && ddClass.GetDays() == daysAlt));

    Assert.IsTrue(success);
  }
}

Die meisten Namen sind leicht albern und erklären nicht wirklich, warum Code den Test nicht bestehen könnte. Allerdings sollten die beiden Daten und die Antwort (en) ausreichen, um den Test zu verstehen.

Es gibt zwei Funktionen, die alle Assert , CheckResults() und CheckControversialResults() . Diese funktionieren gut, um Tipparbeit zu sparen und die richtigen Ergebnisse zu Assert , aber leider machen sie es schwieriger, genau zu sehen, was schiefgelaufen ist (weil das Assert in CheckControversialResults() mit "Expected true" fehlschlägt und Ihnen nicht sagt, welcher Wert falsch war Jeder hat einen besseren Weg, dies zu tun (vermeiden Sie es, jedes Mal die gleichen Checks zu schreiben, aber haben Sie mehr nützliche Fehlermeldungen), lassen Sie es mich wissen.

CheckControversialResults() wird für ein paar Fälle verwendet, in denen es scheinbar zwei verschiedene Meinungen gibt, was richtig ist. Ich habe eine eigene Meinung, aber ich dachte, ich sollte in dem, was ich hier akzeptiere, liberal sein. Der Kern davon ist zu entscheiden, ob ein Jahr nach dem 29. Februar 28. Februar oder 1. März ist.

Diese Tests sind der Kern der Sache, und es könnte sehr wohl Fehler in ihnen geben, also kommentiere bitte, wenn du einen Fehler findest. Es wäre auch gut, einige Vorschläge für andere Tests zu hören, um zukünftige Iterationen von Antworten zu überprüfen.

Kein Test betrifft die Tageszeit - alle DateTime s sind um Mitternacht. Einschließlich der Zeiten, wenn es klar ist, wie das Aufrunden und Abarbeiten von Tagen funktioniert (ich denke, es ist), könnten noch mehr Fehler auftauchen.

Die Ergebnisse

Die vollständige Anzeigetafel der Ergebnisse lautet wie folgt:

ChuckRostance_Test 3 failures               S S S F S S F S F
Dave_Test 6 failures                        F F S F F F F S S
Dylan_Hayes_Test 9 failures                 F F F F F F F F F
ho1_Test 3 failures                         F F S S S S F S S
Jani_Test 6 failures                        F F S F F F F S S
Jon_Test 1 failure                          S S S S S S F S S
lc_Test 2 failures                          S S S S S F F S S
LukeH_Test 1 failure                        S S S S S S F S S
Malu_MN_Test 1 failure                      S S S S S S S F S
Mohammed_Ijas_Nasirudeen_Test 2 failures    F S S F S S S S S
pk_Test 6 failures                          F F F S S F F F S
Rajeshwaran_S_P_Test 7 failures             F F S F F S F F F
ruffin_Test 3 failures                      F S S F S S F S S
this_curious_geek_Test 2 failures           F S S F S S S S S

Aber beachte, dass Janis Lösung tatsächlich korrekt war und alle Tests bestanden hat - siehe Update 4 unten.

Die Spalten sind in alphabetischer Reihenfolge des Testnamens:

  • FastThreeYearsTest
  • FastTwoYearsTest
  • Basistest
  • BornOnALeapYearTest
  • BornOnALeapYearTest2
  • LongMonthToLongMonth
  • LongMonthToLongMonthPenultimateDay
  • LongMonthToPartWayThruShortMonth
  • LongMonthToShortMonth

Drei Antworten scheiterten jeweils nur an 1 Test, Jon's, LukeHs und Manu MN's. Denken Sie daran, dass diese Tests wahrscheinlich speziell geschrieben wurden, um Fehler in diesen Antworten zu beheben.

Jeder Test wurde von mindestens einem Codeabschnitt bestanden, was etwas beruhigend ist, dass keiner der Tests fehlerhaft ist.

Einige Antworten haben viele Tests nicht bestanden. Ich hoffe, niemand fühlt, dass dies eine Verurteilung der Bemühungen dieses Posters ist. Erstens ist die Anzahl der Erfolge ziemlich willkürlich, da die Tests die Problembereiche des Fragebereichs nicht gleichmäßig abdecken. Zweitens ist das kein Produktionscode - die Antworten werden gepostet, damit die Leute von ihnen lernen können und sie nicht genau in ihre Programme kopieren. Code, der viele Tests nicht besteht, kann immer noch gute Ideen haben. Mindestens ein Stück, das viele Tests nicht bestanden hat, hatte einen kleinen Fehler, den ich nicht beheben konnte. Ich bin jedem dankbar, der sich die Zeit genommen hat, seine Arbeit mit allen anderen zu teilen, um dieses Projekt so interessant zu machen.

Meine Schlussfolgerungen

Dort sind drei:

  1. Kalender sind schwer. Ich schrieb neun Tests, drei davon, wo zwei Antworten möglich sind. Einige der Tests, bei denen ich nur eine Antwort hatte, stimmen vielleicht nicht einstimmig überein. Genau darüber nachzudenken, was wir meinen, wenn wir '1 Monat später' oder '2 Jahre früher' sagen, ist in vielen Situationen schwierig. Und keiner von diesem Code musste sich mit all den Komplexitäten von Dingen befassen, wie zum Beispiel, wenn Schaltjahre sind. Alles verwendet Bibliothekscode, um Daten zu verarbeiten. Wenn Sie sich die "Spezifikation" für die Zeit in Tagen, Wochen, Monaten und Jahren vorstellen, gibt es allerhand Gruft. Weil wir es seit der Grundschule gut kennen und es jeden Tag benutzen, sind wir blind für viele Eigenheiten. Die Frage ist nicht akademisch - verschiedene Arten der Zerlegung von Zeiträumen in Jahre, Quartale und Monate sind in der Buchhaltungssoftware für Anleihen und andere Finanzprodukte unerlässlich.

  2. Korrekten Code zu schreiben ist schwer. Es gab viele Bugs. In etwas obskureren Themen oder weniger populären Fragen sind die Chancen, dass ein Fehler existiert, ohne dass ein Kommentator darauf hingewiesen hat, viel höher als bei dieser Frage. Sie sollten niemals wirklich Code von SO in Ihr Programm kopieren, ohne genau zu verstehen, was es tut. Die Kehrseite davon ist, dass Sie in Ihrer Antwort wahrscheinlich keinen Code schreiben sollten, der kopiert und eingefügt werden kann, sondern intelligenten und expressiven Pseudocode, der es jemandem ermöglicht, die Lösung zu verstehen und ihre eigene Version (mit ihren eigenen Fehlern) zu implementieren !)

  3. Unit-Tests sind hilfreich. Ich habe immer noch die Absicht, meine eigene Lösung dafür zu veröffentlichen, wenn ich dazu komme (damit jemand anderes die versteckten, falschen Annahmen findet!) Dies zu tun war ein großartiges Beispiel dafür, die Fehler zu retten, indem man sie in Unit-Tests verwandelte fixiere die nächste Version des Codes mit.

Aktualisieren

Das ganze Projekt ist jetzt unter https://github.com/jwg4/date-difference Dazu gehört mein eigener Versuch jwg.cs , der alle Tests besteht, die ich derzeit habe, einschließlich einiger neuer Tests, die die korrekte Uhrzeit der Verarbeitung überprüfen . Fühlen Sie sich frei, entweder weitere Tests hinzuzufügen, um diese und andere Implementierungen zu brechen, oder besser Code, um die Frage zu beantworten.

Update 2

@MattJohnson hat eine Implementierung hinzugefügt, die Jon Skeets NodaTime verwendet. Es besteht alle aktuellen Tests.

Update 3

@ KirkWolls Antwort auf Differenz in Monaten zwischen zwei Daten wurde dem Projekt auf github hinzugefügt. Es besteht alle aktuellen Tests.

Update 4

@Jani wies in einem Kommentar darauf hin, dass ich seinen Code falsch benutzt habe. Er schlug Methoden vor, die die Jahre, Monate und Tage korrekt zählten (neben einigen, die die Gesamtzahl der Tage und Monate zählen, nicht die Reste), aber ich habe fälschlicherweise die falschen in meinem Testcode verwendet. Ich habe meinen Wrapper um seinen Code korrigiert und er besteht nun alle Tests. Es gibt jetzt vier richtige Lösungen, von denen Janis die erste war. Zwei verwenden Bibliotheken (Intenso.TimePeriod und NodaTime) und zwei werden von Grund auf neu geschrieben.

Question

Wie kann man effizient zwischen zwei Daten in Jahr / Monat / Woche / Tag unterscheiden?

z.B. Unterschied zwischen zwei Daten ist 1 Jahr, 2 Monate, 3 Wochen, 4 Tage.

Differenz stellt die Anzahl der Jahres (e), Monat (e), Woche (n) und Tage (n) zwischen zwei Daten dar.




Subtrahieren Sie zwei DateTime Instanzen, um eine TimeSpan mit einer Days Eigenschaft zu erhalten. (ZB in PowerShell):

PS > ([datetime]::today - [datetime]"2009-04-07")


Days              : 89
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 0
Ticks             : 76896000000000
TotalDays         : 89
TotalHours        : 2136
TotalMinutes      : 128160
TotalSeconds      : 7689600
TotalMilliseconds : 7689600000

Die Umwandlung von Tagen in Jahre oder Wochen ist relativ einfach (Tage in einem Jahr können 365, 365,25, ... sein, je nach Kontext). Monate sind viel schwieriger, weil Sie ohne Basisdatum nicht wissen, welche Monatslängen gelten.

Angenommen, Sie möchten mit Ihrem Basisdatum beginnen, können Sie inkrementell subtrahieren, während Sie die ersten Jahre zählen (auf Schaltjahre prüfen), dann die Monatslängen (Indexierung von startDate.Month), dann Wochen (verbleibende Tage geteilt durch 7) und dann Tage (Rest ).

Es gibt viele Randfälle zu berücksichtigen, zB 2005-03-01 ist ein Jahr ab 2004-03-01 und ab 2004-02-29 abhängig davon, was Sie unter "Jahr" verstehen.




Schaltjahre und ungleiche Monate machen das eigentlich zu einem nicht-trivialen Problem. Ich bin mir sicher, dass jemand einen effizienteren Weg finden kann, aber hier ist eine Option - zuerst auf der kleinen Seite approximieren und dann anpassen (ungetestet):

public static void GetDifference(DateTime date1, DateTime date2, out int Years, 
    out int Months, out int Weeks, out int Days)
{
    //assumes date2 is the bigger date for simplicity

    //years
    TimeSpan diff = date2 - date1;
    Years = diff.Days / 366;
    DateTime workingDate = date1.AddYears(Years);

    while(workingDate.AddYears(1) <= date2)
    {
        workingDate = workingDate.AddYears(1);
        Years++;
    }

    //months
    diff = date2 - workingDate;
    Months = diff.Days / 31;
    workingDate = workingDate.AddMonths(Months);

    while(workingDate.AddMonths(1) <= date2)
    {
        workingDate = workingDate.AddMonths(1);
        Months++;
    }

    //weeks and days
    diff = date2 - workingDate;
    Weeks = diff.Days / 7; //weeks always have 7 days
    Days = diff.Days % 7;
}



Based on Joaquim's answer, but fixing the calculation when end date month is less than start date month, and adding code to handle end date before start date:

        public static class GeneralHelper
        {
          public static int GetYears(DateTime startDate, DateTime endDate)
            {
                if (endDate < startDate)
                    return -GetYears(endDate, startDate);

                int years = (endDate.Year - startDate.Year);

                if (endDate.Year == startDate.Year)
                    return years;

                if (endDate.Month < startDate.Month)
                    return years - 1;

                if (endDate.Month == startDate.Month && endDate.Day < startDate.Day)
                    return years - 1;

                return years;
            }

            public static int GetMonths(DateTime startDate, DateTime endDate)
            {
                if (startDate > endDate)
                    return -GetMonths(endDate, startDate);

                int months = 12 * GetYears(startDate, endDate);

                if (endDate.Month > startDate.Month)
                    months = months + endDate.Month - startDate.Month;
                else
                    months = 12 - startDate.Month + endDate.Month;

                if (endDate.Day < startDate.Day)
                    months = months - 1;

                return months;
            }
       }
    [TestClass()]
    public class GeneralHelperTest
    {
            [TestMethod]
            public void GetYearsTest()
            {
                Assert.AreEqual(0, GeneralHelper.GetYears(new DateTime(2000, 5, 5), new DateTime(2000, 12, 31)));
                Assert.AreEqual(0, GeneralHelper.GetYears(new DateTime(2000, 5, 5), new DateTime(2001, 4, 4)));
                Assert.AreEqual(0, GeneralHelper.GetYears(new DateTime(2000, 5, 5), new DateTime(2001, 5, 4)));
                Assert.AreEqual(1, GeneralHelper.GetYears(new DateTime(2000, 5, 5), new DateTime(2001, 5, 5)));
                Assert.AreEqual(1, GeneralHelper.GetYears(new DateTime(2000, 5, 5), new DateTime(2001, 12, 31)));

                Assert.AreEqual(0, GeneralHelper.GetYears(new DateTime(2000, 12, 31), new DateTime(2000, 5, 5)));
                Assert.AreEqual(0, GeneralHelper.GetYears(new DateTime(2001, 4, 4), new DateTime(2000, 5, 5)));
                Assert.AreEqual(0, GeneralHelper.GetYears(new DateTime(2001, 5, 4), new DateTime(2000, 5, 5)));
                Assert.AreEqual(-1, GeneralHelper.GetYears(new DateTime(2001, 5, 5), new DateTime(2000, 5, 5)));
                Assert.AreEqual(-1, GeneralHelper.GetYears(new DateTime(2001, 12, 31), new DateTime(2000, 5, 5)));
            }

            [TestMethod]
            public void GetMonthsTest()
            {
                Assert.AreEqual(0, GeneralHelper.GetMonths(new DateTime(2000, 5, 5), new DateTime(2000, 6, 4)));
                Assert.AreEqual(1, GeneralHelper.GetMonths(new DateTime(2000, 5, 5), new DateTime(2000, 6, 5)));
                Assert.AreEqual(1, GeneralHelper.GetMonths(new DateTime(2000, 5, 5), new DateTime(2000, 6, 6)));
                Assert.AreEqual(11, GeneralHelper.GetMonths(new DateTime(2000, 5, 5), new DateTime(2001, 5, 4)));
                Assert.AreEqual(12, GeneralHelper.GetMonths(new DateTime(2000, 5, 5), new DateTime(2001, 5, 5)));
                Assert.AreEqual(13, GeneralHelper.GetMonths(new DateTime(2000, 5, 5), new DateTime(2001, 6, 6)));

                Assert.AreEqual(0, GeneralHelper.GetMonths(new DateTime(2000, 6, 4), new DateTime(2000, 5, 5)));
                Assert.AreEqual(-1, GeneralHelper.GetMonths(new DateTime(2000, 6, 5), new DateTime(2000, 5, 5)));
                Assert.AreEqual(-1, GeneralHelper.GetMonths(new DateTime(2000, 6, 6), new DateTime(2000, 5, 5)));
                Assert.AreEqual(-11, GeneralHelper.GetMonths(new DateTime(2001, 5, 4), new DateTime(2000, 5, 5)));
                Assert.AreEqual(-12, GeneralHelper.GetMonths(new DateTime(2001, 5, 5), new DateTime(2000, 5, 5)));
                Assert.AreEqual(-13, GeneralHelper.GetMonths(new DateTime(2001, 6, 6), new DateTime(2000, 5, 5)));
            }
   }

EDIT No that still doesn't work. It fails this test:

Assert.AreEqual(24, GeneralHelper.GetMonths(new DateTime(2000, 5, 5), new DateTime(2003, 5, 5)));

Expected:<24>. Actual:<12>




Nun, @ Jon Skeet, wenn wir uns keine Sorgen machen, granularer zu werden als Tage (und immer noch rollende Tage in größere Einheiten anstatt eine totale Anzahl an Tagen zu haben), wie es beim OP der Fall ist, ist es wirklich nicht so schwierig in C #. Was die Datums-Mathematik so schwierig macht, ist, dass sich die Anzahl der Einheiten in jeder zusammengesetzten Einheit oft ändert. Stellen Sie sich vor, wenn jede 3. Gallone Gas nur 3 Quarts war, aber jeder 12. war 7, außer an Freitagen, wenn ...

Zum Glück sind Daten nur eine lange Fahrt durch die größte Integer-Funktion . Diese verrückten Ausnahmen sind unerträglich, es sei denn, du hast den ganzen Weg durch die verrückten Einheiten gegangen, wenn es keine große Sache mehr ist. Wenn Sie am 12/25/1900 geboren sind, sind Sie immer noch EXAKT 100 am 12/25/2000, unabhängig von den Schaltjahren oder Sekunden oder Sommerzeiten, die Sie durchgemacht haben. Sobald Sie die Prozentsätze durchlaufen haben, die die letzte zusammengesetzte Einheit ausmachen, sind Sie wieder zur Einheit. Du hast eins hinzugefügt und kannst von vorn beginnen.

Was ist nur zu sagen, dass, wenn du Jahre bis Monate bis Tage machst, die einzige seltsam zusammengesetzte Einheit der Monat (der Tage) ist. Wenn Sie von dem Monatswert ausleihen müssen, um einen Ort abzuwickeln, an dem Sie mehr Tage subtrahieren als Sie haben, müssen Sie nur die Anzahl der Tage im vorherigen Monat wissen. Keine anderen Ausreißer sind wichtig.

Und C # gibt Ihnen das in System.DateTime.DaysInMonth (intYear, intMonth).

(Wenn Ihr Now-Monat kleiner als Ihr Then-Monat ist, gibt es kein Problem. Jedes Jahr hat 12 Monate.)

Und das Gleiche gilt, wenn wir granularer vorgehen ... Sie müssen nur wissen, wie viele (kleine Einheiten) in der letzten (zusammengesetzten Einheit) sind. Sobald Sie vorbei sind, erhalten Sie einen weiteren ganzzahligen Wert mehr (zusammengesetzte Einheit). Dann subtrahiere, wie viele kleine Einheiten du vermisst hast, wo du dann begonnen hast und addiere zurück, wie viele von denen du durch den Zusammenbruch mit deinem Jetzt gegangen bist.

Also hier ist, was ich von meinem ersten Schnitt habe, wenn ich zwei Daten abziehe. Es könnte funktionieren. Hoffentlich nützlich.

(BEARBEITEN: Geändert NewMonth> OldMonth check zu NewMonth> = OldMonth, da wir keine leihen müssen, wenn die Monate gleich sind (auch für Tage) Das heißt, der 11. November 2011 minus 9. November 2010 gab -1 Jahr , 12 Monate, 2 Tage (dh 2 Tage, aber das königliche, das wir ausgeliehen haben, als das Königtum nicht nötig war.)

(EDIT: Ich musste nach Monat = Monat suchen, wenn wir Tage borgen mussten, um einen Tag von Tag zu Tag zu subtrahieren, da wir ein Jahr subtrahieren mussten, um 11 Monate und die zusätzlichen Tage zu bekommen Okay, also da sind ein paar Ausreißer.; D Ich glaube, ich bin jetzt nah dran.

private void Form1_Load(object sender, EventArgs e) {
DateTime dteThen = DateTime.Parse("3/31/2010");
DateTime dteNow = DateTime.Now;

int intDiffInYears = 0;
int intDiffInMonths = 0;
int intDiffInDays = 0;


if (dteNow.Month >= dteThen.Month)
{
    if (dteNow.Day >= dteThen.Day)
    {   // this is a best case, easy subtraction situation
        intDiffInYears = dteNow.Year - dteThen.Year;
        intDiffInMonths = dteNow.Month - dteThen.Month;
        intDiffInDays = dteNow.Day - dteThen.Day;
    }
    else
    {   // else we need to substract one from the month diff (borrow the one)
        // and days get wacky.

        // Watch for the outlier of Month = Month with DayNow < DayThen, as then we've 
        // got to subtract one from the year diff to borrow a month and have enough
        // days to subtract Then from Now.
        if (dteNow.Month == dteThen.Month)
        {
            intDiffInYears = dteNow.Year - dteThen.Year - 1;
            intDiffInMonths = 11; // we borrowed a year and broke ONLY 
            // the LAST month into subtractable days
            // Stay with me -- because we borrowed days from the year, not the month,
            // this is much different than what appears to be a similar calculation below.
            // We know we're a full intDiffInYears years apart PLUS eleven months.
            // Now we need to know how many days occurred before dteThen was done with 
            // dteThen.Month.  Then we add the number of days we've "earned" in the current
            // month.  
            //
            // So 12/25/2009 to 12/1/2011 gives us 
            // 11-9 = 2 years, minus one to borrow days = 1 year difference.
            // 1 year 11 months - 12 months = 11 months difference
            // (days from 12/25 to the End Of Month) + (Begin of Month to 12/1) = 
            //                (31-25)                +       (0+1)              =
            //                   6                   +         1                = 
            //                                  7 days diff
            //
            // 12/25/2009 to 12/1/2011 is 1 year, 11 months, 7 days apart.  QED.

            int intDaysInSharedMonth = System.DateTime.DaysInMonth(dteThen.Year, dteThen.Month);
            intDiffInDays = intDaysInSharedMonth - dteThen.Day + dteNow.Day;
        }
        else
        {
            intDiffInYears = dteNow.Year - dteThen.Year;
            intDiffInMonths = dteNow.Month - dteThen.Month - 1;

            // So now figure out how many more days we'd need to get from dteThen's 
            // intDiffInMonth-th month to get to the current month/day in dteNow.
            // That is, if we're comparing 2/8/2011 to 11/7/2011, we've got (10/8-2/8) = 8
            // full months between the two dates.  But then we've got to go from 10/8 to
            // 11/07.  So that's the previous month's (October) number of days (31) minus
            // the number of days into the month dteThen went (8), giving the number of days
            // needed to get us to the end of the month previous to dteNow (23).  Now we
            // add back the number of days that we've gone into dteNow's current month (7)
            // to get the total number of days we've gone since we ran the greatest integer
            // function on the month difference (23 to the end of the month + 7 into the
            // next month == 30 total days.  You gotta make it through October before you 
            // get another month, G, and it's got 31 days).

            int intDaysInPrevMonth = System.DateTime.DaysInMonth(dteNow.Year, (dteNow.Month - 1));
            intDiffInDays = intDaysInPrevMonth - dteThen.Day + dteNow.Day;
        }
    }
}
else
{
    // else dteThen.Month > dteNow.Month, and we've got to amend our year subtraction
    // because we haven't earned our entire year yet, and don't want an obo error.
    intDiffInYears = dteNow.Year - dteThen.Year - 1;

    // So if the dates were THEN: 6/15/1999 and NOW: 2/20/2010...
    // Diff in years is 2010-1999 = 11, but since we're not to 6/15 yet, it's only 10.
    // Diff in months is (Months in year == 12) - (Months lost between 1/1/1999 and 6/15/1999
    // when dteThen's clock wasn't yet rolling == 6) = 6 months, then you add the months we
    // have made it into this year already.  The clock's been rolling through 2/20, so two months.
    // Note that if the 20 in 2/20 hadn't been bigger than the 15 in 6/15, we're back to the
    // intDaysInPrevMonth trick from earlier.  We'll do that below, too.
    intDiffInMonths = 12 - dteThen.Month + dteNow.Month;

    if (dteNow.Day >= dteThen.Day)
    {
        intDiffInDays = dteNow.Day - dteThen.Day;
    }
    else
    {
        intDiffInMonths--;  // subtract the month from which we're borrowing days.

        // Maybe we shoulda factored this out previous to the if (dteNow.Month > dteThen.Month)
        // call, but I think this is more readable code.
        int intDaysInPrevMonth = System.DateTime.DaysInMonth(dteNow.Year, (dteNow.Month - 1));
        intDiffInDays = intDaysInPrevMonth - dteThen.Day + dteNow.Day;
    }

}

this.addToBox("Years: " + intDiffInYears + " Months: " + intDiffInMonths + " Days: " + intDiffInDays); // adds results to a rich text box.

}



I have below solution which works correctly for me(After doing some Test cases). Extra Test cases are acceptable.

public class DateDiffernce
{
    private int m_nDays = -1;
    private int m_nWeek;
    private int m_nMonth = -1;
    private int m_nYear;

    public int Days
    {
        get
        {
            return m_nDays;
        }
    }

    public int Weeks
    {
        get
        {
            return m_nWeek;
        }
    }

    public int Months
    {
        get
        {
            return m_nMonth;
        }
    }

    public int Years
    {
        get
        {
            return m_nYear;
        }
    }

    public void GetDifferenceBetwwenTwoDate(DateTime objDateTimeFromDate, DateTime objDateTimeToDate)
    {
        if (objDateTimeFromDate.Date > objDateTimeToDate.Date)
        {
            DateTime objDateTimeTemp = objDateTimeFromDate;
            objDateTimeFromDate = objDateTimeToDate;
            objDateTimeToDate = objDateTimeTemp;
        }

        if (objDateTimeFromDate == objDateTimeToDate)
        {
            //textBoxDifferenceDays.Text = " Same dates";
            //textBoxAllDifference.Text = " Same dates";
            return;
        }

        // If From Date's Day is bigger than borrow days from previous month
        // & then subtract.
        if (objDateTimeFromDate.Day > objDateTimeToDate.Day)
        {
            objDateTimeToDate = objDateTimeToDate.AddMonths(-1);
            int nMonthDays = DateTime.DaysInMonth(objDateTimeToDate.Year, objDateTimeToDate.Month);
            m_nDays = objDateTimeToDate.Day + nMonthDays - objDateTimeFromDate.Day;

        }

        // If From Date's Month is bigger than borrow 12 Month 
        // & then subtract.
        if (objDateTimeFromDate.Month > objDateTimeToDate.Month)
        {
            objDateTimeToDate = objDateTimeToDate.AddYears(-1);
            m_nMonth = objDateTimeToDate.Month + 12 - objDateTimeFromDate.Month;

        }

       //Below are best cases - simple subtraction
        if (m_nDays == -1)
        {
            m_nDays = objDateTimeToDate.Day - objDateTimeFromDate.Day;
        }

        if (m_nMonth == -1)
        {
            m_nMonth = objDateTimeToDate.Month - objDateTimeFromDate.Month;
        }

        m_nYear = objDateTimeToDate.Year - objDateTimeFromDate.Year;
        m_nWeek = m_nDays / 7;
        m_nDays = m_nDays % 7;    
    }
}



Use Noda Time :

var ld1 = new LocalDate(2012, 1, 1);
var ld2 = new LocalDate(2013, 12, 25);
var period = Period.Between(ld1, ld2);

Debug.WriteLine(period);        // "P1Y11M24D"  (ISO8601 format)
Debug.WriteLine(period.Years);  // 1
Debug.WriteLine(period.Months); // 11
Debug.WriteLine(period.Days);   // 24



    Console.WriteLine("Enter your Date of Birth to Know your Current age in DD/MM/YY Format");
    string str = Console.ReadLine();
    DateTime dt1 = DateTime.Parse(str);
    DateTime dt2 = DateTime.Parse("10/06/2012");
    int result = (dt2 - dt1).Days;
    result = result / 365;
    Console.WriteLine("Your Current age is {0} years.",result);



If you have to find the difference between originalDate and today's date, Here is a reliable algorithm without so many condition checks.

  1. Declare a intermediateDate variable and initialize to the originalDate
  2. Find difference between years.(yearDiff)
  3. Add yearDiff to intermediateDate and check whether the value is greater than today's date.
  4. If newly obtained intermediateDate > today's date adjust the yearDiff and intermediateDate by one.
  5. Continue above steps for month and Days.

I have used System.Data.Linq functions to do find the year, month and day differences. Please find c# code below

        DateTime todaysDate = DateTime.Now;
        DateTime interimDate = originalDate;

        ///Find Year diff
        int yearDiff = System.Data.Linq.SqlClient.SqlMethods.DateDiffYear(interimDate, todaysDate);
        interimDate = interimDate.AddYears(yearDiff);
        if (interimDate > todaysDate)
        {
            yearDiff -= 1;
            interimDate = interimDate.AddYears(-1);
        }

        ///Find Month diff
        int monthDiff = System.Data.Linq.SqlClient.SqlMethods.DateDiffMonth(interimDate, todaysDate);
        interimDate = interimDate.AddMonths(monthDiff);
        if (interimDate > todaysDate)
        {
            monthDiff -= 1;
            interimDate = interimDate.AddMonths(-1);
        }

        ///Find Day diff
        int daysDiff = System.Data.Linq.SqlClient.SqlMethods.DateDiffDay(interimDate, todaysDate);



I came across this post while looking to solve a similar problem. I was trying to find the age of an animal in units of Years, Months, Weeks, and Days. Those values are then displayed in SpinEdits where the user can manually change the values to find/estimate a birth date. When my form was passed a birth date from a month with less than 31 days, the value calculated was 1 day off. I based my solution off of Ic's answer above.

Main calculation method that is called after my form loads.

        birthDateDisplay.Text = birthDate.ToString("MM/dd/yyyy");

        DateTime currentDate = DateTime.Now;

        Int32 numOfDays = 0; 
        Int32 numOfWeeks = 0;
        Int32 numOfMonths = 0; 
        Int32 numOfYears = 0; 

        // changed code to follow this model http://.com/posts/1083990/revisions
        //years 
        TimeSpan diff = currentDate - birthDate;
        numOfYears = diff.Days / 366;
        DateTime workingDate = birthDate.AddYears(numOfYears);

        while (workingDate.AddYears(1) <= currentDate)
        {
            workingDate = workingDate.AddYears(1);
            numOfYears++;
        }

        //months
        diff = currentDate - workingDate;
        numOfMonths = diff.Days / 31;
        workingDate = workingDate.AddMonths(numOfMonths);

        while (workingDate.AddMonths(1) <= currentDate)
        {
            workingDate = workingDate.AddMonths(1);
            numOfMonths++;
        }

        //weeks and days
        diff = currentDate - workingDate;
        numOfWeeks = diff.Days / 7; //weeks always have 7 days

        // if bday month is same as current month and bday day is after current day, the date is off by 1 day
        if(DateTime.Now.Month == birthDate.Month && DateTime.Now.Day < birthDate.Day)
            numOfDays = diff.Days % 7 + 1;
        else
            numOfDays = diff.Days % 7;

        // If the there are fewer than 31 days in the birth month, the date calculated is 1 off
        // Dont need to add a day for the first day of the month
        int daysInMonth = 0;
        if ((daysInMonth = DateTime.DaysInMonth(birthDate.Year, birthDate.Month)) != 31 && birthDate.Day != 1)
        {
            startDateforCalc = DateTime.Now.Date.AddDays(31 - daysInMonth);
            // Need to add 1 more day if it is a leap year and Feb 29th is the date
            if (DateTime.IsLeapYear(birthDate.Year) && birthDate.Day == 29)
                startDateforCalc = startDateforCalc.AddDays(1);
        }

        yearsSpinEdit.Value = numOfYears;
        monthsSpinEdit.Value = numOfMonths;
        weeksSpinEdit.Value = numOfWeeks;
        daysSpinEdit.Value = numOfDays;

And then, in my spinEdit_EditValueChanged event handler, I calculate the new birth date starting from my startDateforCalc based on the values in the spin edits. (SpinEdits are constrained to only allow >=0)

birthDate = startDateforCalc.Date.AddYears(-((Int32)yearsSpinEdit.Value)).AddMonths(-((Int32)monthsSpinEdit.Value)).AddDays(-(7 * ((Int32)weeksSpinEdit.Value) + ((Int32)daysSpinEdit.Value)));
birthDateDisplay.Text = birthDate.ToString("MM/dd/yyyy");

I know its not the prettiest solution, but it seems to be working for me for all month lengths and years.






Related