#include "sopnamsp.h"
#include "machdefs.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <iostream>
#include <time.h>
#include <ctype.h>
#include <math.h>
#include "perrors.h"
#include "dates.h"

/*! 
  \class TimeZone
  \ingroup NTools
  Management of time zones - This class is used with class Date.
  \warning Unless necessary, DO NOT USE this class. 
  \sa SOPHYA::TimeStamp

  \verbatim
  
  Classe de fuseau horaire. Permet les conversion
  GMT <-> temps local, en gérant les changements
  d'heure hiver-été.
  Deux fuseaux horaires sont prédéfinis, "France"
  et "Chili".
  \endverbatim
*/

TimeZone* gTimeZone = NULL;


/*!	
  Constructeur par défaut. Il lit la variable
  d'environnement ACQ_TZ pour choisir le fuseau
  horaire. Si la variable n'est pas définie, nous
  sommes en France...
*/  
TimeZone::TimeZone()
{
  char* p = getenv("ACQ_TZ");
#if defined(__DECCXX) || defined(__KCC__) || defined(__aCC__)
  if (!p) p = const_cast<char *>("France");
#else
  if (!p) p = "France";
#endif
  SetZone(p);
}

//!	Constructeur à partir du nom d'un fuseau horaire.
TimeZone::TimeZone(const char* nom)
{
  SetZone(nom);
}

/*!	
  Choisit un fuseau horaire. Contient la définition
  du fuseau "France" (GMT+1, DST : dernier dimanche
  de mars - dernier dimanche de septembre, c'est-à-dire
  pré-gouvernement Juppé), et Chili (GMT-4, DST :
  deuxième dimanche d'octobre - deuxième dimanche de mars).
*/
void TimeZone::SetZone(const char* nom)
{
  if (!strcmp(nom,"France")) {
     strcpy(code,"MET");
     strcpy(name,nom);
     gmtOffset      = 1;
     hasDST         = 1;
     dstByWeekDay   = 1;
     dstStartDay    = Date::jour_Dimanche; // dernier dimanche de mars
     dstStartNum    = -1;
     dstStartMonth  = Date::mois_Mars;
     dstStopDay     = Date::jour_Dimanche; // dernier dimanche de septembre
     dstStopNum     = -1;
     dstStopMonth   = Date::mois_Septembre;
     dstOffset      = 1;
  } else if (!strcmp(nom,"Chili") || !strcmp(nom,"Chile")) {
// # Chile has 2nd Sunday in October to 2nd Sunday in March DST
    strcpy(code,"CST");
    strcpy(name,"Chili");
    gmtOffset      = -4;
    hasDST         = 1;
    dstByWeekDay   = 1;
    dstStartDay    = Date::jour_Dimanche; // 2e dimanche d'octobre
    dstStartNum    = 2;
    dstStartMonth  = Date::mois_Octobre;
    dstStopDay     = Date::jour_Dimanche; // 2e dimanche de mars
    dstStopNum     = 2;
    dstStopMonth   = Date::mois_Mars;
    dstOffset      = 1;
  } else throw ParmError("TimeZone::SetZone() ");
}

//!	Teste si une date est en heure d'été (DST).
int TimeZone::IsDst(const Date& date)
{
  Date dt = date;
  if (dt.MM < 0 || dt.JJ < 0) throw ParmError(PExcLongMessage(""));
  if (dt.hh >= 0) dt += gmtOffset/24.0; // date "fictive" en local d'hiver
  else dt.ss = dt.hh = dt.mm = 0;       // minuit local si heure indeterminee
  // On est en-dehors des mois frontiere ?
  if (dstStartMonth < dstStopMonth) {
     if (dt.MM < dstStartMonth) return 0;
     if (dt.MM > dstStopMonth)  return 0;
     if (dt.MM > dstStartMonth && dt.MM < dstStopMonth) return 1;
  } else {
     if (dt.MM < dstStopMonth)  return 1;
     if (dt.MM > dstStartMonth) return 1;
     if (dt.MM > dstStopMonth && dt.MM < dstStartMonth) return 0;
  }
  
  // Nous sommes dans un mois-frontiere
  DBASSERT(dt.MM == dstStopMonth || dt.MM == dstStartMonth);
  
  if (dt.MM == dstStartMonth) {
    int jour;
    if (dstByWeekDay) jour = Date::NthMonthDay(dstStartNum,dstStartDay, dt.MM, dt.AA);
    else jour = (dstStartNum>0) ? dstStartNum : Date::MonthDays(dt.MM, dt.AA)+1-dstStartNum;
    if (dt.JJ < jour) return 0;
    if (dt.JJ > jour) return 1;
    if (dt.hh > 1) return 1;
    return 0;
  } else {
    int jour;
    if (dstByWeekDay) jour = Date::NthMonthDay(dstStopNum,dstStopDay, dt.MM, dt.AA);
    else jour = (dstStopNum>0) ? dstStopNum : Date::MonthDays(dt.MM, dt.AA)+1-dstStopNum;
    if (dt.JJ < jour) return 1;
    if (dt.JJ > jour) return 0;
    if (dt.hh < 2) return 0;
    return 1;
  }
}

