#include "sopnamsp.h" #include "machdefs.h" #include #include #include #include #include #include #include #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("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= 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; }