// by Carlos Santander Bernal
// this piece of code is absolutely free

private import std.date, std.string, std.c.stdio;

class FormatStringException : Exception {
	this (char [] str) {
		super("malformed format string: "~str);
	}
}

/*
if invalid_time => ""
given fri jan 22, 2004 17:03:34 gmt-5

yyyy=YYYY: 2004
yy=YY:     04

MM:        01
M:         1
MO=mo:     jan
month:     january

dd=DD:     22
d=D:       22

w:         fri
ww:        friday
W:         6
WW:        5

h=H12:     5
hh=HH12:   05
H=H24:     17
HH=HH24:   17

t:         pm
T:         PM

m:         3
mm:        03

s=S:       34
ss=SS:     34

z=Z:       -5
zz=ZZ:     -05
zzzz=ZZZZ: -0500

any other chars are written as they come
to write the formatting symbols (as "ww"), enclose them between % (like "%ww%")
to write the "%" sign, write 2 consecutives (like "%%")

to get the same result as std.date.toString(), use
fmt = "w mo dd H:mm:ss G%MT%zzzz yyyy"
*/
// based on std.date.toString
char [] toString(d_time time,char [] fmt) {
	if (time == d_time.init)
		return "";

	char [] res;

	char sign;
	int hr, mn, len;
	d_time t, offset, dst;

	dst = DaylightSavingTA(time);
	offset = LocalTZA + dst;
	t = time + offset;
	sign = '+';
	if (offset<0) {
		sign = '-';
		offset = -(LocalTZA+dst);
	}

	mn = cast(int) (offset/msPerMinute);
	hr = mn / 60;
	mn %= 60;

	int W = WeekDay(t),
		M = MonthFromTime(t),
		d = DateFromTime(t),
		H = HourFromTime(t),
		m = MinFromTime(t),
		s = SecFromTime(t);
	long yyyy = YearFromTime(t);

	for (int i;i<fmt.length;++i) {
		if (fmt[i]=='%')
			if (++i<fmt.length)
				if (fmt[i]=='%')
					res ~= '%';
				else
					while (fmt[i]!='%')
						res ~= fmt[i++];
			else
				THROW(fmt);
		else if (fmt[i]=='y' || fmt[i]=='Y')
			res ~= parseYear(fmt,i,yyyy);
		else if (fmt[i]=='M')
			res ~= parseMonth(fmt,i,M);
		else if (fmt[i]=='d' || fmt[i]=='D')
			res ~= parseDay(fmt,i,d);
		else if (fmt[i]=='w')
			res ~= parseWeekDay(fmt,i,W);
		else if (fmt[i]=='W')
			res ~= parseWeekDayNumber(fmt,i,W);
		else if (fmt[i]=='h' || fmt[i]=='H')
			res ~= parseHour(fmt,i,H);
		else if (fmt[i]=='t')
			if (H>11) res ~= "pm";
			else res ~= "am";
		else if (fmt[i]=='m')
			res ~= parseMinute(fmt,i,m,M);
		else if (fmt[i]=='T')
			if (H>11) res ~= "PM";
			else res ~= "AM";
		else if (fmt[i]=='s' || fmt[i]=='S')
			res ~= parseSecond(fmt,i,s);
		else if (fmt[i]=='z' || fmt[i]=='Z')
			res ~= parseTZ(fmt,i,sign,hr,mn);
		else
			res ~= fmt[i];
		//printf("[i=%d,res=%.*s]\n",i,res);
	}

	return res;
}