/*!
  Retourne la difference TL-GMT pour une date donnée,
  en tenant compte de l'heure d'été éventuelle.
*/
int TimeZone::GetOffset(const Date& date)
{
  return IsDst(date) ? gmtOffset+dstOffset : gmtOffset;
}



/*! 
  \class Date
  \ingroup NTools
  Date manipulation classe
  \warning Unless necessary, DO NOT USE this class.
  Use  SOPHYA::TimeStamp instead.

  \verbatim
  
  Une classe date comme une autre, avec gestion
  temps local / GMT / changement d'heure, et un 
  jour temps sidéral.
  
  Une partie de la date (heure, jour...) peut être
  indéterminée. Les comparaisons sont alors faites
  de façon astucieuse...
  
  La date peut être déterminée de minuit à minuit
  ou de midi à midi (nuit d'observation). Logique
  complexe et peut-être foireuse pour passer de
  l'un à l'autre, mais pas encore d'ennuis pour le
  moment... Une date-nuit doit avoir une heure
  indéterminée.
  
  Il faut que Date::gTimeZone soit initialisé pour
  éviter des gros pépins, mais PeidaInit() le fait.
  
  An 2000 : Lorsqu'on gère des dates sous forme de chaine :
  * En sortie, 2 caractères pour l'année entre 1950 et 1999
  et 4 caractères sinon
  * En entrée, si AA<100 on ajoute 1900.

*/
//!	Retourne le nombre de jours dans le mois
short Date::MonthDays(short mois, short annee)
{
  if (mois<1 || mois>12) throw ParmError(PExcLongMessage(""));
  
  switch(mois) {
    case mois_Janvier:
    case mois_Mars:
    case mois_Mai:
    case mois_Juillet:
    case mois_Aout:
    case mois_Octobre:
    case mois_Decembre:
      return 31;
    case mois_Avril:
    case mois_Juin:
    case mois_Septembre:
    case mois_Novembre:
      return 30;
    case mois_Fevrier:
     return (((annee%4 == 0) && (annee%100 != 0)) || (annee%400 == 0)) ? 29 : 28;
  }
  return -1;
}
    
//!  Retourne le nombre de jours dans l'année
short Date::YearDays(short annee)
{
  return (((annee%4 == 0) && (annee%100 != 0)) || (annee%400 == 0)) ? 366 : 365;
}

bool Date::UndetDate() const
/*!
  Retourne true si une partie de la date (jour 
  ou mois ou année) est indéterminée.
*/
{
  return ((AA == -1) || (MM == -1) || (JJ == -1));
}

/*!
  Retourne true si toute la date (jour
  et mois et année) est indéterminée.
*/
bool Date::AllUndetDate() const
{
  return ((AA == -1) && (MM == -1) && (JJ == -1));
}

bool Date::UndetTime() const
/*!	
  Retourne true si une partie de l'heure (heure
  ou minutes ou secondes) est indéterminée.
*/
{
  return ((hh == -1) || (mm == -1) || (ss == -1));
}


/*!
  Retourne true si toute l'heure (heure
  et minutes et secondes) est indéterminée.
*/
bool Date::AllUndetTime() const
{
  return ((hh == -1) && (mm == -1) && (ss == -1));
}

//Date::operator double() const

//!	Jours écoulés depuis le 0 janvier 1901 0h TU
double
Date::GetDays() const
{
   if (UndetTime() && !AllUndetTime()) throw ParmError(PExcLongMessage(""));
   if (UndetDate() && !AllUndetDate()) throw ParmError(PExcLongMessage(""));
   double t=0;
   if (!UndetDate()) {
     int nban = AA-1901;
     if (nban >= 0)  
       t = nban*365 + (nban/4) - (nban/100) + ((nban+300)/400);
     else 
       t = nban*365 + (nban/4) - (nban/100) + ((nban-100)/400);
     for (int i=1; i<MM; i++)  t += Date::MonthDays(i,AA);
     t += JJ;
   }
   if (!UndetTime()) t += hh/24.0 + mm/1440.0 + ss/86400.0;
   else if (nuit) t += 0.5;
   return t;
}

