//: C02:Datum.cpp {O}
// From "Thinking in C++, Volume 2", by Bruce Eckel & Chuck Allison.
// (c) 1995-2004 MindView, Inc. All Rights Reserved.
// See source code use permissions stated in the file 'License.txt',
// distributed with the code package available at www.MindView.net.
#include "Date.h"
#include <iostream>
#include <sstream>
#include <cstdlib>
#include <string>
#include <algorithm> // kvůli swap()
#include <ctime>
#include <cassert>
#include <iomanip>
using namespace std;

namespace {
  const int daysInMonth[][13] = {
    { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
    { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
  };
  inline bool isleap(int y) {
    return y%4 == 0 && y%100 != 0 || y%400 == 0;
  }
}

Datum::Datum() {
  // Zjistit aktuální datum
  time_t tval = time(0);
  struct tm *now = localtime(&tval);
  year = now->tm_year + 1900;
  month = now->tm_mon + 1;
  day = now->tm_mday;
}

Datum::Datum(int yr,int mon,int dy) throw(Datum::DateError) {
  if(!(1 <= mon && mon <= 12))
    throw DateError("patné datum v konstruktoru Datum");
  if(!(1 <= dy && dy <= daysInMonth[isleap(year)][mon]))
    throw DateError("patný den v konstruktoru Datum");
  year = yr;
  month = mon;
  day = dy;
}

Datum::Datum(const std::string& s) throw(Datum::DateError) {
  // předpokládáme formát YYYYMMDD 
  if(!(s.size() == 8))
    throw DateError("patný řetězec v konstruktoru Datum");
  for(int n = 8; --n >= 0;)
    if(!isdigit(s[n]))
      throw DateError("patný řetězec v konstruktoru Datum");
  string buf = s.substr(0, 4);
  year = atoi(buf.c_str());
  buf = s.substr(4, 2);
  month = atoi(buf.c_str());
  buf = s.substr(6, 2);
  day = atoi(buf.c_str());
  if(!(1 <= month && month <= 12))
    throw DateError("patný měsíc v konstruktoru Datum");
   if(!(1 <= day && day <=
     daysInMonth[isleap(year)][month]))
    throw DateError("patný den v konstruktoru Datum");
}

int Datum::getRok() const { return year; }

int Datum::getMesic() const { return month; }

int Datum::getDen() const { return day; }

string Datum::toString() const {
  ostringstream os;
  os.fill('0');
  os << setw(4) << year
     << setw(2) << month
     << setw(2) << day;
  return os.str();
}

int Datum::compare(const Datum& d2) const {
  int result = year - d2.year;
  if(result == 0) {
    result = month - d2.month;
    if(result == 0)
      result = day - d2.day;
  }
  return result;
}

int Datum::daysInPrevMonth(int year, int month) {
  if(month == 1) {
    --year;
    month = 12;
  }
  else
    --month;
  return daysInMonth[isleap(year)][month];
}

bool operator<(const Datum& d1, const Datum& d2) {
  return d1.compare(d2) < 0;
}
bool operator<=(const Datum& d1, const Datum& d2) {
  return d1 < d2 || d1 == d2;
}
bool operator>(const Datum& d1, const Datum& d2) {
  return !(d1 < d2) && !(d1 == d2);
}
bool operator>=(const Datum& d1, const Datum& d2) {
  return !(d1 < d2);
}
bool operator==(const Datum& d1, const Datum& d2) {
  return d1.compare(d2) == 0;
}
bool operator!=(const Datum& d1, const Datum& d2) {
  return !(d1 == d2);
}

Datum::Trvani
trvani(const Datum& date1, const Datum& date2) {
  int y1 = date1.year;
  int y2 = date2.year;
  int m1 = date1.month;
  int m2 = date2.month;
  int d1 = date1.day;
  int d2 = date2.day;

  // Porovnání
  int order = date1.compare(date2);
  if(order == 0)
    return Datum::Trvani(0,0,0);
  else if(order > 0) {
    // Lokálně umístit date1 před date2
    using std::swap;
    swap(y1, y2);
    swap(m1, m2);
    swap(d1, d2);
  }

  int roky = y2 - y1;
  int mesice = m2 - m1;
  int dny = d2 - d1;
  assert(roky > 0 ||
     roky == 0 && mesice > 0 ||
     roky == 0 && mesice == 0 && dny > 0);

  // Udělat potřebné korekce (musí se nastavit dny před měsícem!) Cyklus se 
	// uplatní, jestlie je předchozí měsíc únor a dny < -28.
	int lastMonth = m2;
  int lastYear = y2;
  while(dny < 0) {
    assert(mesice > 0);
    dny += Datum::daysInPrevMonth(
      lastYear, lastMonth--);
    --mesice;
  }

  if(mesice < 0) {
    assert(roky > 0);
    mesice += 12;
    --roky;
  }
  return Datum::Trvani(roky, mesice, dny);
}

ostream& operator<<(ostream& os, const Datum& d) {
  char fillc = os.fill('0');
  os << setw(2) << d.getMesic() << '-'
     << setw(2) << d.getDen() << '-'
     << setw(4) << setfill(fillc) << d.getRok();
  return os;
}

istream& operator>>(istream& is, Datum& d) {
  is >> d.month;
  char dash;
  is >> dash;
  if(dash != '-')
    is.setstate(ios::failbit);
  is >> d.day;
  is >> dash;
  if(dash != '-')
    is.setstate(ios::failbit);
  is >> d.year;
  return is;
} ///:~
