// Copyright (c) 2002 David Muse
// See the COPYING file for more information

#include <rudiments/datetime.h>
#include <rudiments/charstring.h>
#ifdef RUDIMENTS_HAVE_RTC
	#include <rudiments/file.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#ifdef RUDIMENTS_HAVE_RTC
	#include <linux/rtc.h>
	#include <sys/ioctl.h>
#endif

#ifdef RUDIMENTS_NAMESPACE
namespace rudiments {
#endif

class datetimeprivate {
	friend class datetime;
	private:
		#if defined(RUDIMENTS_HAVE_MKTIME)
			int32_t	_sec;
			int32_t	_min;
			int32_t	_hour;
			int32_t	_mday;
			int32_t	_mon;
			int32_t	_year;
			int32_t	_wday;
			int32_t	_yday;
			int32_t	_isdst;

			char	*_zone;
			int32_t	_gmtoff;

			char		*_timestring;
			struct tm	*_structtm;

			time_t	_epoch;

			environment	_env;
		#elif defined(RUDIMENTS_HAVE_GETSYSTEMTIME)
			SYSTEMTIME		_st;
			TIME_ZONE_INFORMATION	_tzi;
		#endif
};

static mutex	*_timemutex;

static const char _monthname[][10]={
	"January","February","March",
	"April","May","June",
	"July","August","September",
	"October","November","December"
};

static const char _monthabbr[][4]={
	"Jan","Feb","Mar",
	"Apr","May","Jun",
	"Jul","Aug","Sep",
	"Oct","Nov","Dec"
};

datetime::datetime() {
	pvt=new datetimeprivate;
	#if defined(RUDIMENTS_HAVE_MKTIME)
		pvt->_sec=0;
		pvt->_min=0;
		pvt->_hour=0;
		pvt->_mday=0;
		pvt->_mon=0;
		pvt->_year=0;
		pvt->_wday=0;
		pvt->_yday=0;
		pvt->_isdst=0;
		pvt->_zone=NULL;
		pvt->_gmtoff=0;
		pvt->_timestring=NULL;
		pvt->_structtm=NULL;
		pvt->_epoch=0;
	#elif defined(RUDIMENTS_HAVE_GETSYSTEMTIME)
		rawbuffer::zero(pvt->_st,sizeof(SYSTEMTIME));
		rawbuffer::zero(pvt->_tzi,sizeof(TIME_ZONE_INFORMATION));
	#endif
	_timemutex=NULL;
}

datetime::~datetime() {
	#if defined(RUDIMENTS_HAVE_MKTIME)
		delete[] pvt->_zone;
		delete[] pvt->_timestring;
		delete pvt->_structtm;
	#endif
	delete pvt;
}

bool datetime::initialize(const char *tmstring) {

	// get the date
	const char	*ptr=tmstring;
	#if defined(RUDIMENTS_HAVE_MKTIME)
	pvt->_mon=charstring::toInteger(ptr)-1;
	#elif defined(RUDIMENTS_HAVE_GETSYSTEMTIME)
	pvt->_st.wMonth=charstring::toShort(ptr);
	#endif
	ptr=charstring::findFirst(ptr,'/');
	if (!ptr || !ptr[0]) {
		return false;
	}
	ptr=ptr+sizeof(char);
	#if defined(RUDIMENTS_HAVE_MKTIME)
	pvt->_mday=charstring::toInteger(ptr);
	#elif defined(RUDIMENTS_HAVE_GETSYSTEMTIME)
	pvt->_st.wDay=charstring::toShort(ptr);
	#endif
	ptr=charstring::findFirst(ptr,'/');
	if (!ptr || !ptr[0]) {
		return false;
	}
	ptr=ptr+sizeof(char);
	#if defined(RUDIMENTS_HAVE_MKTIME)
	pvt->_year=charstring::toInteger(ptr)-1900;
	#elif defined(RUDIMENTS_HAVE_GETSYSTEMTIME)
	pvt->_st.wYear=charstring::toShort(ptr);
	#endif

	// get the time
	ptr=charstring::findFirst(ptr,' ');
	if (!ptr || !ptr[0]) {
		return false;
	}
	ptr=ptr+sizeof(char);
	#if defined(RUDIMENTS_HAVE_MKTIME)
	pvt->_hour=charstring::toInteger(ptr);
	#elif defined(RUDIMENTS_HAVE_GETSYSTEMTIME)
	pvt->_st.wHour=charstring::toShort(ptr);
	#endif
	ptr=charstring::findFirst(ptr,':');
	if (!ptr || !ptr[0]) {
		return false;
	}
	ptr=ptr+sizeof(char);
	#if defined(RUDIMENTS_HAVE_MKTIME)
	pvt->_min=charstring::toInteger(ptr);
	#elif defined(RUDIMENTS_HAVE_GETSYSTEMTIME)
	pvt->_st.wMinute=charstring::toShort(ptr);
	#endif
	ptr=charstring::findFirst(ptr,':');
	if (!ptr || !ptr[0]) {
		return false;
	}
	ptr=ptr+sizeof(char);
	#if defined(RUDIMENTS_HAVE_MKTIME)
	pvt->_sec=charstring::toInteger(ptr);
	#elif defined(RUDIMENTS_HAVE_GETSYSTEMTIME)
	pvt->_st.wSecond=charstring::toShort(ptr);
	#endif

	// initialize the daylight savings time flag
	pvt->_isdst=-1;

	// get the time zone if it was provided
	delete[] pvt->_zone;
	if ((ptr=charstring::findFirst(ptr,' '))) {
		ptr=ptr+sizeof(char);
		pvt->_zone=(ptr && ptr[0])?charstring::duplicate(ptr):NULL;
	} else {
		pvt->_zone=NULL;
	}

	// normalize, string and epoch
	return normalizeBrokenDownTime(true);
}

bool datetime::initialize(time_t seconds) {
	pvt->_epoch=seconds;
	return getBrokenDownTimeFromEpoch(true);
}

bool datetime::initialize(const struct tm *tmstruct) {

	pvt->_sec=tmstruct->tm_sec;
	pvt->_min=tmstruct->tm_min;
	pvt->_hour=tmstruct->tm_hour;
	pvt->_mday=tmstruct->tm_mday;
	pvt->_mon=tmstruct->tm_mon;
	pvt->_year=tmstruct->tm_year;
	pvt->_wday=tmstruct->tm_wday;
	pvt->_yday=tmstruct->tm_yday;
	pvt->_isdst=tmstruct->tm_isdst;
	// FIXME: what if the zone/offset are garbage, is there a good way to
	// tell?
	#ifdef RUDIMENTS_HAS___TM_ZONE
		pvt->_zone=charstring::duplicate(tmstruct->__tm_zone);
	#elif RUDIMENTS_HAS_TM_ZONE
		pvt->_zone=charstring::duplicate(tmstruct->tm_zone);
	#elif RUDIMENTS_HAS_TM_NAME
		pvt->_zone=charstring::duplicate(tmstruct->tm_name);
	#else
		if (_timemutex && !acquireLock()) {
			return false;
		}
		tzset();
		pvt->_zone=charstring::duplicate(
				(tzname[pvt->_isdst] && tzname[pvt->_isdst][0])?
						tzname[pvt->_isdst]:"UCT");
		releaseLock();
	#endif
	#ifdef RUDIMENTS_HAS___TM_GMTOFF
		pvt->_gmtoff=tmstruct->__tm_gmtoff;
	#elif RUDIMENTS_HAS_TM_GMTOFF
		pvt->_gmtoff=tmstruct->tm_gmtoff;
	#elif RUDIMENTS_HAS_TM_TZADJ
		pvt->_gmtoff=-tmstruct->tm_tzadj;
	#else
		if (_timemutex && !acquireLock()) {
			return false;
		}
		tzset();
		#ifdef RUDIMENTS_HAS_TIMEZONE
			pvt->_gmtoff=-timezone;
		#else
			pvt->_gmtoff=-_timezone;
		#endif
		releaseLock();
	#endif
	return normalizeBrokenDownTime(true);
}

int32_t datetime::getHour() const {
	return pvt->_hour;
}

int32_t datetime::getMinutes() const {
	return pvt->_min;
}

int32_t datetime::getSeconds() const {
	return pvt->_sec;
}

int32_t datetime::getMonth() const {
	return pvt->_mon+1;
}

const char *datetime::getMonthName() const {
	return _monthname[pvt->_mon];
}

const char *datetime::getMonthAbbreviation() const {
	return _monthabbr[pvt->_mon];
}

int32_t datetime::getDayOfMonth() const {
	return pvt->_mday;
}

int32_t datetime::getDayOfWeek() const {
	return pvt->_wday+1;
}

int32_t datetime::getDayOfYear() const {
	return pvt->_yday+1;
}

int32_t datetime::getYear() const {
	return pvt->_year+1900;
}

bool datetime::isDaylightSavingsTime() const {
	return pvt->_isdst;
}

const char *datetime::getTimeZoneString() const {
	return pvt->_zone;
}

int32_t datetime::getTimeZoneOffset() const {
	return pvt->_gmtoff;
}

time_t datetime::getEpoch() const {
	return pvt->_epoch;
}

struct tm *datetime::getTm() {