//!	Initialisation a partir jours écoulés depuis le 0 janvier 1901 0h TU
void Date::Set(double t)
{
   t += 1/8640000.0;
   int nban = int(floor((t/*-1*/) / 365.2425));
   if (nban >= 0)  
     t -= nban*365 + ((nban)/4) - ((nban)/100) + ((nban+300)/400);
   else 
     t -= nban*365 + ((nban)/4) - ((nban)/100) + ((nban-100)/400);
     
   AA = 1901 + nban;
   if (t > Date::YearDays(AA)+1) {
     t -= Date::YearDays(AA);
     AA++;
   } else if (t<1) {
     AA--;
     t += Date::YearDays(AA);
   }
   
   MM = 1;
   while (t > Date::MonthDays(MM,AA)+1) {
     t -= Date::MonthDays(MM,AA);
     MM++;
   }
   
   JJ = int(t);
   t -= JJ;
   
   t *= 24;
   hh = int(t);
   
   t = (t-hh)*60;
   mm = int(t);
   
   t = (t-mm)*60;
   ss = t;
   
   nuit = 0;
}

//!	Constructeur. Prend l'heure courante...
Date::Date()
: timeZone(gTimeZone)
{
  time_t t = time(NULL);
  struct tm* TM = gmtime(&t);
  
  AA = TM->tm_year + 1900;
  MM = TM->tm_mon+1;
  JJ = TM->tm_mday;
  hh = TM->tm_hour;
  mm = TM->tm_min;
  ss = TM->tm_sec;
  nuit = 0;
}

//!	Constructeur simple.
Date::Date(int J, int M, int A, int h, int m, double s)
: JJ(J), MM(M), AA(A), hh(h), mm(m), ss(s), timeZone(gTimeZone), nuit(0)
{
}

//!	Constructeur à partir des jours écoulés depuis le 0 janvier 1901 0h TU
Date::Date(double t)
: timeZone(gTimeZone)
{
  Set(t);
}

//!	On change de fuseau horaire.
void Date::SetTimeZone(TimeZone* tz)
{
  timeZone = tz;
}

/*!
  Constructeur à partir de la date sous la forme
  'DD/MM/YYYY'  'HH/MM/SS', et tOpt est Date::kGMTTime
  (par défaut) ou Date::kLocalTime.
*/
Date::Date(const char* date, const char* heure, int tOpt)
: timeZone(gTimeZone)
{
  Set(date,heure,tOpt);
}

/*!
  Constructeur à partir de la date sous la forme
  'DD/MM/YYYY'  'HH/MM/SS', et tOpt est Date::kGMTTime
  (par défaut) ou Date::kLocalTime.
  
  Tout ou partie de la date peut être indéterminée ('??').
*/
Date::Date(string const& date, string const& heure, int tOpt)
: timeZone(gTimeZone)
{
  Set(date,heure,tOpt);
}

/*!
  Positionne la date sous la forme
  'DD/MM/YYYY'  'HH/MM/SS', et tOpt est Date::kGMTTime
  (par défaut) ou Date::kLocalTime.
  
  Tout ou partie de la date peut être indéterminée ('??').
*/
void Date::Set(string const& date, string const& heure, int tOpt)
{
  Set(date.c_str(), heure == "" ? (char*)NULL : heure.c_str(), tOpt);
}