private {
	void THROW(char [] fmt) { throw new FormatStringException(fmt); }

	// parse 'yy' or 'yyyy' (don't worry about the case)
	char [] parseYear (char [] fmt,inout int pos,int year) {
		if (pos<=fmt.length-4)
			if ( comp(fmt[pos..pos+4],"yyyy") ) {
				pos += 3;
				return std.string.toString(year);
			}

		if (pos>fmt.length-2)
			THROW(fmt);
		
		if (comp(fmt[pos..pos+2],"yy")) {
			++pos;
			char * y = new char[3];
			sprintf(y,"%02d",year % 100);
			return std.string.toString(y);
		}

		return toStr(fmt[pos]);
	}

	// parse MM, M, MO
	char [] parseMonth(char [] fmt,inout int pos,int month) {
		if (pos<=fmt.length-2)
			if ( fmt[pos..pos+2] == "MM" ) {
				++pos;
				char * m = new char[3];
				sprintf(m,"%02d",month+1);
				return std.string.toString(m);
			} else if ( fmt[pos..pos+2] == "MO" ) {
				++pos;
				return getMonth(month,false);
			}

		return std.string.toString(month+1);
	}

	// parse dd,d (don't worry about the case)
	char [] parseDay(char [] fmt,inout int pos,int day) {
		if (pos<=fmt.length-2)
			if (comp(fmt[pos..pos+2],"dd")) {
				++pos;
				char * d = new char[3];
				sprintf(d,"%02d",day);
				return std.string.toString(d);
			}

		return std.string.toString(day);
	}

	// parse w,ww
	char [] parseWeekDay(char [] fmt,inout int pos,int W) {
		if (pos<=fmt.length-2)
			if (fmt[pos..pos+2]=="ww") {
				++ pos;
				return getWeekDay(W,true);
			}

		return getWeekDay(W,false);
	}

	// parse W,WW
	char [] parseWeekDayNumber(char [] fmt,inout int pos,int W) {
		if (pos<=fmt.length-2)
			if (fmt[pos..pos+2]=="WW") {
				++ pos;
				return std.string.toString(W ? W : 7);
			}

		return std.string.toString(W+1);
	}

	// parse h,H,hh,HH,H12,H24,HH12,HH24
	char [] parseHour (char [] fmt,inout int pos,int hour) {
		char [] format1() { //h,H12
			return std.string.toString(ampmHour(hour));
		}
		char [] format2() { //hh,HH12
			char * h = new char[3];
			sprintf(h,"%02d",ampmHour(hour));
			return std.string.toString(h);
		}
		char [] format3() { //H,H24
			return std.string.toString(hour);
		}
		char [] format4() { //HH,HH24
			char * h = new char[3];
			sprintf(h,"%02d",hour);
			return std.string.toString(h);
		}

		if (pos<=fmt.length-4) {
			if (fmt[pos..pos+4]=="HH12") {
				pos += 3;
				return format2();
			}
			if (fmt[pos..pos+4]=="HH24") {
				pos += 3;
				return format4();
			}
		}

		if (pos<=fmt.length-3) {
			if (fmt[pos..pos+3]=="H12") {
				pos += 2;
				return format1();
			}
			if (fmt[pos..pos+3]=="H24") {
				pos += 2;
				return format3();
			}
		}

		if (pos<=fmt.length-2) {
			if (fmt[pos..pos+2]=="hh") {
				++pos;
				return format2();
			}
			if (fmt[pos..pos+2]=="HH") {
				++pos;
				return format4();
			}
		}

		if (fmt[pos]=='h')
			return format1();
		return format3();
	}

	// parse m,mm,mo,month
	char [] parseMinute(char [] fmt,inout int pos,int min,int month) {
		if (pos<=fmt.length-5)
			if (fmt[pos..pos+5]=="month") {
				pos += 4;
				return getMonth(month,true);
			}

		if (pos<=fmt.length-2) {
			if (fmt[pos..pos+2]=="mo") {
				++pos;
				return getMonth(month,false);
			}
			if (fmt[pos..pos+2]=="mm") {
				++ pos;
				char * m = new char[3];
				sprintf(m,"%02d",min);
				return std.string.toString(m);
			}
		}

		return std.string.toString(min);
	}

	// parse s,ss (don't worry about the case)
	char [] parseSecond(char [] fmt,inout int pos,int sec) {
		if (pos<=fmt.length-2)
			if (comp(fmt[pos..pos+2],"ss")) {
				++ pos;
				char * s = new char[3];
				sprintf(s,"%02d",sec);
				return std.string.toString(s);
			}

		return std.string.toString(sec);
	}

	// parse z,zz,zzzz (don't worry about the case)
	char [] parseTZ(char [] fmt,inout int pos,char sign,int hr,int mn) {
		if (pos<=fmt.length-4)
			if (comp(fmt[pos..pos+4],"zzzz")) {
				pos += 3;
				char * s = new char[5];
				sprintf(s,"%c%02d%02d",sign,hr,mn);
				return std.string.toString(s);
			}

		if (pos<=fmt.length-2)
			if (comp(fmt[pos..pos+2],"zz")) {
				++ pos;
				char * s=new char[3];
				sprintf(s,"%c%02d",sign,hr);
				return std.string.toString(s);
			}

		return toStr(sign) ~ std.string.toString(hr);		
	}

	// the hour in am/pm format
	int ampmHour(int year) {
		if (year%12==0)
			return 12;
		return year % 12;
	}

	// the name of the month
	char [] getMonth(int month,bool longname) {
		static char [][] name =
			[ "january", "february", "march", "april",
			"may", "june", "july", "august",
			"september", "october", "november", "december" ],
			shortname =
			[ "jan", "feb", "mar", "apr", "may", "jun",
			"jul", "aug", "sep", "oct", "nov", "dec" ];
		if (longname)
			return name[month];
		return shortname[month];
	}

	// the name of the week day
	char [] getWeekDay(int weekday,bool longname) {
		static char [][] name =
			[ "sunday", "monday", "tuesday",
			"wednesday", "thursday", "friday", "saturday" ],
			shortname =
			[ "sun", "mon", "tue", "wed", "thu", "fri", "sat" ];
		if (longname)
			return name[weekday];
		return shortname[weekday];
	}

	// char -> char []
	char [] toStr(char c) {
		char [] r;
		r ~= c;
		return r;
	}

	// str1 == str2, w/o case
	bool comp ( char [] str1, char [] str2 ) {
		if (str1.length!=str2.length)
			return false;
		version (Windows)
			return stricmp( toStringz(str1), toStringz(str2) ) == 0;
		else
			return strcasecmp( toStringz(str1), toStringz(str2) ) == 0;
	}

	extern (C):
		version (Windows)
			int stricmp (char *, char *);
		else
			int strcasecmp (char *, char *);
}


