// Differenz zweier Datumseingaben in Tagen
// Mit Terminal-Eingabe und Exceptions mit eigener Klasse
//
// Aufruf: datum-diff-xcpt
// 
// Klaus Kusche, 2012

#include <iostream>
#include <cstdlib>

using namespace std;

class falschesDatum
{
  public:
  
    falschesDatum(const char *fehlerText, int fehlerWert) :
      text(fehlerText), wert(fehlerWert) {}
      
    void print() const
    {
      cerr << "Eingabefehler: " << text << " (" << wert << ")" << endl
           << "Korrektes Eingabeformat: ddmmyyyy" << endl;
    }
    
  private:
    const char *text;
    int wert;
};

bool IstSchaltjahr(int jahr);
int TageImJahr(int jahr);
int TageImMonat(int monat, int jahr);
int JahrAusDatum(int datum);
int MonatAusDatum(int datum);
int TagAusDatum(int datum);
int TageBisDatum(int datum);
int liesDatum(const char *prompt);

// Ist "jahr" ein Schaltjahr? (Returnwert true oder false)
bool IstSchaltjahr(int jahr)
{
  // Gibt das Ergebnis der Bedingung als Returnwert zurück
  return (jahr % 400 == 0) || ((jahr % 4 == 0) && (jahr % 100 != 0));
}

// Wie viele Tage hat das Jahr "jahr"?
int TageImJahr(int jahr)
{
  return IstSchaltjahr(jahr) ? 366 : 365;
}

// Liefert die Anzahl der Tage des Monats "monat" im Jahr "jahr"
int TageImMonat(int monat, int jahr)
{
  static const int monatTage[12] =
    { 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
    
  if (monat == 2) {
    return IstSchaltjahr(jahr) ? 29 : 28;
  } else {
    return monatTage[monat - 1];
  }
}

// Liefert das "yyyy" aus "ddmmyyyy"
int JahrAusDatum(int datum)
{
  int jahr = datum % 10000;
  if (jahr < 1) {
    throw falschesDatum("Jahr ist ungültig", jahr);
  }
  
  return jahr;
}

// Liefert das "mm" aus "ddmmyyyy"
int MonatAusDatum(int datum)
{
  int monat = (datum / 10000) % 100;
  if ((monat < 1) || (monat > 12)) {
    throw falschesDatum("Monat ist ungültig", monat);
  }

  return monat;
}

// Liefert das "dd" aus "ddmmyyyy"
int TagAusDatum(int datum)
{
  int tag = datum / 1000000;
  int maxTag = TageImMonat(MonatAusDatum(datum), JahrAusDatum(datum));
  if ((tag < 1) || (tag > maxTag)) {
    throw falschesDatum("Tag ist ungültig", tag);
  }
  
  return tag;
}

// Verwandelt "ddmmyyyy"-Zahl in "Tage seit 1.1.01": Liefert Anzahl der Tage.
int TageBisDatum(int datum)
{
  if (datum <= 0) {
    throw falschesDatum("Datum ist <= 0", datum);
  }

  int tag = TagAusDatum(datum);
  int monat = MonatAusDatum(datum);
  int jahr = JahrAusDatum(datum);

  // summiere:
  // * alle ganzen Jahre vor dem Datum
  // * alle ganzen Monate vor dem Datum
  // * die Tage im aktuellen Monat
  int tage = 0;
  for (int i = 1; i < jahr; ++i) {
    tage += TageImJahr(i);
  }
  for (int i = 1; i < monat; ++i) {
    tage += TageImMonat(i, jahr); 
  }
  tage += tag;

  return tage;
}

int liesDatum(const char *prompt)
{
  for (;;) {  // immer wieder eingeben lassen, bis das return erreicht wird
    int eingabe;
    cout << prompt;
    cin >> eingabe;
    try {
      // "Fleißaufgabe": Erkenne Konvertierungsfehler in der Eingabe, z.B.
      // - Zahl zu lang (Überlauf)
      // - Text statt Zahl eingegeben
      if (!cin) {
        // Bei "cin >> eingabe" ist ein Konvertierungsfehler aufgetreten
        // Ist die Eingabe komplett zu Ende?
        if (cin.eof()) exit(EXIT_FAILURE);
        // Fehlerstatus von cin zurücksetzen
        // (bleibt sonst auch bei folgenden korrekten Eingaben gesetzt!)
        cin.clear();
        // nicht verarbeitete Zeichen (z.B. Text statt Zahl) überlesen              
        cin.ignore(100000, '\n');
        // Benutzer zu neuer Eingabe auffordern
        throw falschesDatum("Konvertierungsfehler", 0); 
      }
      // das return wird nur dann erreicht,
      // wenn in TageBisDatum keine Exception passiert!
      return TageBisDatum(eingabe);
    }
    catch (const falschesDatum &e) {
      e.print();
    }
  }
}

// Alternative Variante
//int liesDatum(const char *prompt)
//{
//  int tage = -1;  // damit wir erkennen, wenn auf tage nichts zugewiesen wird
//
//  do {
//    int eingabe;
//    cout << prompt;
//    cin >> eingabe;
//    try {
//      // "Fleißaufgabe": Erkenne Konvertierungsfehler in der Eingabe, z.B.
//      // - Zahl zu lang (Überlauf)
//      // - Text statt Zahl eingegeben
//      if (!cin) {
//        // Bei "cin >> eingabe" ist ein Konvertierungsfehler aufgetreten:
//        // Fehlerstatus von cin zurücksetzen
//        // (bleibt sonst auch bei folgenden korrekten Eingaben gesetzt!)
//        cin.clear();
//        // nicht verarbeitete Zeichen (z.B. Text statt Zahl) überlesen              
//        cin.ignore(100000, '\n');
//        // Benutzer zu neuer Eingabe auffordern
//        throw falschesDatum("Konvertierungsfehler", 0); 
//      }
//      tage = TageBisDatum(eingabe);
//    }
//    catch (const falschesDatum &e) {
//      e.print();
//    }
//    // Wenn TageBisDatum eine Exception wirft,
//    // wird die Zuweisung auf tage nicht mehr ausgeführt.
//    // tage hat daher nach einer Exception noch den ursprünglichen Wert -1
//  } while (tage < 0);
//
//  return tage;
//}

int main(void)
{
  int von = liesDatum("Anfangsdatum? ");
  int bis = liesDatum("Enddatum? ");
  cout << "Zeitabstand " << bis - von << " Tage." << endl;

  exit(EXIT_SUCCESS);
}