/*!
  Positionne la date sous la forme
  'DD/MM/YYYY'  'HH/MM/SS', et tOpt est Date::kGMTTime
  (par défaut) ou Date::kLocalTime.
  
  Tout ou partie de la date peut être indéterminée ('??').
*/
void Date::Set(const char* date, const char* heure, int tOpt)
{
  nuit = 0;
  if (date) {
   if (strlen(date) >= 5) {         //  J(J)/M(M)/AA(AA), ?/?/?, combinaison
    const char* p = date;
    if (p[0] == '?' && (p[1] == '?' || p[1] == '/'))   JJ = -1;
    else {
      if (!isdigit(p[0])) throw ParmError(PExcLongMessage(""));
      if (!isdigit(p[1]) && p[1] != '/') throw ParmError(PExcLongMessage(""));
      JJ = atoi(p);
      if (JJ <= 0) throw ParmError(PExcLongMessage(""));
    }
    
    if (p[1] == '/') p += 2;
    else if (p[2] == '/') p+= 3;
    else throw ParmError(PExcLongMessage(""));

    if (p[0] == '?' && (p[1] == '?' || p[1] == '/'))   MM = -1;
    else {
      if (!isdigit(p[0])) throw ParmError(PExcLongMessage(""));
      if (!isdigit(p[1]) && p[1] != '/') throw ParmError(PExcLongMessage(""));
      MM = atoi(p);
      if (MM <= 0 || MM >12) throw ParmError(PExcLongMessage(""));
    }

    if (p[1] == '/') p += 2;
    else if (p[2] == '/') p+= 3;
    else throw ParmError(PExcLongMessage(""));

    if (p[0] == '?')  AA = -1;
    else {
      if (!isdigit(p[0])) throw ParmError(PExcLongMessage(""));
      if (!isdigit(p[1])) throw ParmError(PExcLongMessage(""));
      AA = atoi(p);
      if (AA < 100 && AA >= 0 && date[6] != '0') AA += 1900;
    }
    
    if (AA > 0 && MM > 0 && JJ > 0 && JJ > MonthDays(MM,AA)) 
      throw ParmError(PExcLongMessage(""));
    
   } else if (strlen(date)==4) {      // Code EROS de date
     if (date[0] == '?') AA = -1;
     else {
       if (!isdigit(date[0])) throw ParmError(PExcLongMessage(""));
       AA = 1990 + date[0] - '0';
     }
     
     if (date[1] == '?') MM = -1;
     else {
       if (!isalpha(date[1])) throw ParmError(PExcLongMessage(""));
       if (islower(date[1]))
         MM = date[1] - 'a' + 1;
       else
         MM = date[1] - 'A' + 1;
       if (MM<1 || MM>12) throw ParmError(PExcLongMessage(""));
     }
     
     if (date[2] == '?' && date[3] == '?') JJ = -1;
     else {
       if (!isdigit(date[2])) throw ParmError(PExcLongMessage(""));
       if (!isdigit(date[3])) throw ParmError(PExcLongMessage(""));
       JJ = atoi(date+2);
     }
     
     if (AA > 0 && MM > 0 && JJ > 0 && JJ > MonthDays(MM,AA)) 
       throw ParmError(PExcLongMessage(""));
     nuit = 1;
   } else throw ParmError(PExcLongMessage(""));   // Mauvaise longueur
  } else {                          // Pas de date
    JJ = MM = AA = -1;
  }
  
  if (heure) {
    const char* p = heure;
    if (p[0] == '?' && (p[1] == '?' || p[1] == ':'))   hh = -1;
    else {
      if (!isdigit(p[0])) throw ParmError(PExcLongMessage(""));
      if (!isdigit(p[1]) && p[1] != ':') throw ParmError(PExcLongMessage(""));
      hh = atoi(p);
    }

    if (p[1] == ':') p += 2;
    else if (p[2] == ':') p+= 3;
    else throw ParmError(PExcLongMessage(""));

    if (p[0] == '?' && (p[1] == '?' || p[1] == ':'))   mm = -1;
    else {
      if (!isdigit(p[0])) throw ParmError(PExcLongMessage(""));
      if (!isdigit(p[1]) && p[1] != '/') throw ParmError(PExcLongMessage(""));
      mm = atoi(p);
    }

    if (p[1] == ':') p += 2;
    else if (p[2] == ':') p+= 3;
    else throw ParmError(PExcLongMessage(""));

    if (p[0] == '?')   ss = -1;
    else {
      if (!isdigit(p[0])) throw ParmError(PExcLongMessage(""));
      ss = atoi(p);
    }
  } else {
    ss = hh = mm = -1;
  }
  
  if (tOpt == kLocalTime) {
    DBASSERT(timeZone != NULL);
    operator -= (timeZone->gmtOffset/24.0);
    if (timeZone->IsDst(*this))
      operator -= (timeZone->dstOffset/24.0);   // A VERIFIER SANS DOUTE INEXACT
  } 
  
  if (nuit && !UndetTime() && hh < 12)
    *this += 1;
}