	if (_timemutex && !acquireLock()) {
		return NULL;
	}

	delete pvt->_structtm;
	pvt->_structtm=new struct tm;
	pvt->_structtm->tm_mon=pvt->_mon;
	pvt->_structtm->tm_mday=pvt->_mday;
	pvt->_structtm->tm_year=pvt->_year;
	pvt->_structtm->tm_hour=pvt->_hour;
	pvt->_structtm->tm_min=pvt->_min;
	pvt->_structtm->tm_sec=pvt->_sec;
	pvt->_structtm->tm_isdst=pvt->_isdst;

	// FIXME: lookup combined zone here too
	char	*oldzone;
	if ((pvt->_zone && pvt->_zone[0] &&
			!setTimeZoneEnvVar(pvt->_zone,&oldzone,false)) ||
		(mktime(pvt->_structtm)==-1) ||
		(pvt->_zone && pvt->_zone[0] &&
			!restoreTimeZoneEnvVar(oldzone))) {
		return NULL;
	}

	releaseLock();
	return pvt->_structtm;
}

bool datetime::setSeconds(int32_t seconds) {
	pvt->_sec=seconds;
	return normalizeBrokenDownTime(true);
}

bool datetime::setMinutes(int32_t minutes) {
	pvt->_min=minutes;
	return normalizeBrokenDownTime(true);
}

bool datetime::setHours(int32_t hours) {
	pvt->_hour=hours;
	return normalizeBrokenDownTime(true);
}

bool datetime::setDays(int32_t days) {
	pvt->_yday=days;
	return normalizeBrokenDownTime(true);
}

bool datetime::setMonths(int32_t months) {
	pvt->_mon=months;
	return normalizeBrokenDownTime(true);
}

bool datetime::setYears(int32_t years) {
	pvt->_year=years;
	return normalizeBrokenDownTime(true);
}

bool datetime::addSeconds(int32_t seconds) {
	pvt->_sec=pvt->_sec+seconds;
	return normalizeBrokenDownTime(true);
}

bool datetime::addMinutes(int32_t minutes) {
	pvt->_min=pvt->_min+minutes;
	return normalizeBrokenDownTime(true);
}

bool datetime::addHours(int32_t hours) {
	pvt->_hour=pvt->_hour+hours;
	return normalizeBrokenDownTime(true);
}

bool datetime::addDays(int32_t days) {
	pvt->_yday=pvt->_yday+days;
	return normalizeBrokenDownTime(true);
}

bool datetime::addMonths(int32_t months) {
	pvt->_mon=pvt->_mon+months;
	return normalizeBrokenDownTime(true);
}

bool datetime::addYears(int32_t years) {
	pvt->_year=pvt->_year+years;
	return normalizeBrokenDownTime(true);
}

void datetime::setTimeMutex(mutex *mtx) {
	_timemutex=mtx;
}

const char *datetime::getString() {
	delete[] pvt->_timestring;
	size_t	timestringlen=2+1+2+1+charstring::integerLength(getYear())+1+
				2+1+2+1+2+1+
				charstring::length(getTimeZoneString())+1;
	pvt->_timestring=new char[timestringlen];
	snprintf(pvt->_timestring,timestringlen,
			"%02d/%02d/%d %02d:%02d:%02d %s",
			getMonth(),getDayOfMonth(),getYear(),
			getHour(),getMinutes(),getSeconds(),
			getTimeZoneString());
	return pvt->_timestring;
}


bool datetime::getSystemDateAndTime() {
	return initialize(time(NULL));
}

bool datetime::setSystemDateAndTime() {
	timeval	tv;
	tv.tv_sec=pvt->_epoch;
	tv.tv_usec=0;
	// FIXME: should set /etc/localtime (or /etc/TZ) and TZ env var too...
	return !settimeofday(&tv,NULL);
}

bool datetime::getHardwareDateAndTime(const char *hwtz) {

	#ifdef RUDIMENTS_HAVE_RTC
		// open the rtc
		file	devrtc;
		if (!devrtc.open("/dev/rtc",O_RDONLY)) {
			return false;
		}

		// get the time from the rtc
		rtc_time rt;
		if (devrtc.ioctl(RTC_RD_TIME,&rt)==-1) {
			devrtc.close();
			return false;
		}
		devrtc.close();

		// set the local values
		pvt->_mon=rt.tm_mon;
		pvt->_mday=rt.tm_mday;
		pvt->_year=rt.tm_year;
		pvt->_hour=rt.tm_hour;
		pvt->_min=rt.tm_min;
		pvt->_sec=rt.tm_sec;
		pvt->_isdst=rt.tm_isdst;
		pvt->_zone=charstring::duplicate(hwtz);

		return normalizeBrokenDownTime(true);
	#else
		return false;
	#endif
}

bool datetime::getAdjustedHardwareDateAndTime(const char *hwtz) {
	return (getHardwareDateAndTime(hwtz) && adjustTimeZone(NULL));
}

bool datetime::setHardwareDateAndTime(const char *hwtz) {

	#ifdef RUDIMENTS_HAVE_RTC
		// open the rtc
		file	devrtc;
		if (!devrtc.open("/dev/rtc",O_WRONLY)) {
			return false;
		}

		// adjust the time zone
		if (!adjustTimeZone(hwtz)) {
			devrtc.close();
			return false;
		}

		// set the values to be stored in the rtc
		rtc_time rt;
		rt.tm_mon=pvt->_mon;
		rt.tm_mday=pvt->_mday;
		rt.tm_year=pvt->_year;
		rt.tm_hour=pvt->_hour;
		rt.tm_min=pvt->_min;
		rt.tm_sec=pvt->_sec;
		rt.tm_wday=pvt->_wday;
		rt.tm_yday=pvt->_yday;
		rt.tm_isdst=pvt->_isdst;

		// set the rtc and clean up
		bool	retval=(devrtc.ioctl(RTC_SET_TIME,&rt)!=-1);
		devrtc.close();

		return retval;
	#else
		return false;
	#endif
}

bool datetime::adjustTimeZone(const char *newtz) {
	return adjustTimeZone(newtz,false);
}

bool datetime::adjustTimeZone(const char *newtz, bool ignoredst) {

	if (!acquireLock()) {
		return false;
	}

	// Clear out the zone so getBrokenDownTimeFromEpoch() won't try to
	// preserve it.
	delete[] pvt->_zone;
	pvt->_zone=NULL;

	// Change the time zone, get the broken down time relative to the
	// current epoch, in the new time zone.
	char	*oldzone;
	bool	retval=true;
	if ((newtz && newtz[0] &&
			!setTimeZoneEnvVar(newtz,&oldzone,ignoredst)) ||
		!getBrokenDownTimeFromEpoch(false) ||
			(newtz && newtz[0] &&
				!restoreTimeZoneEnvVar(oldzone))) {
		retval=false;
	}

	releaseLock();
	return retval;
}

char *datetime::getString(time_t seconds) {
	datetime	dt;
	return ((dt.initialize(seconds))?
		charstring::duplicate(dt.getString()):NULL);
}

char *datetime::getString(const struct tm *tmstruct) {
	datetime	dt;
	return ((dt.initialize(tmstruct))?
		charstring::duplicate(dt.getString()):NULL);
}

time_t datetime::getEpoch(const char *datestring) {
	datetime	dt;
	return ((dt.initialize(datestring))?dt.getEpoch():-1);
}

time_t datetime::getEpoch(const struct tm *tmstruct) {
	datetime	dt;
	return ((dt.initialize(tmstruct))?dt.getEpoch():-1);
}

bool datetime::setTimeZoneEnvVar(const char *zone, char **oldzone,
							bool ignoredst) {

	// If a daylight timezone was passed in, override it with the combined
	// timezone.
	//
	// If a standard timezone was passed in, but we want to account for
	// daylight savings time (ie. EST was passed in, but daylight savings
	// time is currently in effect, so we want to use EDT instead) then
	// use the combined timezone.
	const char	*combinedzone=lookupCombinedTimeZone(zone);
	const char	*realzone=zone;
	if (daylightZone(zone) || !ignoredst) {
		realzone=combinedzone;
	}

	const char	*tz=pvt->_env.getValue("TZ");
	if (tz) {
		*oldzone=charstring::duplicate(tz);
	} else {
		*oldzone=NULL;
	}
	return pvt->_env.setValue("TZ",realzone);
}

bool datetime::restoreTimeZoneEnvVar(const char *oldzone) {
	if (oldzone) {
		bool	retval=pvt->_env.setValue("TZ",oldzone);
		delete[] oldzone;
		return retval;
	}
	pvt->_env.remove("TZ");
	return true;
}

bool datetime::getBrokenDownTimeFromEpoch(bool needmutex) {

	// I'm using localtime here instead of localtime_r because
	// localtime_r doesn't appear to handle the timezone properly,
	// at least, not in glibc-2.3
	if (needmutex && !acquireLock()) {
		return false;
	}
	bool	retval=false;
	struct tm	*tms;
	if ((tms=localtime(&pvt->_epoch))) {
		pvt->_sec=tms->tm_sec;
		pvt->_min=tms->tm_min;
		pvt->_hour=tms->tm_hour;
		pvt->_mday=tms->tm_mday;
		pvt->_mon=tms->tm_mon;
		pvt->_year=tms->tm_year;
		pvt->_isdst=tms->tm_isdst;
		retval=normalizeBrokenDownTime(false);
	}
	if (needmutex) {
		releaseLock();
	}
	return retval;
}

bool datetime::normalizeBrokenDownTime(bool needmutex) {

	if (needmutex && !acquireLock()) {
		return false;
	}

	// If a time zone was passed in, use it.
	char	*oldzone=NULL;

	if (pvt->_zone && pvt->_zone[0] &&
			!setTimeZoneEnvVar(pvt->_zone,&oldzone,false)) {
		if (needmutex) {
			releaseLock();
		}
		return false;
	}

	bool	retval=true;

	// copy relevent values into a struct tm
	struct tm	tms;
	tms.tm_sec=pvt->_sec;
	tms.tm_min=pvt->_min;
	tms.tm_hour=pvt->_hour;
	tms.tm_mday=pvt->_mday;
	tms.tm_mon=pvt->_mon;
	tms.tm_year=pvt->_year;
	tms.tm_isdst=pvt->_isdst;

	// mktime() will get the epoch, set wday, yday, isdst
	// and normalize other values
	time_t	ep=mktime(&tms);
	if (ep==-1) {
		retval=false;
	} else {

		// store the epoch
		pvt->_epoch=ep;

		// copy values back out of struct tm
		pvt->_sec=tms.tm_sec;
		pvt->_min=tms.tm_min;
		pvt->_hour=tms.tm_hour;
		pvt->_mday=tms.tm_mday;
		pvt->_mon=tms.tm_mon;
		pvt->_year=tms.tm_year;
		pvt->_isdst=tms.tm_isdst;
		pvt->_wday=tms.tm_wday;
		pvt->_yday=tms.tm_yday;
	
		// Use tzset to get the timezone name
		tzset();
		delete[] pvt->_zone;
		pvt->_zone=charstring::duplicate(tzname[pvt->_isdst]);

		// Get the offset from the struct tm if we can, otherwise get
		// it from the value set by tzset()
		#ifdef RUDIMENTS_HAS___TM_GMTOFF
			pvt->_gmtoff=tms.__tm_gmtoff;
		#elif RUDIMENTS_HAS_TM_GMTOFF
			pvt->_gmtoff=tms.tm_gmtoff;
		#elif RUDIMENTS_HAS_TM_TZADJ
			pvt->_gmtoff=-tms.tm_tzadj;
		#elif RUDIMENTS_HAS_TIMEZONE
			pvt->_gmtoff=-timezone;
		#else
			pvt->_gmtoff=-_timezone;
		#endif
	}

	if (oldzone) {
		retval=(retval && restoreTimeZoneEnvVar(oldzone));
	}

	if (needmutex) {
		releaseLock();
	}

	return retval;
}

bool datetime::acquireLock() {
	return !(_timemutex && !_timemutex->lock());
}

bool datetime::releaseLock() {
	return !(_timemutex && !_timemutex->unlock());
}

static const char * const _timezones[]={

	"ACST",	// Australian Central Standard Time	UTC + 9:30 hours
	"ACDT",	// Australian Central Daylight Time	UTC + 10:30 hours
	"ACST-10:30ACDT",

	"AST",	// Atlantic Standard Time		UTC - 4 hours
	"ADT",	// Atlantic Daylight Time		UTC - 3 hours
	"AST4ADT",

	"AEST",	// Australian Eastern Standard Time	UTC + 10 hours
	"AEDT",	// Australian Eastern Daylight Time	UTC + 11 hours
	"AEST10AEDT",

	"AKST",	// Alaska Standard Time	UTC - 9 hours
	"AKDT",	// Alaska Daylight Time	UTC - 8 hours
	"AKST-9AKDT",

	"CST",	// Central Standard Time	UTC - 6 hours
	"CDT",	// Central Daylight Time	UTC - 5 hours
	"CST6CDT",

	"CET",	// Central European Time	UTC + 1 hour
	"CEST",	// Central European Summer Time	UTC + 2 hours
	"CET-1CST",

	"EST",	// Eastern Standard Time	UTC - 5 hours
	"EDT",	// Eastern Daylight Time	UTC - 4 hours
	"EST5EDT",

	"EET",	// Eastern European Time	UTC + 2 hours
	"EEST",	// Eastern European Summer Time	UTC + 3 hours
	"EET-2EEST",

	"GMT",	// Greenwich Mean Time	UTC
	"BST",	// British Summer Time	UTC + 1 hour
	"GMT0BST",

	"HNA",	// Heure Normale de l'Atlantique	UTC - 4 hours
	"HAA",	// Heure Avancée de l'Atlantique	UTC - 3 hours
	"HNA4HAA",

	"HNC",	// Heure Normale du Centre		UTC - 6 hours
	"HAC",	// Heure Avancée du Centre		UTC - 5 hours
	"HNC6HAC",

	"HAST",	// Hawaii-Aleutian Standard Time	UTC - 10 hours
	"HADT",	// Hawaii-Aleutian Daylight Time	UTC - 9 hours
	"HAST10HADT",

	"HNE",	// Heure Normale de l'Est	UTC - 5 hours
	"HAE",	// Heure Avancée de l'Est	UTC - 4 hours
	"HNE5HAE",

	"HNP",	// Heure Normale du Pacifique	UTC - 8 hours
	"HAP",	// Heure Avancée du Pacifique	UTC - 7 hours
	"HNP8HAP",

	"HNR",	// Heure Normale des Rocheuses	UTC - 7 hours
	"HAR",	// Heure Avancée des Rocheuses	UTC - 6 hours
	"HNR7HAR",

	"HNT",	// Heure Normale de Terre-Neuve	UTC - 3:30 hours
	"HAT",	// Heure Avancée de Terre-Neuve	UTC - 2:30 hours
	"HNT3:30HAT",

	"HNY",	// Heure Normale du Yukon	UTC - 9 hours
	"HAY",	// Heure Avancée du Yukon	UTC - 8 hours
	"HNY9HAY",

	"MST",	// Mountain Standard Time	UTC - 7 hours
	"MDT",	// Mountain Daylight Time	UTC - 6 hours
	"MST7MDT",

	"MEZ",	// Mitteleuropäische Zeit	UTC + 1 hour
	"MESZ",	// Mitteleuropäische Sommerzeit	UTC + 2 hours
	"MEZ-1MESZ",

	"NST",	// Newfoundland Standard Time	UTC - 3:30 hours
	"NDT",	// Newfoundland Daylight Time	UTC - 2:30 hours
	"NST3:30NDT",

	"PST",	// Pacific Standard Time	UTC - 8 hours
	"PDT",	// Pacific Daylight Time	UTC - 7 hours
	"PST8PDT",

	"WET",	// Western European Time	UTC
	"WEST",	// Western European Summer Time	UTC + 1 hour
	"WET-1WEST",

	"",
	"",
	""
};

static const int32_t	_timezoneoffsets[]={

	34200,	// Australian Central Standard Time	UTC + 9:30 hours
	37800,	// Australian Central Daylight Time	UTC + 10:30 hours
	34200,

	-14400,	// Atlantic Standard Time		UTC - 4 hours
	-10800,	// Atlantic Daylight Time		UTC - 3 hours
	-14400,

	36000,	// Australian Eastern Standard Time	UTC + 10 hours
	39600,	// Australian Eastern Daylight Time	UTC + 11 hours
	36000,

	-32400,	// Alaska Standard Time	UTC - 9 hours
	-28800,	// Alaska Daylight Time	UTC - 8 hours
	-32400,

	-21600,	// Central Standard Time	UTC - 6 hours
	-18000,	// Central Daylight Time	UTC - 5 hours
	-21600,

	3600,	// Central European Time	UTC + 1 hour
	7200,	// Central European Summer Time	UTC + 2 hours
	3600,

	-18000,	// Eastern Standard Time	UTC - 5 hours
	-14400,	// Eastern Daylight Time	UTC - 4 hours
	-18000,

	7200,	// Eastern European Time	UTC + 2 hours
	10800,	// Eastern European Summer Time	UTC + 3 hours
	7200,

	0,	// Greenwich Mean Time	UTC
	3600,	// British Summer Time	UTC + 1 hour
	0,

	-14400,	// Heure Normale de l'Atlantique	UTC - 4 hours
	-10800,	// Heure Avancée de l'Atlantique	UTC - 3 hours
	-14400,

	-21600,	// Heure Normale du Centre		UTC - 6 hours
	-18000,	// Heure Avancée du Centre		UTC - 5 hours
	-21600,

	-36000,	// Hawaii-Aleutian Standard Time	UTC - 10 hours
	-32400,	// Hawaii-Aleutian Daylight Time	UTC - 9 hours
	-36000,

	-18000,	// Heure Normale de l'Est	UTC - 5 hours
	-14400,	// Heure Avancée de l'Est	UTC - 4 hours
	-18000,

	-28800,	// Heure Normale du Pacifique	UTC - 8 hours
	-25200,	// Heure Avancée du Pacifique	UTC - 7 hours
	-28800,

	-25200,	// Heure Normale des Rocheuses	UTC - 7 hours
	-21600,	// Heure Avancée des Rocheuses	UTC - 6 hours
	-25200,

	-12600,	// Heure Normale de Terre-Neuve	UTC - 3:30 hours
	-9000,	// Heure Avancée de Terre-Neuve	UTC - 2:30 hours
	-12600,

	-32400,	// Heure Normale du Yukon	UTC - 9 hours
	-28800,	// Heure Avancée du Yukon	UTC - 8 hours
	-32400,

	-25200,	// Mountain Standard Time	UTC - 7 hours
	-21600,	// Mountain Daylight Time	UTC - 6 hours
	-25200,

	3600,	// Mitteleuropäische Zeit	UTC + 1 hour
	7200,	// Mitteleuropäische Sommerzeit	UTC + 2 hours
	3600,

	-12600,	// Newfoundland Standard Time	UTC - 3:30 hours
	-9000,	// Newfoundland Daylight Time	UTC - 2:30 hours
	-12600,

	-28800,	// Pacific Standard Time	UTC - 8 hours
	-25200,	// Pacific Daylight Time	UTC - 7 hours
	-28800,

	0,	// Western European Time	UTC
	3600,	// Western European Summer Time	UTC + 1 hour
	0,

	0,
	0,
	0
};

const char * const *datetime::getTimeZoneAbbreviations() {
	return _timezones;
}

const int32_t *datetime::getTimeZoneOffsets() {
	return _timezoneoffsets;
}

// FIXME: this is kind of lame.  There must be a better way to do this than
// looking up values in a table embedded in the class.  I guess I could look
// through every zoneinfo file (on platforms that support them), but I've tried
// that before and I get multiple hits for a given zone.  If anyone reads this
// comment and knows the answer, please let me know.
const char *datetime::lookupCombinedTimeZone(const char *zn) const {

	// if the zone name is longer than 4 chars, then it's a combined zone
	if (charstring::length(zn)>4) {
		return zn;
	}

	// run through the list of timezones that observe daylight
	// savings time, if "zn" is in that list, return the
	// combined zone name, otherwise just return "zn"
	for (int index=0; _timezones[index][0]; index=index+3) {
		if (!charstring::compare(zn,_timezones[index]) ||
			!charstring::compare(zn,_timezones[index+1])) {
			return _timezones[index+2];
		}
	}
	return zn;
}

bool datetime::daylightZone(const char *zn) const {

	// run through the list of timezones that observe daylight
	// savings time, if "zn" is in that list, return true,
	// otherwise return false
	for (int index=0; _timezones[index][0]; index=index+3) {
		if (!charstring::compare(zn,_timezones[index+1])) {
			return true;
		}
	}
	return false;
}

bool datetime::validDateTime(const char *string) {

	// must at least be 19 chars long (format: "00/00/0000 00:00:00")
	if (charstring::length(string)<19) {
		return false;
	}

	// truncate timezone
	char	*newstring=charstring::duplicate(string,19);

	// Initialize a new instance of datetime using the string, then
	// compare it to the string returned by the instance of datetime
	// (ignoring the timezone).
	// If they're the same then it was a valid date.
	datetime	dt;
	bool	result=(dt.initialize(newstring) &&
			!charstring::compare(newstring,dt.getString(),19));
	delete[] newstring;
	return result;
}

#ifdef RUDIMENTS_NAMESPACE
}
#endif


syntax highlighted by Code2HTML, v. 0.9.1