Hinfügen von Tagen, Monaten und Jahren zu einem Datum



  • Hey Leute!

    Ich schreibe gerade ein Programm, mit dem ich zu einem Datum eine verschiedene Anzahl an Tagen, Monaten und Jahren hinzufügen kann. Das Endergebnis ist dann natürlich ein neues gültiges(!) Datum. D.h. es sollten Schaltjahre, Anzahl der Tage im Monat etc. berücksichtigt werden. Es sollten keine fertigen Funktionen o.Ä. benutzt werden.

    Ich habe eine Klasse Date erstellt:

    class Date {
    public:
        //! Construct a date
        Date(int y, int m, int d);
    
        //! Set the date
        void set_date(int y, int m, int d);
    
        //! return year
        int year();
    
        //! return month
        int month();
    
        //! return date
        int day();
    
        //! add d days to the date
        void add_days(int d);
    
        //! add m months to the date
        void add_months(int m);
    
        //! add y years to the date
        void add_years(int y);
    
    private:
        //! store year
        int y_;
    
        //! store year
        int m_;
    
        //! store year
        int d_;
    };
    

    Ich denke, dass ich einer Erste Lösung zum add_months() habe:

    void Date::add_months(int m)
    {
      int new_y;
      int new_m;
    
      if (m < 0)
      {
        m = -m;
        new_y = Date::y_ - (m / 12);
    
        if (Date::m_ - m%12 <= 0)
          new_y--;
    
        new_m = 12 - ((12 + (m - Date::m_) % 12) % 12);
    
        m = -m;
      }
    
      if (m > 0)
      {
        new_y = Date::y_ + (m/12);
    
        if (Date::m_ + m % 12 >= 12)
          new_y++;
    
        new_m = ((12 + (m + Date::m_) % 12) % 12);
      }
    
      if (Date::d_ == 31 || Date::d_ == 30 || Date::d_ == 28 || Date::d_ == 29)
      {
        if (new_m == 2)
        {
          if (is_leap_year(new_y))
          {
            Date::d_ = 29;
          }
          else
          {
            Date::d_ = 28;
          }
        }
        else
        {
          if (is_even_month(new_m))
          {
            Date::d_ = 30;
          }
          else
          {
            Date::d_ = 31;
          }
        }
      }
    
      Date::m_ = new_m;
      Date::y_ = new_y;
    }
    

    is_leap_year(y) gibt true zurück, falls y um ein Schaltjahr handelt und is_even_month() gibt true zurück, falls m ein "gerader" Monat ist (also April, Juni, September, November).

    Ich habe verschiedene Monate addiert und subtrahiert und anschließend mit https://www.timeanddate.com/date/dateadd.html verglichen. Was meint ihr? Ist das so richtig bzw. kann man es noch verbessern?

    Ich würde mich über jegliche Tipps freuen bitte!

    Gruß
    c_boy



  • Den neuen zukünftigen Monat/Jahr sollte so viel kürzer gehen.

    new_y = Date::y_ + ((Date::m_ + m)/12);
    new_m =            ((Date::m_ + m)%12);
    

    Und das mit dem is_even_month solltest du dir nochmal überlegen.
    Betrachte einfach den aktuellen und den nächsten Monat (von diesem Beitrag)

    Hast du dir mal das Julianisches Datum angesehen?



  • Yeah, die 1000. Datumsbibliothek!

    Man kann dabei sooo viel falsch machen - verwende doch einfach das, was es in chrono gibt. Und schau dir dazu noch Howard Hinnants Bibliothek an: https://github.com/HowardHinnant/date (du brauchst nur den Header date.h zu kopieren)



  • Hey, danke für eure Antworten. Ich möchte mich erstmal nur auf eine Lösung, ohne zusätzliche Hilfsmittel wie andere Bibliotheken etc. konzentrieren.

    Ich habe mir im Wikipedia über das Juliansche Datum informiert und vorallem die Funktion

    add_days()
    

    geht damit viel einfacher und man müsste nicht per Schleife jeden Tag durchzählen, somit man auch Schaltjahre (Alle 4 Jahre) beachtet. Stimmts? D.h. im Prinzip ich verwende einfach die Umrechnung hier: https://de.wikipedia.org/wiki/Julianisches_Datum#Berechnung_aus_dem_Kalenderdatum

    Aber ich möchte die Zeit nicht berücksichtigen, d.h. ich setze das verwendete H einfach auf Null, so wie ichs in den Algorithmen unten gemacht habe. (einfach weglassen)

    Vom Gregorianischen Datum ins JD:

    wenn Monat > 2 dann Y = Jahr, M = Monat
    sonst Y = Jahr-1, M = Monat+12

    D = Tag

    wenn TT.MM.YYYY >= 15.10.1582
    dann gregorianischer Kalender: A = Int(Y/100), B = 2 - A + Int(A/4)
    sonst: "fehler"

    JD = Int(365,25*(Y+4716)) + Int(30,6001*(M+1)) + D + B - 1524,5

    Das komische ist, dass die Zeit H = Stunde/24 + Minute/1440 + Sekunde/86400 nicht einmal bei Konvertierung ins Gregorianische Datum berücksichtigt wird, oder?

    Vom JD ins Gregorianische Datum:

    Z = Int(JD + 0,5)
    F = Frac(JD + 0,5)

    wenn Z > 2299161 dann g = Int((Z - 1867216,25) / 36524,25) // Ergebnis gregorianisch
    A = Z + 1 + g - Int(g/4)
    sonst: ungültig

    B = A + 1524
    C = Int((B-122,1) / 365,25)
    D = Int(365,25 * C)
    E = Int((B-D) / 30,6001)

    Tag = B - D - Int(30,6001*E) + F // Tag, inklusive Tagesbruchteil

    wenn E<14 dann Monat = E - 1 // Monat
    sonst Monat = E - 13

    wenn Monat>2 dann Jahr = C - 4716 // Jahr
    sonst Jahr = C - 4715

    Letzte Frage: Also der 1. Schritt ist, ich konvertiere mein ursprüngliches Datum ins JD. Und wenn ich dann X Tage zu meinem ursprünglichem Datum hinzufügen will, addiere ich einfach JD und X und mit anschließender Rücktransformation erhalte ich mein gesuchtes Gregorianisches Datum?

    Denn laut Wikipedia gibt das JD die Zeit in Tagen seit 1. Januar −4712 an.


  • Mod

    C_Boy schrieb:

    Das komische ist, dass die Zeit H = Stunde/24 + Minute/1440 + Sekunde/86400 nicht einmal bei Konvertierung ins Gregorianische Datum berücksichtigt wird, oder?

    Nur bei deiner Variante, wo du sie richtigerweise schon raus genommen hast. Bei Wikipedia wird ganz am Ende noch H addiert.

    Letzte Frage: Also der 1. Schritt ist, ich konvertiere mein ursprüngliches Datum ins JD. Und wenn ich dann X Tage zu meinem ursprünglichem Datum hinzufügen will, addiere ich einfach JD und X und mit anschließender Rücktransformation erhalte ich mein gesuchtes Gregorianisches Datum?

    Ja.

    Denn laut Wikipedia gibt das JD die Zeit in Tagen seit 1. Januar −4712 an.

    Denn?



  • C_Boy schrieb:

    Das komische ist, dass die Zeit H = Stunde/24 + Minute/1440 + Sekunde/86400 nicht einmal bei Konvertierung ins Gregorianische Datum berücksichtigt wird, oder?

    Die diversen ,5 die ab und zu da auftauchen, berücksichtigen den Umstand, das das Julianische Datum ab 12:00 definiert ist.

    C_Boy schrieb:

    Letzte Frage: Also der 1. Schritt ist, ich konvertiere mein ursprüngliches Datum ins JD. Und wenn ich dann X Tage zu meinem ursprünglichem Datum hinzufügen will, addiere ich einfach JD und X und mit anschließender Rücktransformation erhalte ich mein gesuchtes Gregorianisches Datum?

    Ja, so ist das gedacht.

    C_Boy schrieb:

    Denn laut Wikipedia gibt das JD die Zeit in Tagen seit 1. Januar −4712 an.

    12:00 Uhr



  • C_Boy schrieb:

    ...

    class Date {
    public:
        //! Construct a date
        Date(int y, int m, int d);
    
        //! Set the date
        void set_date(int y, int m, int d);
    
        //! return year
        int year();
    
        //! return month
        int month();
    
        //! return date
        int day();
    
        //! add d days to the date
        void add_days(int d);
    
        //! add m months to the date
        void add_months(int m);
    
        //! add y years to the date
        void add_years(int y);
    
    private:
        //! store year
        int y_;
    
        //! store year
        int m_;
    
        //! store year
        int d_;
    };
    

    ...

    Wieso verwendest du eine class um dann direkt mit public zu beginnen? Nutze einfach struct und lasse das public weg. Damit entfernst du unnötige Informationen aus dem Code. Besser wäre es die privaten Variablen nach oben zu tun:

    class Date {
        //! store year
        int y_;
    
        //! store year
        int m_;
    
        //! store year
        int d_;
    
      public:
        //! Construct a date
        Date(int y, int m, int d);
    
        //! Set the date
        void set_date(int y, int m, int d);
    
        //! return year
        int year();
    
        //! return month
        int month();
    
        //! return date
        int day();
    
        //! add d days to the date
        void add_days(int d);
    
        //! add m months to the date
        void add_months(int m);
    
        //! add y years to the date
        void add_years(int y);
    };
    


  • asdsaddsasds schrieb:

    Wieso verwendest du eine class um dann direkt mit public zu beginnen?

    Weil das eine sehr übliche Variante ist.
    Klar, gibt viele Projekte die es anders machen (z.B. so wie du), aber auch genug die es genau so machen.

    Also class für alles was mehr als ein POD ist (also für alles was irgendwelche protected oder private Member hat).
    Und die Member dann einfach so sortiert dass die für den Benutzer der Klasse wichtigsten zuerst kommen. Also z.B. erstmal alle public Konstruktoren - schliesslich muss man ja erstmal ein Objekt haben bevor man was damit machen kann. D.h. üblicherweise erst alles was public ist und danach protected und ganz zum Schuss private.

    asdsaddsasds schrieb:

    Nutze einfach struct und lasse das public weg.

    Finde ich furchtbar. struct nehme ich nur für sachen die PODs sind oder POD-like. Sobald etwas protected oder private ist nehme ich immer class .

    asdsaddsasds schrieb:

    Damit entfernst du unnötige Informationen aus dem Code.

    Was unnötig ist und was nicht ist wohl sehr subjektiv. Wenn man in einem grösseren Projekt konsequent ein bestimmtes Muster verwendet wann man class und wann struct verwendet, dann kann man damit durchaus nützliche Information vermitteln.

    asdsaddsasds schrieb:

    Besser wäre es die privaten Variablen nach oben zu tun:

    Kann man so machen. Muss man aber nicht. Wieso findet sich immer jemand der seine persönliche Meinung zu solchen Themen als einzige Wahrheit predigt? Also von Themen wo es in der Community nichtmal ansatzweise nen Konsens gibt.



  • hustbaer schrieb:

    Was unnötig ist und was nicht ist wohl sehr subjektiv.

    Wenn der Source-Code compiliert und exakt das gleiche tut, ist es unnötige Information. Ich bin dazu gekommen es zu kommentieren weil es zunehmend Leute gibt die ihren Source mit unnötigem Zeug vollmüllen. Sei es so etwas oder das unnötige Semikolon nach jeder (!) geschweiften Klammer 🙄

    hustbaer schrieb:

    Wenn man in einem grösseren Projekt konsequent ein bestimmtes Muster verwendet

    Ja, das ist wohl das Einzige wozu es gut sein kann. Eine Unlogik konsequent weiterführen um unnötig-peinliche commits zu vermeiden.

    asdsaddsasds schrieb:

    Wieso findet sich immer jemand der seine persönliche Meinung zu solchen Themen als einzige Wahrheit predigt?

    Im Prinzip habe ich recht, weil der zusätliche Teil weder der Leserlichkeit noch einem Programmablauf dient.
    Man muss drüber sprechen, weil sich so etwas fortpflanzt. Manche Klassen aus bekannten Repos wechseln auch 10 mal zwischen public: und private:



  • asdsaddsasds schrieb:

    Wenn der Source-Code compiliert und exakt das gleiche tut, ist es unnötige Information.

    Dass die Aussage völliger Schwachsinn ist, ist natürlich klar. Ich denke darüber müssen wir gar nicht diskutieren. (sprechende Variabel-Namen, Kommentare, Formatierung, ... ).

    asdsaddsasds schrieb:

    Ja, das ist wohl das Einzige wozu es gut sein kann. Eine Unlogik konsequent weiterführen um unnötig-peinliche commits zu vermeiden.

    Die Logik dahinter wurde dir doch erklärt. Was passt dir daran nicht?
    Du argumentierst hier ja auch für zwei Dinge: struct statt class und private vor public in class.
    Über private vor public in class müssen wir nicht diskutieren, warum das oft andersrum gemacht wird ist bereits erklärt worden und ist auch logisch.
    Zu struct's vs class: struct ist auch für mich was anderes als class. Das hat sich einfach historisch etabliert und liegt auch einfach an den Namen. Dementsprechend erwarte ich unterschiedliche Dinge, wenn ich struct lese als wenn ich class lese. So geht es vielen anderen auch.
    Inwiefern es jetzt mehr Aufwand sein soll, "public:" anstatt "struct" zu lesen weiß ich auch nicht. Völlig unsinniger "Optimierungs"-Aufwand hier.



  • asdsaddsasds schrieb:

    Im Prinzip habe ich recht, weil der zusätliche Teil weder der Leserlichkeit noch einem Programmablauf dient.

    Nein. Im Prinzip bist du ein Idiot.



  • hustbaer schrieb:

    asdsaddsasds schrieb:

    Im Prinzip habe ich recht, weil der zusätliche Teil weder der Leserlichkeit noch einem Programmablauf dient.

    Nein. Im Prinzip bist du ein Idiot.

    Hach ist es schön wieder einen verranzten Neckbeard flamen zu sehen. Wir wissen doch beide wie du aussiehst und wie viel du im realen Leben zu melden hast. Daher ist es einfach nur lustig, wenn so ein Würstchen sich versucht im Internet aufzublasen 😃 😃 😃

    Dann noch einen auf sozialer Neckbeard machen, mit Amnesty International in der Signatur.

    Wechsel lieber schnell in den Tab, wo dein Hentai läuft, sonst verpasst du noch was.



  • Wow, da ist jemand aber jemand verunsichert.
    So wie die Leute, die in Online Games immer anfangen über IRL Sachen zu reden, sobald sie merken, dass der andere wesentlich besser ist.
    "Tja ich bin zwar kacke, aber bestimmt bin ich dafür im IRL cooler! Haha, gewonnen!"

    Jetzt musst du uns aber schon ein Link zu deinen Social-Media Accounts geben, damit wir sicherstellen können, dass du auch wirklich so ein krasser Typ bist. Da du ja so ein krasser Typ BIST sollte das kein Problem sein?



  • cvcv schrieb:

    Wow, da ist jemand aber jemand verunsichert.
    So wie die Leute, die in Online Games immer anfangen über IRL Sachen zu reden, sobald sie merken, dass der andere wesentlich besser ist.
    "Tja ich bin zwar kacke, aber bestimmt bin ich dafür im IRL cooler! Haha, gewonnen!"

    Du kannst drumherumreden wie viel du möchtest. Jeder weiß wie diese Code-Monkeys aussehen und das sie kein bisschen Selbstkontrolle besitzen. Liegt einfach an mangelnder sozialen Interaktion. Jahrelanger Kelleraufenthalt mit Gleichgesinnten hat nunmal ihren Preis. Sie toben in solchen Foren nur ihren Frust aus. Ist ja nicht so, als ob diese Tatsachen neu sind. Im Hinterkopf weiß es Jeder und solche Beispiel belegen, dass es auch tatsächlich so ist.

    cvcv schrieb:

    Jetzt musst du uns aber schon ein Link zu deinen Social-Media Accounts geben, damit wir sicherstellen können, dass du auch wirklich so ein krasser Typ bist. Da du ja so ein krasser Typ BIST sollte das kein Problem sein?

    Sicher 🙄 Da kann ich meine Profile auch bei 4chan öffentlich machen. Das wird dasselbe Klientel sein 😃



  • Ich sollte nicht auf die Detour (Das getrolle) eingehen, aber:

    Das voranstellen des öffentlichen Interfaces der Klasse dient der Lesbarkeit.
    Weil der private Teil den Leser nicht interessiert und nicht zu interessieren hat. Sondern lediglich das Interface der Klasse. Und das sollte immer vorne stehen, außer es geht nicht anders (und das ist "nie" der Fall).

    Ob man ein private/public mehr oder weniger hat interessiert keine Sau.

    EDIT: IMO



  • 5cript schrieb:

    Ich sollte nicht auf die Detour (Das getrolle) eingehen, aber:

    Du musst ja nicht mit-trollen, solange du sinnvoll argumentierst ist es okay sich gegenseitig zu widersprechen.

    5cript schrieb:

    Das voranstellen des öffentlichen Interfaces der Klasse dient der Lesbarkeit. Weil der private Teil den Leser nicht interessiert und nicht zu interessieren hat. Sondern lediglich das Interface der Klasse. Und das sollte immer vorne stehen, außer es geht nicht anders (und das ist "nie" der Fall).

    Ob man ein private/public mehr oder weniger hat interessiert keine Sau.

    EDIT: IMO

    Mir ist es auch bewusst, das es um Sichtbarleiten geht. Wenn's Keinen interessieren soll, dann bilde ich doch einfach so etwas:

    #include <QScopedPointer>
    
    class PrivatePimpl;
    class Pimpl
    {
      QScopedPointer<PrivatePimpl> const impl;
    
      public:
    
        Pimpl();
       ~Pimpl();
    };
    

    Oder etwa nicht?



  • Und das soll jetzt besser sein, als "public:" lesen zu müssen? Ist doch großer Schwachsinn - kannst mir nicht erzählen, dass du das ernst meinst.



  • cvcv schrieb:

    Und das soll jetzt besser sein, als "public:" lesen zu müssen? Ist doch großer Schwachsinn - kannst mir nicht erzählen, dass du das ernst meinst.

    Genau so schwachsinnig ist es von Sichtbarleiten zu sprechen, wenn man etwas weiter unten alle Member auflistet.

    Damit bleibt nur das Argument, dass du das Interface hervorheben möchtest. Sehe ich ein. Hier kommt es aber wieder auf die Arbeitsweise an.

    Wenn ich eine fremde Klasse nutzen möchte, dann schaue ich mir die Doku oder ein Klassendiagramm an. Wenn das Letztere nicht gibt, dann kann man es sich mit zwei Klicks erstellen lassen.

    Beim Programmieren bekomme ich als Vervollständigung ohnehin nur den öffentlichen Teil, es sei denn ich habe was anderes voreingestellt.

    Von Sinn und Unsinn kannst du hier nicht sprechen.



  • Jetzt hast du ein paar Möglichkeiten aufgezählt, wie die du Nachteile von 'private first' etwas lindern kannst.
    Aber welchen Vorteil siehst du denn?



  • Jockelx schrieb:

    Jetzt hast du ein paar Möglichkeiten aufgezählt, wie die du Nachteile von 'private first' etwas lindern kannst.
    Aber welchen Vorteil siehst du denn?

    Mit möglichst wenig Zusatzinfos den selben Informationsgehalt bereitstellen?

    Dann kommen aber Gegenargumente wie "Ich mag das Schlüsselwort 'struct' nicht.", dass exakt diesen Fall abdeckt. Oder Ähnliches, was wieder in der Gefühls- und Ehrenebene endet.

    Versteh mich jetzt nicht falsch, Jeder so wie er es möchte. Nur wenn man anfängt das eine oder das Andere als Unsinn zu bezeichnen, sollte man es schon konsequent begründen.


Log in to reply