/*!
  Récupère la date sous la forme d'une chaîne 'DD/MM/YYYY'
  en tOpt = Date::kGMTTime ou Date::kLocalTime.
*/
void Date::GetDateStr(char* s, int tOpt) const
{
  Date dt(*this);
  if (tOpt == kLocalTime && !UndetTime()) {
    DBASSERT(timeZone != NULL);
    dt += timeZone->GetOffset(*this)/24.0;
  }
  
  if (dt.AA < 1950 || dt.AA > 1999) 
    sprintf(s, "%02d/%02d/%04d", dt.JJ, dt.MM, dt.AA);
  else
    sprintf(s, "%02d/%02d/%02d", dt.JJ, dt.MM, dt.AA-1900);
    
  if (dt.JJ == -1)
    s[0] = s[1] = '?';
  if (dt.MM == -1)
    s[3] = s[4] = '?';
  if (dt.AA == -1) {
    s[6] = s[7] = '?';
    s[8] = '\0';
  }
}

//!	Code EROS de la date.
void Date::GetDateCode(char* s, int tOpt) const
{
  Date dt(*this);
  if (tOpt == kLocalTime && !UndetTime()) {
    DBASSERT(timeZone != NULL);
    dt += timeZone->GetOffset(*this)/24.0;
  }

  if (!dt.UndetTime() && dt.hh<12) dt -= 1;

  if (dt.AA != -1) {
    if (dt.AA < 1990 || dt.AA > 2016) throw ParmError(PExcLongMessage(""));
    int i = dt.AA - 1990;
    if (i<10)
      s[0] = '0' + i;
    else
      s[0] = 'a' + i - 10;
  } else
    s[0] = '?';
    
  if (dt.MM != -1)
    s[1] = 'a' + dt.MM - 1;
  else
    s[1] = '?';
    
  if (dt.JJ != -1)
    sprintf(s+2, "%02d", dt.JJ);
  else
    s[2] = s[3] = '?';
  
  s[4] = 0;
}

//!	Récupère l'heure sous la forme 'HH:MM:SS'
void Date::GetTimeStr(char* s, int tOpt) const
{
  Date dt(*this);
  if (tOpt == kLocalTime) {
    DBASSERT(timeZone != NULL);
    dt += timeZone->GetOffset(*this)/24.0;
  }

  sprintf(s, "%02d:%02d:%02d", dt.hh, dt.mm, int(dt.ss));
  if (dt.hh == -1)
    s[0] = s[1] = '?';
  if (dt.mm == -1)
    s[3] = s[4] = '?';
  if (dt.ss == -1)
    s[6] = s[7] = '?';
}

//!	Récupèrera un jour le temps sidéral.
void Date::GetSidTStr(char* s) const
{
  strcpy(s,"TOBEDONE!!");
}

string Date::DateStr(int tOpt) const
/*!
  Récupère la date sous la forme d'une chaîne 'DD/MM/YYYY'
  en tOpt = Date::kGMTTime ou Date::kLocalTime.
*/
{
   char s[20];
   GetDateStr(s,tOpt);
   return s;
}

//!	Code EROS de la date.
string Date::DateCode(int tOpt) const
{
   char s[20];
   GetDateCode(s,tOpt);
   return s;
}

//!	Récupère l'heure sous la forme 'HH:MM:SS'
string Date::TimeStr(int tOpt) const
{
   char s[20];
   GetTimeStr(s,tOpt);
   return s;
}

//!	Récupèrera un jour le temps sidéral.
string Date::SidTStr() const
{
   char s[20];
   GetSidTStr(s);
   return s;
}

//!   Retourne le jour dans la semaine, 0-6 avec 0 = Date::jour_Lundi.
int Date::DayOfWeek(int tOpt) const
{
  double t = GetDays();
  if (tOpt == kLocalTime && hh >=0) {
    DBASSERT(timeZone != NULL);
    t += timeZone->GetOffset(*this)/24.0;
  }
  int ndays = int(t);  // nb de jours depuis 0 janvier 1901
  ndays = (ndays + 1) % 7;
  if (ndays<0) ndays += 7;
  return ndays;
}

/*!
  Retourne la date correspondant au ieme joursem dans le mois 
  correspondant (exemple, 2e dimanche de mars 1960).
*/
short Date::NthMonthDay(short i, short joursem, short mois, short annee)
{
   if (i>0) {
     int jprem = Date(1, mois, annee, 0, 0, 0).DayOfWeek();
     int d = (joursem - jprem);
     if (d<0) d+=7;
     return d + 1 + (i-1)*7;
   } else {
     int ider = MonthDays(mois, annee);
     int jder = Date(ider, mois, annee, 0, 0, 0).DayOfWeek();
     int d = (joursem - jder);
     if (d>0) d-=7;
     return d + ider + (i+1)*7;
   }
}

