/***************************************************************************
                          SIMPLY POWERFUL TOOLKIT (SPTK)
                          CDateTime.cpp  -  description
                             -------------------
    begin                : Tue Dec 14 1999
    copyright            : (C) 1999-2006 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;
using namespace sptk;

namespace sptk {

	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 / 1000.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 * 3600; // 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;

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

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 + (CDateTime& dt) {
	return CDateTime(m_dateTime + dt.m_dateTime);
}

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

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 ( (double) dt1 < (double) dt2 );
}

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

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

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

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

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

CDateTime operator + (const CDateTime &dt1, const CDateTime &dt2) {
	return double(dt1) + double(dt2);
}

CDateTime operator - (const CDateTime &dt1, const CDateTime &dt2) {
	return double(dt1) - double(dt2);
}

//----------------------------------------------------------------
// 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() {
	double dat,tim;
#ifndef _WIN32
	timeval tp;
	time_t  tt;
	time(&tt);
	struct tm *t = localtime(&tt);
	encodeDate(dat,short(t->tm_year+1900),short(t->tm_mon+1),short(t->tm_mday));
	gettimeofday(&tp,0L);
	short msec = short(tp.tv_usec / 1000);
	encodeTime(tim,short(t->tm_hour),short(t->tm_min),short(t->tm_sec),short(msec));
#else
	SYSTEMTIME systemTime;
	GetLocalTime(&systemTime);
	encodeDate(dat,short(systemTime.wYear),short(systemTime.wMonth),short(systemTime.wDay));
	encodeTime(tim,short(systemTime.wHour),short(systemTime.wMinute),short(systemTime.wSecond),short(systemTime.wMilliseconds));
#endif
	return dat + tim;
}

// 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 time_t 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;
}
