/***************************************************************************
                          SIMPLY POWERFUL TOOLKIT (SPTK)
                          CDateTime.cpp  -  description
                             -------------------
    begin                : Tue Dec 14 1999
    copyright            : (C) 1999-2005 by Alexey Parshin
    email                : alexeyp@gmail.com
 ***************************************************************************/

/***************************************************************************
   This library is free software; you can redistribute it and/or modify it 
   under the terms of the GNU Library General Public License as published by
   the Free Software Foundation; either version 2 of the License, or (at 
   your option) any later version.

   This library is distributed in the hope that it will be useful, but 
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library
   General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; if not, write to the Free Software Foundation, 
   Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.

   Please report all bugs and problems to "alexeyp@gmail.com"
 ***************************************************************************/

#include <stdio.h>
#include <string>
#include <stdlib.h>
#include <locale.h>

#ifndef _WIN32
#ifdef __BORLANDC__
#include <time.h>
#else
#include <sys/time.h>
#endif
#endif

#include <time.h>
#include <ctype.h>

#include <sptk3/CDateTime.h>
#include <sptk3/CException.h>
#include <sptk3/CStrings.h>

#ifdef _WIN32
#include <windows.h>
#endif

using namespace std;

class SP_EXPORT CDateTimeFormat {
public:
   CDateTimeFormat();
   string dateFormat();
   string timeFormat();
   void    weekDayNames(string wdn[7]);
   static char parseDateOrTime(char *format,char *dt);
};