//! Ajout un nombre \b dt en jours
Date& Date::operator += (double dt)     
{
  int u = UndetTime();
  Set(GetDays()+dt);
  if (u) ss = hh = mm = -1;
  return *this;
}

//!  retranche un intervalle de temps \b dt en jours
Date& Date::operator -= (double dt)    
{
  int u = UndetTime();
  Set(GetDays()-dt);
  if (u) ss = hh = mm = -1;
  return *this;
}

Date operator + (Date const& d, double dt)
{
  Date a(d);
  a += dt;
  return a;
}

Date operator - (Date const& d, double dt)
{
  Date a(d);
  a -= dt;
  return a;
}

//!	Résultat en jours
double operator - (Date const& a, Date const& b)
{
  if (a.UndetTime() != b.UndetTime()) throw ParmError(PExcLongMessage(""));
  if (a.UndetDate() != b.UndetDate()) throw ParmError(PExcLongMessage(""));
  return a.GetDays() - b.GetDays();
}

//!	On n'a pas égalite dès que des éléments déterminés sont différents
bool operator == (Date const& a, Date const& b)
{
  if (a.nuit && a.UndetTime() && !b.nuit && !b.UndetTime() && b.hh<12)
    {Date d = a+1; d.nuit = 0; return d == b;}
  else if (b.nuit && b.UndetTime() && !a.nuit && !a.UndetTime() && a.hh<12)
    {Date d = b+1; d.nuit = 0; return a == d;}

  if (a.AA != -1 && b.AA != -1 && a.AA != b.AA) return false;
  if (a.MM != -1 && b.MM != -1 && a.MM != b.MM) return false;
  if (a.JJ != -1 && b.JJ != -1 && a.JJ != b.JJ) return false;
  if (a.hh != -1 && b.hh != -1 && a.hh != b.hh) return false;
  if (a.mm != -1 && b.mm != -1 && a.mm != b.mm) return false;
  if (a.ss != -1 && b.ss != -1 && a.ss != b.ss) return false;
  return true;
}

bool operator != (Date const& a, Date const& b)
{
  return !(a == b);
}

//!	Inégalité large. Pas de subtilités
bool operator <= (Date const& a, Date const& b)
{
  if (a.nuit && a.UndetTime() && !b.nuit && !b.UndetTime() && b.hh<12)
    {Date d = a+1; d.nuit = 0; return d <= b;}
  else if (b.nuit && b.UndetTime() && !a.nuit && !a.UndetTime() && a.hh<12)
    {Date d = b+1; d.nuit = 0; return a <= d;}

  if (a.AA != -1 && b.AA != -1) {
    if (a.AA < b.AA) return true;
    if (a.AA > b.AA) return false;
  }
  if (a.MM != -1 && b.MM != -1) {
    if (a.MM < b.MM) return true;
    if (a.MM > b.MM) return false;
  }
  if (a.JJ != -1 && b.JJ != -1) {
    if (a.JJ < b.JJ) return true;
    if (a.JJ > b.JJ) return false;
  }
  if (a.hh != -1 && b.hh != -1) {
    if (a.hh < b.hh) return true;
    if (a.hh > b.hh) return false;
  }
  if (a.mm != -1 && b.mm != -1) {
    if (a.mm < b.mm) return true;
    if (a.mm > b.mm) return false;
  }
  if (a.ss != -1 && b.ss != -1) {
    if (a.ss < b.ss) return true;
    if (a.ss > b.ss) return false;
  }
  return true;
}

/*!
  Inégalité stricte, codée à partir de la large pour résoudre
  sans cas particuliers les subtilités 
  01/02/95 < ??/03/95 mais pas 01/02/95 < ??/02/95
*/
bool operator < (Date const& a, Date const& b)
{
  return (a <= b) && !(a == b);
}

bool operator >= (Date const& a, Date const& b)
{
  return (b <= a);
}

bool operator > (Date const& a, Date const& b)
{
  return (b < a);
}

ostream& operator << (ostream& s, Date const& d)
{
  char x[20];
  d.GetDateStr(x);
  s << x << " ";
  d.GetTimeStr(x);
  s << x;
  return s;
}