static const short _monthDays[2][12] = {
   {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
   {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};

static const short _monthDaySums[2][12] = {
   {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334},
   {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}
};

#define DateDelta 693594

bool     CDateTime::m_time24Mode;
double   CDateTime::m_dateTimeOffset;

char     CDateTime::dateFormat[32];
char     CDateTime::datePartsOrder[4];
char     CDateTime::fullTimeFormat[32];
char     CDateTime::shortTimeFormat[32];
char     CDateTime::dateSeparator;
char     CDateTime::timeSeparator;
string  CDateTime::weekDayNames[7];
string  CDateTime::monthNames[12];
string  CDateTime::timeZoneName;
int      CDateTime::timeZoneOffset;

static void upperCase(char *dest,const char *src) {
   int i = 0;
   int len = strlen(src);
   for (; i < len; i++)
      dest[i] = (char) toupper( src[i] );
   dest[i] = 0;
}

char CDateTimeFormat::parseDateOrTime(char *format,char *dateOrTime) {
   char  separator[] = " ";
   char  dt[32];

   strcpy(dt,dateOrTime);

   // Cut-off trailing non-digit characters
   int len = strlen(dt);
   for (int index = len - 1; index >= 0; index--) {
      if (isdigit(dt[index])) {
         dt[index+1] = 0;
         break;
      }
   }
   char *ptr = dt;
   // find a separator char
   ptr = dt;
   while (isalnum(*ptr) || *ptr == ' ') ptr++;
   separator[0] = *ptr;
   ptr = strtok(dt,separator);
   strcpy(format,"");

   bool processingTime = false;
   const char *pattern;
   while (ptr) {
      int number = atoi(ptr);
      switch (number) {
         case 10: pattern = "19";   // hour (12-hour mode)
            CDateTime::m_time24Mode = false;
            break;
         case 22: pattern = "29";    // hour (24-hour mode)
            CDateTime::m_time24Mode = true;
            break;
         case 48: pattern = "59";    // minute
            break;
         case 59: pattern = "59";    // second
            break;
         case 17: pattern = "39";   // day
            strcat(CDateTime::datePartsOrder,"D");  
            break;
         case 6:  pattern = "19";   // month
            strcat(CDateTime::datePartsOrder,"M");  
            break;
         case 2000:
         case 0:  pattern = "2999"; // year
            strcat(CDateTime::datePartsOrder,"Y");  
            break;
         default: pattern = NULL; break;
      }
      if (pattern) {
         strcat(format,pattern);
         strcat(format,separator);
      }
      ptr = strtok(NULL,separator);
   }
   len = strlen(format);
   if (len)
      format[len-1] = 0;
   if (processingTime && CDateTime::m_time24Mode)
      strcat(format,"TM");

   return separator[0];
}

CDateTimeFormat::CDateTimeFormat() {
   char  dateBuffer[32];
   char  timeBuffer[32];
   // make a special date and time - today :)
   struct tm t;
   t.tm_year = 100;    // since 1900, -> 2000
   t.tm_mon  = 5;      // June (January=0)
   t.tm_mday = 17;
   t.tm_hour = 22;
   t.tm_min  = 48;
   t.tm_sec  = 59;
   t.tm_wday = 0;      // Sunday

#ifndef _WIN32
   // For unknown reason this call of setlocale() under Windows makes
   // calls of sprintf to produce access violations. If you know why please
   // tell me..
   setlocale(LC_TIME,"");
#endif

   // Build local data and time
   strftime(timeBuffer,32,"%X",&t);
   strftime(dateBuffer,32,"%x",&t);

   // Build local date and time formats
   CDateTime::datePartsOrder[0] = 0;
   CDateTime::dateSeparator = parseDateOrTime(CDateTime::dateFormat,dateBuffer);
   CDateTime::time24Mode(timeBuffer[0] == '2');

   // Filling up the week day names, as defined in locale.
   // This date should be Monday:
   t.tm_year = 103;    // since 1900, -> 2003
   t.tm_mon  = 9;
   t.tm_mday = 21;
   t.tm_hour = 0;
   t.tm_min  = 0;
   t.tm_sec  = 0;
   for (unsigned wday = 0; wday < 7; wday++) {
      t.tm_wday = wday;
      strftime(dateBuffer,32,"%A",&t);
      CDateTime::weekDayNames[wday] = dateBuffer;
   }

   // Filling up the month names, as defined in locale.
   // This date should be January 1st:
   t.tm_year = 103;    // since 1900, -> 2003
   t.tm_mon  = 1;
   t.tm_mday = 1;
   t.tm_hour = 0;
   t.tm_min  = 0;
   t.tm_sec  = 0;
   t.tm_wday = 3;
   for (unsigned month = 0; month < 12; month++) {
      t.tm_mon = month;
      strftime(dateBuffer,32,"%B",&t);
      CDateTime::monthNames[month] = dateBuffer;
   }
   tzset();
#ifdef __BORLANDC__
   const char *ptr = _tzname[0];
#else
   const char *ptr = tzname[0];
#endif
   char zname[4];
   int cnt = 0;
   if (strlen(ptr) < 4)
      strcpy(zname,ptr);
   else
      while (ptr && cnt < 3) {
      zname[cnt++] = *ptr;
      ptr = strchr(ptr,' ');
      if (ptr)
         ptr++;
   }
   zname[cnt] = 0;
   CDateTime::timeZoneName = zname;
#ifdef __BORLANDC__
   CDateTime::timeZoneOffset = -_timezone / 3600;
#else
   CDateTime::timeZoneOffset = -timezone / 3600;
#endif
}

static CDateTimeFormat dateTimeFormatInitializer;

void CDateTime::time24Mode(bool t24mode) {
   char  *timeBuffer = "10:48:59AM";

   if (t24mode)
      timeBuffer = "22:48:59";

   CDateTime::m_time24Mode = t24mode;
   CDateTime::timeSeparator = CDateTimeFormat::parseDateOrTime(CDateTime::fullTimeFormat,timeBuffer);
   strcpy(CDateTime::shortTimeFormat,CDateTime::fullTimeFormat);
   char *p = strchr(CDateTime::shortTimeFormat,CDateTime::timeSeparator);
   if (p) {
      p = strchr(p+1,CDateTime::timeSeparator);
      if (p) *p = 0;
   }
   if (!CDateTime::m_time24Mode) {
      strcat(CDateTime::fullTimeFormat,"TM");
      strcat(CDateTime::shortTimeFormat,"TM");
   }
}

int CDateTime::isLeapYear(const short year) {
   return (year&3) == 0 && year%100 != 0 || year%400 == 0;
}

void CDateTime::encodeDate(double &dt,short year,short month,short day) {
   if (year == 0 && month == 0 && day == 0) {
      dt = 0;
      return;
   }
   if (month < 1 || month > 12) {
      throw CException("Invalid month in the date");
   }
   int yearKind = isLeapYear(year);
   if (day < 1 || day > _monthDays[yearKind][month-1])
      throw CException("Invalid day in the date");
   if (year <= 0 || year > 9999)
      throw CException("Invalid year in the date");

   day += _monthDaySums[yearKind][month-1];
   int i = year - 1;
   dt = i * 365 + i / 4 - i / 100 + i / 400 + day - DateDelta;
}

void CDateTime::encodeDate(double &dt,const char *dat) {
   char     bdat[64];
   short    datePart[7], partNumber = 0;
   char     *ptr = NULL;

   memset(datePart,0,sizeof(datePart));
   upperCase(bdat,dat);

   if (strcmp(bdat,"TODAY") == 0) {
      dt = Date();        // Sets the current date
      return;
   } else {
      int len = strlen(bdat);
      for (int i = 0; i <= len && partNumber < 7; i++) {
         char c = bdat[i];
         if (c == dateSeparator || c == timeSeparator || c == ' ' || c == 0) {
            if (c == timeSeparator && partNumber < 3) partNumber = 3;
            if (ptr) { // end of token
               bdat[i] = 0;
               datePart[partNumber] = (short)atoi(ptr);
               partNumber++;
               ptr = NULL;
            }
         } else {
            if (!isdigit(c)) {
               dt = 0;
               return;
            }
            if (!ptr) ptr = bdat + i;
         }
      }
      if (partNumber < 3) { // Not enough date parts
         dt = 0;
         return;
      }
      short month=0, day=0, year=0;
      for (int ii = 0; ii < 3; ii++)
         switch (datePartsOrder[ii]) {
         case 'M': month = datePart[ii]; break;
         case 'D': day   = datePart[ii]; break;
         case 'Y': year  = datePart[ii]; break;
      }

      if (year < 100) {
         if (year < 35) year = short(year + 2000);
         else           year = short(year + 1900);
      }
      double dd;
      encodeDate(dd,year,month,day);
      if (partNumber > 3) { // Time part included into string
         double d;
         encodeTime(d,datePart[3],datePart[4],datePart[5],datePart[6]);
         dd += d;
      }
      dt = dd;

   }
}

void CDateTime::encodeTime(double& dt,short h,short m,short s,short ms) {
   dt = (h + ((m + (s + ms / 100.0) / 60.0) / 60.0)) / 24.0;
}

static int trimRight(char *s) {
   int   len = strlen(s);

   while ((len--) >= 0) {
      if ( (unsigned char)s[len] > 32 ) {
         len++;
         s[len] = 0;
         break;
      }
   }
   return len;
}

void CDateTime::encodeTime(double& dt,const char *tim) {
   char  bdat[32];
   short timePart[4] = { 0, 0, 0, 0},
   partNumber = 0;
   char  *ptr = NULL;
   bool  afternoon = false;

   upperCase(bdat,tim);

   if (!trimRight(bdat)) {
      dt = 0;
      return;
   }

   if (strcmp(bdat,"TIME") == 0) {
      dt = Time();        // Sets the current date
      return;
   } else {
      char *p = strstr(bdat,"AM");
      if (p) {
         *p = 0;
      } else {
         p = strstr(bdat,"PM");
         if (p) {
            *p = 0;
            afternoon = true;
         }
      }
      trimRight(bdat);
      int len = strlen(bdat);
      for (int i = 0; i <= len && partNumber < 4; i++) {
         char c = bdat[i];
         if (c == timeSeparator || c == ' ' || c == '.' || c == 0) {
            if (ptr) { // end of token
               bdat[i] = 0;
               timePart[partNumber] = (short) atoi(ptr);

               partNumber++;
               ptr = NULL;
            }
         } else {
            if (!isdigit(c)) {
               dt = 0;
               return;
            }
            if (!ptr) ptr = bdat + i;
         }
      }
      if (afternoon && timePart[0] != 12)
         timePart[0] = short(timePart[0] + 12);
      encodeTime(dt,timePart[0],timePart[1],timePart[2],timePart[3]);
   }
}

const int S1 = 24 * 60 * 60; // seconds in 1 day

void CDateTime::decodeTime(const double dt,short& h,short& m,short& s,short& ms) {
   double t = dt - (int) dt;

   if (t < 0) t = t + 1;

   int secs = int(t * S1 + 0.5);
   h = short(secs / 3600);
   secs = secs % 3600;
   m = short(secs / 60);
   secs = secs % 60;
   s = short(secs);
   ms = 0;
}

const int D1 = 365;              // Days in 1 year
const int D4 = D1 * 4 + 1;       // Days in 4 years
const int D100 = D4 * 25 - 1;    // Days in 100 years
const int D400 = D100 * 4 + 1;   // Days in 400 years

static void DivMod(int op1, int op2, int& div, int& mod) {
   div = op1 / op2;
   mod = op1 % op2;
}

void CDateTime::decodeDate(const double dat,short& year,short& month,short& day) {
   int Y, M, D, I;
   int T = (int) dat + DateDelta;

   T--;
   Y = 1;
   while (T >= D400) {
      T -= D400;
      Y += 400;
   }

   DivMod(T, D100, I, D);
   if (I == 4) {
      I--;
      D += D100;
   }

   Y += I * 100;
   DivMod(D, D4, I, D);
   Y += I * 4;
   DivMod(D, D1, I, D);
   if (I == 4) {
      I--;
      D += D1;
   }
   Y += I;
   year = Y;
   //year  = short (Y + 1900);

   int leapYear = isLeapYear(short(year));
   for (M = 0;;M++) {
      I = _monthDays[leapYear][M];
      if (D < I)
         break;
      D -= I;
   }

   month = short (M + 1);
   day   = short (D + 1);
}

//----------------------------------------------------------------
// Constructors
//----------------------------------------------------------------
CDateTime::CDateTime (short year,short month,short day,short hour,short minute,short second) {
   double t;
   encodeDate(m_dateTime,year,month,day);
   encodeTime(t,hour,minute,second);
   m_dateTime += t;
}

CDateTime::CDateTime (const char * dat) {
   string  s1( trim(dat) );
   string  s2;

   if (!*dat) {
      m_dateTime = 0;
      return;
   }
   int p = s1.find(" ");
   if (p >= 0) {
      s2 = trim(s1.substr(p+1,20));
      s1[p] = 0;
   }
   if ( strchr(s1.c_str(),dateSeparator) ) {
      encodeDate(m_dateTime, s1.c_str());
      if ( strchr(s2.c_str(),timeSeparator) ) {
         double dt;
         encodeTime(dt, s2.c_str());
         m_dateTime += dt;
      }
   }
   else  encodeTime(m_dateTime, s1.c_str());

}

CDateTime::CDateTime (const CDateTime &dt) {
   m_dateTime = dt.m_dateTime;
}

CDateTime::CDateTime (const double dt) {
   m_dateTime = dt;
}
//----------------------------------------------------------------
// Assignments
//----------------------------------------------------------------
void CDateTime::operator = (const CDateTime &dt) {
   m_dateTime = dt.m_dateTime;
}

void CDateTime::operator = (const char * dat) {
   if (dat)
      encodeDate(m_dateTime, dat);
   else m_dateTime = 0;
}

//----------------------------------------------------------------
// Conversion operations
//----------------------------------------------------------------
// CDateTime::operator int (void) { return (int) dateTime; }

CDateTime::operator double (void) const { return m_dateTime; }

//----------------------------------------------------------------
// Date Arithmetic
//----------------------------------------------------------------
CDateTime CDateTime::operator + (int i) {
   return CDateTime(m_dateTime + i);
}

CDateTime CDateTime::operator - (int i) {
   return CDateTime(m_dateTime - i);
}

CDateTime CDateTime::operator + (CDateTime& dt) {
   return CDateTime(m_dateTime + dt.m_dateTime);
}

CDateTime CDateTime::operator - (CDateTime& dt) {
   return CDateTime(m_dateTime - dt.m_dateTime);
}

CDateTime& CDateTime::operator += (int i) {
   m_dateTime += i;
   return *this;
}

CDateTime& CDateTime::operator -= (int i) {
   m_dateTime -= i;
   return *this;
}

CDateTime& CDateTime::operator += (CDateTime& dt) {
   m_dateTime += dt.m_dateTime;
   return *this;
}

CDateTime& CDateTime::operator -= (CDateTime& dt) {
   m_dateTime -= dt.m_dateTime;
   return *this;
}

CDateTime& CDateTime::operator ++() {
   m_dateTime += 1;
   return *this;
}

CDateTime &CDateTime::operator ++(int) {
   m_dateTime += 1;
   return *this;
}

CDateTime &CDateTime::operator --() {
   m_dateTime -= 1;
   return *this;
}

CDateTime &CDateTime::operator --(int) {
   m_dateTime -= 1;
   return *this;
}

//----------------------------------------------------------------
// Date comparison
//----------------------------------------------------------------

bool operator <  (const CDateTime &dt1, const CDateTime &dt2) {
   return ( dt1.m_dateTime < dt2.m_dateTime );
}

bool operator <= (const CDateTime &dt1, const CDateTime &dt2) {
   return ( (dt1.m_dateTime <= dt2.m_dateTime) );
}

bool operator >  (const CDateTime &dt1, const CDateTime &dt2) {
   return ( dt1.m_dateTime > dt2.m_dateTime );
}

bool operator >= (const CDateTime &dt1, const CDateTime &dt2) {
   return ( (dt1.m_dateTime >= dt2.m_dateTime) );
}

bool operator == (const CDateTime &dt1, const CDateTime &dt2) {
   return ( dt1.m_dateTime == dt2.m_dateTime );
}

bool operator != (const CDateTime &dt1, const CDateTime &dt2) {
   return ( dt1.m_dateTime != dt2.m_dateTime );
}

//----------------------------------------------------------------
// Format routine
//----------------------------------------------------------------
void CDateTime::formatDate (char *str) const {
   char *ptr = str;
   short month, day, year;

   if (m_dateTime == 0) {
      *str = 0;
      return;
   }
   decodeDate(m_dateTime,year,month,day);
   for (int i = 0; i < 3; i++) {
      switch (datePartsOrder[i]) {
         case 'M':   sprintf(ptr,"%02i%c",month,dateSeparator);
            break;
         case 'D':   sprintf(ptr,"%02i%c",day,dateSeparator);
            break;
         case 'Y':   sprintf(ptr,"%04i%c",year,dateSeparator);
            break;
      }
      ptr += strlen(ptr);
   }
   *(ptr-1) = 0;
}

void CDateTime::formatTime (char *str,bool ampm,bool showSeconds) const {
   short h,m,s,ms;

   if (m_dateTime == 0) {
      *str = 0;
      return;
   }
   decodeTime(m_dateTime,h,m,s,ms);
   const char *appendix = 0L;
   if (ampm) {
      if (h > 11) 
         appendix = "PM";
      else appendix = "AM";
      if (h > 12) h = h%12;
   }
   if (!showSeconds)
      sprintf(str,"%02i%c%02i",h,timeSeparator,m);
   else sprintf(str,"%02i%c%02i%c%02i",h,timeSeparator,m,timeSeparator,s);
   if (ampm)
      strcat(str,appendix);
}
//----------------------------------------------------------------
//  Miscellaneous Routines
//----------------------------------------------------------------
short CDateTime::dayOfYear( void ) const {
   CDateTime temp( 1, 1, year() );

   return (short) (m_dateTime - temp.m_dateTime);
}

// Get the current system time
CDateTime CDateTime::System() {
   time_t   tt;
   time(&tt);
   struct tm *t = localtime(&tt);
   double dat,tim;
   encodeDate(dat,short(t->tm_year+1900),short(t->tm_mon+1),short(t->tm_mday));
   encodeTime(tim,short(t->tm_hour),short(t->tm_min),short(t->tm_sec),short(0));
#ifndef _WIN32
   timeval tp;
   gettimeofday(&tp,0L);
   double mcsec = tp.tv_usec / 1000000.0 / (3600 * 24);
#else
   double mcsec = (GetTickCount() % 1000) / 1000.0 / (3600 * 24);
#endif
   return dat + tim + mcsec;
}

// Get the current system time with optional synchronization offset
CDateTime CDateTime::Now() {
   return (double)CDateTime::System() + CDateTime::m_dateTimeOffset;
}

// Set the synchronization offset
void CDateTime::Now(CDateTime dt) {
   CDateTime::m_dateTimeOffset = (double)dt - (double)CDateTime::System(); 
}

CDateTime CDateTime::Date() {
   double dat = Now();
   return double(int(dat));
}

CDateTime CDateTime::Time() {
   double dat = Now();
   return dat - int(dat);
}

short CDateTime::daysInMonth() const {
   short y, m, d;
   decodeDate(m_dateTime,y,m,d);
   return _monthDays[isLeapYear(y)][m-1];
}

unsigned CDateTime::date() const {
   return unsigned(m_dateTime);
}

short CDateTime::day() const {
   short y, m, d;
   decodeDate(m_dateTime,y,m,d);
   return d;
}

short CDateTime::month() const {
   short y, m, d;
   decodeDate(m_dateTime,y,m,d);
   return m;
}

short CDateTime::year() const {
   short y, m, d;
   decodeDate(m_dateTime,y,m,d);
   return y;
}

short CDateTime::dayOfWeek (void) const {
   return short((int(m_dateTime) - 1) % 7 + 1);
}

string CDateTime::dayOfWeekName (void) const {
   return CDateTime::weekDayNames[dayOfWeek() - 1];
}

string CDateTime::monthName() const {
   return CDateTime::monthNames[month()-1];
}

string CDateTime::dateString() const {
   char  buffer[32];
   formatDate(buffer);
   return string(buffer);
}

string CDateTime::timeString(bool showSeconds) const {
   char  buffer[32];
   formatTime(buffer,!m_time24Mode,showSeconds);
   return string(buffer);
}

void CDateTime::decodeDate(short *y,short *m,short *d) const {
   decodeDate(m_dateTime,*y,*m,*d);
}

void CDateTime::decodeTime(short *h,short *m,short *s,short *ms) const {
   decodeTime(m_dateTime,*h,*m,*s,*ms);
}

CDateTime CDateTime::convertCTime(const long tt) {
   struct tm *t = localtime((time_t*)&tt);
   double dat,tim;
   encodeDate(dat,short(t->tm_year+1900),short(t->tm_mon+1),short(t->tm_mday));
   encodeTime(tim,short(t->tm_hour),short(t->tm_min),short(t->tm_sec),short(0));
   return dat + tim;
}
