#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <memory.h>

/*
Calendar lib used by SurgePlus client and server.
*/
#define DMID 14

#ifndef IS_SURGEPLUS_CLIENT
#include "../dftp/mail/mail.h"
#endif
#include "debug.h"

int calendar_lib_gmt_offset_at_time(time_t t);

#include "calendar_lib.h"

#ifdef IS_SURGEPLUS_CLIENT
#include "VCTime.h"
struct tm *safe_localtime(time_t *t, struct tm *bf)
{
	struct tm *ret=vc_localtime(t);
	if (!ret) return NULL;
	memcpy(bf,ret,sizeof(tm));
	return bf;
}
struct tm *safe_gmtime(time_t *t, struct tm *bf)
{
	struct tm *ret=vc_gmtime(t);
	if (!ret) return NULL;
	memcpy(bf,ret,sizeof(tm));
	return bf;
}
#else
/*#define vc_localtime localtime*/
#define vc_mktime mktime
#define vc_time time
#endif

/* !fmsg */
int calendar_lib_is_leap_year(int year)
{
	if (year%4==0 && (year%100!=0 || year%400==0)) return 1;
	return 0;
}

static int calendar_lib_months_days[]={31,28,31,30,31,30,31,31,30,31,30,31};

int calendar_lib_days_in_month(int month, int year) /* month from 1 to 12 */
{
	if (month<1) month=1;
	if (month>12) month=12;
	if (month==2 && calendar_lib_is_leap_year(year)) return 29;
	return calendar_lib_months_days[month-1];
}

void calendar_lib_month_fix(int *month_from_0_to_11, int *year)
{
	while(*month_from_0_to_11<0) {
		*month_from_0_to_11+=12;
		(*year)--;
	}
	while(*month_from_0_to_11>=12) {
		*month_from_0_to_11-=12;
		(*year)++;
	}
}

void calendar_lib_inc_days(int *day, int *month_from_0_to_11, int *year, int ddays)
{
	int n;
	*day+=ddays;
	while (*day<1) {
		(*month_from_0_to_11)--;
		calendar_lib_month_fix(month_from_0_to_11,year);
		n=calendar_lib_days_in_month(*month_from_0_to_11+1,*year);
		*day+=n;
	}
	n=calendar_lib_days_in_month(*month_from_0_to_11+1,*year);
	while(*day>n) {
		(*day)-=n;
		(*month_from_0_to_11)++;
		calendar_lib_month_fix(month_from_0_to_11,year);
		n=calendar_lib_days_in_month(*month_from_0_to_11+1,*year);
	}
}

int calendar_lib_day_of_week(int year, int month, int day)
/* Year - 4 digits. month 1-12. day 1-31.
*/
{
	struct tm t;
	memset(&t,0,sizeof(t));
	if (year>1900) year-=1900;
	t.tm_year=year;
	t.tm_mon=month-1;
	t.tm_mday=day;
	mktime(&t);
	return t.tm_wday;
}


int calendar_lib_seconds_until_time(int year, int month, int day, int hour, int min, int sec, CalendarTimeZone *user_tz)
/*
Gives how many seconds until a particular time, taking DST into account.
e.g the same time 6 months from now will not be a multiple of 86400 seconds, but
will be 3600 seconds off.
We have to use visual c calls for this since borland's localtime function
is broken for times that are not current. mktime on both compilers
is also wrong outside of US (but at least MS documents this)
/*
Year: either 4 digit year or year-1900.
Month: 0-11
Day: 1-31
*/
{
	int dif;
	int h1,h2,hdif;
	time_t then,then2;
	struct tm tm1,*tt;
	int offset;
	struct tm *tt2;
	struct tm tmbf1;
	memset(&tm1,0,sizeof(tm1));
	tt=&tm1;
	if (year>1900) year=year-1900;
	if (sec>=60) {
		min=min+sec/60;
		sec=sec%60;
	}
	if (min>=60) {
		hour=hour+min/60;
		min=min%60;
	}
	if (hour>=24) {
		day=day+hour/24;
		hour=hour%24;
	}
	tt->tm_year=year;
	tt->tm_hour=hour;
	tt->tm_min=min;
	tt->tm_sec=sec;
	tt->tm_mday=day;
	tt->tm_mon=month;
	h1=hour;
	then=vc_mktime(tt);
	offset=calendar_lib_offset_at(then,user_tz);
	then=then-offset;

	dif=(int)(then-vc_time(NULL));
	then2=then;
	tt2=calendar_lib_localtime(&then2,&tmbf1,user_tz);
/*	tt2=vc_localtime(&then2);*/
	if (tt2==NULL) return 0; /* before 1970 */
/*	tt2=localtime(&then2);*/
	h2=tt2->tm_hour;
	hdif=h2-h1;
	if (hdif<-12) hdif+=24;
	if (hdif>12) hdif-=24;

	dif=dif-hdif*60*60;
	return dif;
}

time_t calendar_lib_mktime(struct tm *t, CalendarTimeZone *user_tz)
/*
Unlike the borland and visual c time functions, this one is correct for the local time-zone. It works by
doing a mktime, then localtiming that value back again, and looking at the difference from what it should be, then adjusting.
*/
{
	int diff;
	if (t->tm_year<70) return 0;
	diff=calendar_lib_seconds_until_time(t->tm_year,t->tm_mon,t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec,user_tz);
	return vc_time(NULL)+diff;
}

time_t calendar_lib_mktime_for_offset(struct tm *tt, int offset_from_gmt)
{
	struct tm *tt2;
	struct tm tmbf1;
	time_t then;
	int hdif;
	int dif=0;
	int h1,h2;
	int local_offset;
	time_t estimated_return_value;
	time_t actual_return_value;
	then=vc_mktime(tt);

	h1=tt->tm_hour;

	local_offset=calendar_lib_gmt_offset_at_time(then);

	estimated_return_value=then-(offset_from_gmt-local_offset);

	tt2=calendar_lib_localtime_for_offset(estimated_return_value,&tmbf1,offset_from_gmt);
/*	tt2=vc_localtime(&then2);*/
	if (tt2==NULL) return 0; /* before 1970 */
/*	tt2=localtime(&then2);*/
	h2=tt2->tm_hour;
	hdif=h2-h1;
	if (hdif<-12) hdif+=24;
	if (hdif>12) hdif-=24;

	dif=hdif*60*60;

	actual_return_value=estimated_return_value-dif;
	//printf("actual =%d dif=%d\n",actual_return_value,dif);
	return actual_return_value;
}


int calendar_lib_tm_to_day(struct tm *t, CalendarTimeZone *user_tz)
/*
Takes a struct tm. Ignores time part and returns the number of
days since 1 Jan 1970
*/
{

	time_t t1,t2;
	struct tm tt;
	int diff;
	memcpy(&tt,t,sizeof(tt));
	tt.tm_hour=12;
	t1=calendar_lib_mktime(&tt,user_tz);
	tt.tm_hour=12;
	tt.tm_year=1970;
	tt.tm_mday=1;
	tt.tm_mon=0;
	t2=calendar_lib_mktime(&tt,user_tz);
	diff=(int) (t1-t2);
	diff=(diff+60*60*12)/(60*60*24);
	return diff;
}



int calendar_lib_gmt_offset_at_time(time_t t)
/*
Returns the current time-zone offset from GMT.
*/
{
	time_t t2=t;
	struct tm tt1,tt2;
	int diff;
	int day_diff;
	safe_localtime(&t2,&tt1);
	t2=t;
	safe_gmtime(&t2,&tt2);
	diff=tt1.tm_sec-tt2.tm_sec+(tt1.tm_min-tt2.tm_min)*60+(tt1.tm_hour-tt2.tm_hour)*60*60;
	if (tt1.tm_year>tt2.tm_year) day_diff=1;
	else if (tt1.tm_year<tt2.tm_year) day_diff=-1;
	else if (tt1.tm_mon>tt2.tm_mon) day_diff=1;
	else if (tt1.tm_mon<tt2.tm_mon) day_diff=-1;
	else if (tt1.tm_mday>tt2.tm_mday) day_diff=1;
	else if (tt1.tm_mday<tt2.tm_mday) day_diff=-1;
	else day_diff=0;

	diff=diff+day_diff*60*60*24;
	return diff;
}

void calendar_lib_guess_ds_rule( CalendarTimeZone *user_tz)
{
	/*
	According to
	http://webexhibits.org/daylightsaving/g.html
	most countries (all in Eurpoe, north america, australisa) with the exception of a 
	few we probably don't care about
	have rules like
	last Sunday in Month
	or 
	first Sunday in Month
	3rd sunday in month

	*/
	struct tm tmbf;
	struct tm *tt;
	time_t t;
	int i;

	user_tz->ds_rules[0].change_by=0;
	user_tz->ds_rules[1].change_by=0;

	if (user_tz->change_at[0]==0 || user_tz->change_at[1]==0) return;
	for(i=0;i<2;i++) {
		int nth=0;
		t=user_tz->change_at[i];

		t+=60*60*3; // Make sure past start of day.

		tt=calendar_lib_localtime(&t,&tmbf,user_tz);
		if(tt==NULL) return;
		//printf("wday=%d day=%d mon=%d\n",tt->tm_wday,tt->tm_mday,tt->tm_mon);
		while(tt->tm_mday>=1) {
			tt->tm_mday-=7;
			nth++;
		}
		if (nth>=4) nth=9;
		user_tz->ds_rules[i].change_by=user_tz->change_by[i];
		user_tz->ds_rules[i].nth=nth;
		user_tz->ds_rules[i].weekday=tt->tm_wday;
		user_tz->ds_rules[i].month_num=tt->tm_mon;
		user_tz->ds_rules[i].at_minutes=tt->tm_hour*60+tt->tm_min-(60*3);
			//printf("rule is on %dth %d (0=Sun) of month %d (0=Jan), at %d mins adjust %d\n",nth,tt->tm_wday,tt->tm_mon,user_tz->ds_rules[i].at_minutes,user_tz->change_by[i]);
	}
}


int calendar_lib_next_change_time(time_t t,  int cur_offset, CalendarTimeZone *user_tz, time_t *change_at, int *change_by, int is_prev)
{
	struct tm tmbf;
	struct tm *tt;
	int wday;
	int nth;
	int rule_index=0;
	int mday=0;
	int minutes;
	int inc=is_prev?-1:1;

	t=t+60*60*24*35*inc;

	//printf("offset=%d\n",cur_offset);

	tt=calendar_lib_localtime_for_offset(t,&tmbf,cur_offset);

	//tt=calendar_lib_localtime(&t,&tmbf,user_tz);
	if (user_tz->ds_rules[0].change_by==0 || user_tz->ds_rules[1].change_by==0) return false;
	while(tt->tm_mon!=user_tz->ds_rules[0].month_num && tt->tm_mon!=user_tz->ds_rules[1].month_num) {
		tt->tm_mon+=inc;
		if (tt->tm_mon>=12) {
			tt->tm_mon=0;
			tt->tm_year++;
		}
		if (tt->tm_mon<0) {
			tt->tm_mon=11;
			tt->tm_year--;
		}
	}
	if (tt->tm_mon==user_tz->ds_rules[1].month_num)
		rule_index=1;
	mday=1;
	wday=calendar_lib_day_of_week(tt->tm_year+1900,tt->tm_mon+1,mday);
	//printf("first day is %d, want %d\n",wday,user_tz->ds_rules[rule_index].weekday);
	while(wday!=user_tz->ds_rules[rule_index].weekday) {
		mday++;
		wday=(wday+1)%7;
	}
	nth=user_tz->ds_rules[rule_index].nth;
	//printf("use rule is on %dth %d (0=Sun) of month %d (0=Jan), at %d mins adjust %d\n",nth,wday,tt->tm_mon,user_tz->ds_rules[rule_index].at_minutes,user_tz->change_by[rule_index]);
	if (nth<9) {
		mday+=7*(nth-1);
	}
	else {
		while(mday+7<=calendar_lib_days_in_month(tt->tm_mon+1,tt->tm_year+1900)) {
			mday+=7;
		}
	}
	minutes=user_tz->ds_rules[rule_index].at_minutes;
	tt->tm_mday=mday;
	tt->tm_hour=minutes/60;
	tt->tm_min=minutes%60;
	tt->tm_sec=0;

	*change_at=calendar_lib_mktime_for_offset(tt,cur_offset);
	*change_by=user_tz->ds_rules[rule_index].change_by;
	return true;


}


int calendar_lib_user_offset_at(time_t t, CalendarTimeZone *user_tz)
{
	int i;
	int user_offset;
	int num_valid=0;
	static int cc=0;
	//static int ccs=0;
	time_t last_valid=0;
	int did_num=0;
	if (user_tz==0) return calendar_lib_gmt_offset_at_time(t);
	user_offset=user_tz->offset;
	if (t==0) return user_offset;
	//printf("want at %d\n",t);
	if (t<60*60*29) {
		return user_offset;
	}
	for(i=0;i<MAX_CALENDAR_TIMEZONE_CHANGES;i++) {
		if (user_tz->change_at[i]==0) break;
		num_valid++;
		last_valid=user_tz->change_at[i];
		if (user_tz->change_at[i]<t) {
			did_num++;
			user_offset+=user_tz->change_by[i];
		}
	}
	//printf("at %d, offset=%d\n",t,user_offset);
	if (did_num==num_valid && num_valid>0) {
		// this is a time more than a year in the future (past cuurently listed dst times)
		// Use rules to work out real time.
		time_t change_at;
		time_t upto=last_valid;
		int change_by;
		int ind=0;
		while(true) {
			if (ind>=MAX_CALENDAR_TIMEZONE_CHANGES2) break;
			change_at=user_tz->future_changes_at[ind];
			change_by=user_tz->future_changes_by[ind];
			if (change_at==0) {
				if (!calendar_lib_next_change_time(upto,  user_offset, user_tz, &change_at, &change_by, false)) {
					change_at=1;
					change_by=0;
				}
				//printf("slow cf at %d by %d, %d\n",change_at,change_by,ccs++);
				user_tz->future_changes_at[ind]=(int)change_at;
				user_tz->future_changes_by[ind]=change_by;
			}
			if (change_at==1) {
				break;
			}
			ind++;


//		chrisp commented out, since this expression is always false...
//			if (change_at>=4294967296-(60*60*24*400)) break;

			//printf("cf at %d by %d, %d\n",change_at,change_by,cc++);
			if (change_at<t) {
				user_offset+=change_by;
			}
			else {
				break;
			}
			upto=change_at;
		}
	}
	if (did_num==0 && num_valid>0) {
		time_t change_at;
		time_t upto=user_tz->change_at[0];
		int change_by;
		int ind=0;
		while(true) {
			if (ind>=MAX_CALENDAR_TIMEZONE_CHANGES2) break;
			change_at=user_tz->past_changes_at[ind];
			change_by=user_tz->past_changes_by[ind];
			if (change_at==0) {
				if (!calendar_lib_next_change_time(upto,  user_offset, user_tz, &change_at, &change_by, true)) {
					change_at=1;
					change_by=0;
				}
				//printf("slow c at %d by %d, %d\n",change_at,change_by,ccs++);
				user_tz->past_changes_at[ind]=change_at;
				user_tz->past_changes_by[ind]=change_by;
			}
			if (change_at==1) {
				break;
			}
			ind++;

			if (change_at<=60*60*24*400) break;
			//printf("c at %d by %d, %d\n",change_at,change_by,cc++);
			if (change_at>t) {
				user_offset-=change_by;
			}
			else {
				break;
			}
			upto=change_at;
		}

	}
	//printf("at %d, offset=%d\n",t,user_offset);
	return user_offset;
}

int calendar_lib_offset_at(time_t t, CalendarTimeZone *user_tz)
/*
If user_tz!=NULL, returns difference between local time and users' tz at given time 't'
*/
{
	int local_offset,user_offset;
	if (user_tz==NULL) return 0;
	local_offset=calendar_lib_gmt_offset_at_time(t);
	user_offset=calendar_lib_user_offset_at(t,user_tz);
	return user_offset-local_offset;
}

time_t calendar_lib_adjust_tm_for_offset(time_t a, int offset)
{
		int local_offset2;
		int local_offset1=calendar_lib_gmt_offset_at_time(a);

		local_offset2=calendar_lib_gmt_offset_at_time(a+offset);
		/* Calculate offset at new time, just in case local dst switchover occurs between the difference: */
		/*
		e.g if we go forward by an hour during this time, must subtract 1 hour from new val.
		*/
		if (local_offset1!=local_offset2) {
			int sub=local_offset2-local_offset1;
			offset-=sub;
		}

		a=a+offset;
		return a;
}

struct tm *calendar_lib_localtime(time_t *t, struct tm *tt, CalendarTimeZone *user_tz)
{
	time_t a=*t;
	int offset;

	offset=calendar_lib_offset_at(a,user_tz);
	a=calendar_lib_adjust_tm_for_offset(a,offset);

	return safe_localtime(&a,tt);
/*	struct tm *ttt=vc_localtime(t);
	memcpy(tt,ttt,sizeof(*ttt));
	return tt;*/
}

struct tm *calendar_lib_localtime_for_offset(time_t t, struct tm *tt, int offset_from_gmt)
{

	int local_offset=calendar_lib_gmt_offset_at_time(t);
	
	t+=offset_from_gmt-local_offset;

	return safe_localtime(&t,tt);
/*	struct tm *ttt=vc_localtime(t);
	memcpy(tt,ttt,sizeof(*ttt));
	return tt;*/
}


int calendar_lib_tm_same_dayp(struct tm *t1, struct tm *t2)
{
	if (!t1 || !t2) return false;
	return t1->tm_year==t2->tm_year && t1->tm_mon==t2->tm_mon && t1->tm_mday==t2->tm_mday;
}

int calendar_lib_tm_same_day(struct tm t1, struct tm t2)
{
	return calendar_lib_tm_same_dayp(&t1,&t2);
}


int calendar_lib_is_nth_weekday_of_month(int day, int month, int year, int n, int backwards_search, int weekend_search)
{
	int days_in_month=calendar_lib_days_in_month(month,year);
	int cday=1;
	int inc=1;
	int wday;
	if (backwards_search) {
		cday=days_in_month;
		inc=-1;
	}
	wday=calendar_lib_day_of_week(year,month,cday);

	if (n>=31) n=31;

	while(cday<=days_in_month && cday>=1) {
		int is_correct_type_of_day=0;
		if (weekend_search)
			is_correct_type_of_day=wday==0 || wday==6;
		else
			is_correct_type_of_day=wday>0 && wday<6;

		if (is_correct_type_of_day) {
			n--;
			if (n==0) return day==cday;
		}
		cday+=inc;
		wday+=inc;
		wday=(wday+7)%7;
	}
	return 0;
}


int calendar_lib_is_nth_x_of_month(int n, int x, int day, int month, int year)
/*
Assumes day=1->31 month=1->12, year=4 digits.
*/
{
	int wday;
	int days_in_month=calendar_lib_days_in_month(month,year);
	if (days_in_month<day) return 0;

	if (x==9) { // Day of month:
		if (n==99) {
			if (day==days_in_month) return 1;
			return 0;
		}
		return n==day;
	}
	wday=calendar_lib_day_of_week(year,month,day);
	//TDateTime d=TDateTime(year,month,day);
	if (x<7) { // Day of week:
		int from,to;
		if (wday!=x) return 0;
		if (n<=1) {
			if (day<=7) return 1;
		}
		if (n==99) { // Last day of month
			// e.g. 30 days in month. If day>=24, return true;
			if (day+6>=days_in_month) return 1;
		}
		// e.g. if n==2, must be in range 8->14
		from=1+7*(n-1);
		to=from+6;
		if (day>=from && day<=to) return 1;
		return 0;
	}


	if (x==7) {
		// Nth week day
		if (n==99) {
			return calendar_lib_is_nth_weekday_of_month(day,month,year,1,1,0);
		}
		else {
			return calendar_lib_is_nth_weekday_of_month(day,month,year,n,0,0);
		}
	}
	if (x==8) {
		// Nth weekend day.
		if (n==99) {
			return calendar_lib_is_nth_weekday_of_month(day,month,year,1,1,1);
		}
		else {
			return calendar_lib_is_nth_weekday_of_month(day,month,year,n,0,1);
		}
	}
	return 0;
}



time_t calendar_lib_event_get_next_occurance_do(CalendarEvent e, time_t from_time, int find_previous, int allow_on_current_day, CalendarTimeZone *user_tz)
{
	struct tm tnow,tevent,tnext;
	time_t ret;
	int day1,day2;
	struct tm tp;
	int i;
	time_t ret2;

	calendar_lib_localtime(&from_time,&tnow,user_tz);
	calendar_lib_localtime(&e.event_start,&tevent,user_tz);
/*	memcpy(&tnow,vc_localtime(&from_time),sizeof(tnow));
	memcpy(&tevent,vc_localtime(&e.event_start),sizeof(tevent));*/

	if (e.repeat_type==0) {
		if (find_previous) {
			if (e.event_start>from_time) return 0;
		}
		else {
			if (e.event_start<from_time) return 0;
		}
		return e.event_start;
	}
	memset(&tnext,0,sizeof(tnext));
	tnext.tm_hour=tevent.tm_hour;
	tnext.tm_min=tevent.tm_min;
	tnext.tm_sec=tevent.tm_sec;

	day1=calendar_lib_tm_to_day(&tevent,user_tz);
	day2=calendar_lib_tm_to_day(&tnow,user_tz);
	if (e.repeat_type==1) {
		int occurs_in_x_days;
		int repeat_days=e.repeat_interval/(60*60*24);
		if (repeat_days<=0) return 0;

		if (day2==day1) {
			if (allow_on_current_day)
				occurs_in_x_days=0;
			else
				occurs_in_x_days=repeat_days;
		}
		else {
			if (find_previous) {
				occurs_in_x_days=(day2-day1)%repeat_days;
				if (occurs_in_x_days==0) occurs_in_x_days=repeat_days;
			}
			else {
				occurs_in_x_days=repeat_days-(day2-day1)%repeat_days;
			}
			if (occurs_in_x_days==repeat_days && allow_on_current_day)
				occurs_in_x_days=0;
		}

		tnext.tm_year=tnow.tm_year;
		tnext.tm_mon=tnow.tm_mon;
		if (find_previous)
			tnext.tm_mday=tnow.tm_mday-occurs_in_x_days;
		else
			tnext.tm_mday=tnow.tm_mday+occurs_in_x_days;
	}
	else {
		int ii;
		int month_dif=tnow.tm_mon-tevent.tm_mon+(tnow.tm_year-tevent.tm_year)*12;

		int month_inc=12;
		if (e.repeat_every_month>0) {
			int occurs_in_x_months;

			if (find_previous) {
				occurs_in_x_months=-((month_dif)%e.repeat_every_month);
			}
			else {
				occurs_in_x_months=e.repeat_every_month-(month_dif)%e.repeat_every_month;
			}
			if (occurs_in_x_months==e.repeat_every_month) occurs_in_x_months=0;
			tnext.tm_year=tnow.tm_year;
			tnext.tm_mon=tnow.tm_mon+occurs_in_x_months;
			month_inc=e.repeat_every_month;
			if (find_previous) month_inc=-e.repeat_every_month;
		}
		else if (e.repeat_on_month>0) {
			tnext.tm_year=tnow.tm_year;
			tnext.tm_mon=e.repeat_on_month-1;
			if (find_previous) {
				if (tnext.tm_mon>tnow.tm_mon) tnext.tm_year--;
				month_inc=-12;
			}
			else {
				if (tnext.tm_mon<tnow.tm_mon) tnext.tm_year++;
				month_inc=12;
			}
		}
		else {
			/* Unknown type.*/
			return 0;
		}
		for(ii=0;ii<2;ii++) {
			int did=0;
			int day_reject;
			int i;
			while(tnext.tm_mon>=12) {
				tnext.tm_mon-=12;
				tnext.tm_year+=1;
			}
			while(tnext.tm_mon<0) {
				tnext.tm_mon+=12;
				tnext.tm_year-=1;
			}
			for(i=1;i<=31;i++) {
				if (calendar_lib_is_nth_x_of_month(e.repeat_day_num,e.repeat_day_type,i,tnext.tm_mon+1,tnext.tm_year+1900)) {
					tnext.tm_mday=i;
					did=1;
					break;
				}
			}
			if (!did) return 0;
			day_reject=(find_previous && tnext.tm_mday>tnow.tm_mday) || (!find_previous && tnext.tm_mday<tnow.tm_mday);
			if (tnext.tm_year==tnow.tm_year && tnext.tm_mon==tnow.tm_mon &&
				(day_reject || (tnext.tm_mday==tnow.tm_mday && !allow_on_current_day))) {
				if (ii==2) return 0;
				tnext.tm_mon+=month_inc;
			}
			else {
				break;
			}
		}
	}



	ret=calendar_lib_mktime(&tnext,user_tz);
	ret2=ret;
	calendar_lib_localtime(&ret2,&tp,user_tz);
	if (e.alternative_day_mask[tp.tm_wday]==2) {
		/* Alternative time/duration on this day:*/
		tnext.tm_hour=0;
		tnext.tm_min=0;
		tnext.tm_sec=e.alternative_day_time[tp.tm_wday];
	}

	for(i=0;i<e.repeat_exceptions_time_length && i<e.repeat_exceptions_length;i++) {
		struct tm t;
		if (e.repeat_exceptions_time[i]==0) continue; /* It is a a non-occurance exception instead of an alternative time exception*/
		calendar_lib_localtime(&e.repeat_exceptions[i],&t,user_tz);

		if (calendar_lib_tm_same_day(t,tnext)) {
			tnext.tm_hour=0;
			tnext.tm_min=0;
			tnext.tm_sec=e.repeat_exceptions_time[i]-1;
		}
	}

	ret=calendar_lib_mktime(&tnext,user_tz);

	if (e.event_last_repeat_at>0 && ret>e.event_last_repeat_at) return 0;
	if (ret<e.event_start) return 0;
	return ret;
}

time_t calendar_lib_event_get_next_occurance(CalendarEvent e, time_t now_in_user_time, int find_previous, CalendarTimeZone *user_tz)
/*
Takes a calendar event and a time and returns the time_t (in user time)
at which the event should next occur on or after the given time. Returns 0 if it never occurs after this time.
*/
{
	time_t t;
	int loop_count=0;
	if (find_previous) {
		if (e.event_last_repeat_at>0 && now_in_user_time>e.event_last_repeat_at)
			now_in_user_time=e.event_last_repeat_at;
	}
	else {
		if (now_in_user_time<e.event_start) now_in_user_time=e.event_start;
	}
	t=now_in_user_time;
	t=calendar_lib_event_get_next_occurance_do(e,t,find_previous,1,user_tz);
	while(1) {
		if (loop_count++>=100) return 0; /* Just in case it is set to repeat every 7 days but only occur on a day of the week we can't reach (or anything similar)*/
		if (t==0) return 0;
		if ((find_previous && t<=now_in_user_time) || (!find_previous && t>=now_in_user_time)) {
			int ok=1;
			int i;
			struct tm tnext;
			if (e.repeat_type==0) return t;
			calendar_lib_localtime(&t,&tnext,user_tz);
/*			memcpy(&tnext,vc_localtime(&t),sizeof(tnext));*/
			if (e.alternative_day_mask[tnext.tm_wday]==1) ok=0;
			for(i=0;ok && i<e.repeat_exceptions_length;i++) {
				struct tm tes,*te;
				if (i<e.repeat_exceptions_time_length && e.repeat_exceptions_time[i]>0) continue; // It is an alternative time exception instead of a non-occurance exception
				calendar_lib_localtime(&e.repeat_exceptions[i],&tes,user_tz);
				te=&tes;
				if (te->tm_year==tnext.tm_year && te->tm_mon==tnext.tm_mon && te->tm_mday==tnext.tm_mday) {
					ok=0;
				}
			}
			if ((find_previous && t<=now_in_user_time) || (!find_previous && t>=now_in_user_time)) {
			}
			else {
				ok=0;
			}
			if (ok) return t;
		}
		t=calendar_lib_event_get_next_occurance_do(e,t,find_previous,0,user_tz);
	}
}

int calendar_lib_cur_timezone_offset_at(time_t t)
/*
returns the local time-zone offset from GMT at this particular time.
*/
{
	CalendarTimeZone tz;
	memset(&tz,0,sizeof(tz));
	return -calendar_lib_offset_at(t,&tz);
}

int calendar_lib_get_hour(char *rec, int *hour, int *min, int *sec);
int calendar_lib_get_zone(char *rec); /* return time zone offset in seconds, GMT = 0 */
int calendar_lib_tzone_find(char *s);

time_t calendar_lib_parse_date(char *rec)
{
	struct tm t;
	struct tm t2;
	time_t v;
	char bf[200]; 
	char *s;
	int tzone;
	int y;
	int cur_tzone_mod=false;
	int done_year=false;

	ncpy(bf,rec,199); s = bf;
	memset(&t,0,sizeof(t));
	s = strtok(bf,", \n\t");
	if (s==NULL) return 0;
	if (!sisdigit(*s)) { 
		/* Must be optional Day, ignore it!! */
		s = strtok(NULL," \n\t"); if (s==NULL) return 0;
	} 
	if (!sisdigit(*s) && lib_month_find(s)>=0) {
		/* one dumb virus scanner gives dates in this format:
		Date: Sat, Apr 03, 2004 10:46 AM EST
		instead of the standard:
		Date: Wed, 15 Dec 2004 16:40:02 +1300
	*/ 
		t.tm_mon = lib_month_find(s); /* returns 0-11 */
		if (t.tm_mon<0) return 0;
		s = strtok(NULL," \n\t"); if (s==NULL) return 0;
		t.tm_mday = atoi(s); 
		if (t.tm_mday==0) return 0;
	}
	else {
		t.tm_mday = atoi(s); 
		if (t.tm_mday==0) return 0;
		s = strtok(NULL," \n\t"); if (s==NULL) return 0;
		t.tm_mon = lib_month_find(s); /* returns 0-11 */
		if (t.tm_mon<0) return 0;
	}
	s = strtok(NULL," \n\t"); if (s==NULL) return 0;
	if (s[0] && s[1] && s[2]==':') {
		/* Some programs send with time before year: 
		Date: Mon Mar 10 16:56:23 2003 -0800 (PST)
		instead of the standard:
		Date: Wed, 15 Dec 2004 16:40:02 +1300
	*/
		done_year=false;
	}
	else {
		done_year=true;
		y = atoi(s);
		if (y<50) y += 100;  /* Allow 00 = 2000*/
		if (y>200) y -= 1900; /* Allow 2010 = 110 */
		t.tm_year = y;
		if (t.tm_year==0) return 0;
		s = strtok(NULL," \n\t");/* if (s==NULL) return 0;*/
	}
	/*if (!get_hour(s,&t.tm_hour,&t.tm_min,&t.tm_sec)) return 0;*/

	if (s) calendar_lib_get_hour(s,&t.tm_hour,&t.tm_min,&t.tm_sec);

	s = strtok(NULL," \n\t"); 

	if (!done_year) {
		y = atoi(s);
		if (y<50) y += 100;  /* Allow 00 = 2000*/
		if (y>200) y -= 1900; /* Allow 2010 = 110 */
		t.tm_year = y;
		if (t.tm_year==0) return 0;
		s = strtok(NULL," \n\t");/* if (s==NULL) return 0;*/
	 }

	if (s==NULL) { /* Should have been a time zone, lets assume GMT */ 	
		tzone = 0;
		/*
		Matt changed - let's assume it's the local timezone.
		*/
		cur_tzone_mod=true;

	} else {
		tzone = calendar_lib_get_zone(s);
	}


	if (s!=NULL) s = strtok(NULL," \n\t"); 
	if (s!=NULL) tzone += calendar_lib_get_zone(s); /*Offset from time zone*/

	/*
	Algorithm. Parse time given.
	Get cur_tzone in hours
	(eg+1300)
	subtract cur timezone hours from parsed time.

	Convert to local time
	*/
	memcpy(&t2,&t,sizeof(t));
	v=calendar_lib_mktime(&t2,NULL);
	if (v==0) return 0; /* probably pre-1970 */
	tzone=-tzone;
	if (!cur_tzone_mod) {
		tzone+=calendar_lib_cur_timezone_offset_at(v); 
		/* By now, tzone should be difference between their zone and ours. */
	}


	if (tzone > (60*60*27) ) tzone = 0;
	if (tzone < (-60*60*27) ) tzone = 0;
	/*tmsg("tzone=%d date=%d hr=%d min=%d\n",tzone,t.tm_mday,t.tm_hour,t.tm_min);*/
	t.tm_hour += (tzone/(60*60));
	t.tm_min += ((tzone % (60*60))/60);



/*	t.tm_sec += tzone;*/

	return calendar_lib_mktime(&t,NULL);
}
int calendar_lib_get_hour(char *rec, int *hour, int *min, int *sec)
{
	char hours[200],bf[50];
	char *s2,*s3;

	*hour = 0; *min = 0; *sec = 0;
	ncpy(hours,rec,190);
	s2 = strchr(hours,':');
	if (s2==NULL) { /* Maybe it's 4 digit format, no colon's */
		if (lib_strlen(hours)==2) {
			*hour = atoi(hours); return TRUE;
		}
		if (lib_strlen(hours)==3) {
			ncpy(bf,hours,1); 
		 	*hour = atoi(bf); return TRUE;
		}
		if (lib_strlen(hours)!=4) return FALSE; 
		ncpy(bf,hours,2); *hour = atoi(bf);
		ncpy(bf,hours+2,2); *min = atoi(bf);
		*sec = 0;
		return TRUE;
	} 
	s3 = strchr(s2+1,':');
	*hour = atoi(hours);
	*min = atoi(s2+1);
	*sec = 0;	
	if (s3!=NULL) *sec = atoi(s3+1);
	return TRUE;
}
int calendar_lib_get_zone(char *rec) /* return time zone offset in seconds, GMT = 0 */
{
	int tsign = 1;
	int h,m,sec;

	if (*rec=='+') rec++;
	if (*rec=='-') {tsign = -1; rec++;}
	if (sisdigit(*rec)) {
		if (!calendar_lib_get_hour(rec,&h,&m,&sec)) return 0;
		return tsign*(h*60*60+m*60+sec);
	} else {
		return calendar_lib_tzone_find(rec);
	}	
}

int calendar_lib_extract_zone(char *date)
{
	char *last_space=NULL;
	char *p;
	for(p=date;*p;p++) {
		if (*p==' ') last_space=p;
		if (*p==':') last_space=NULL;
	}
	if (!last_space) return 0;
	return calendar_lib_get_zone(last_space+1);
}

typedef struct {char *name; int type; int value;} TABLE;
#define tZONE 0
#define tDAYZONE 1
#define HOUR(x) (x*60)

static TABLE	tzone_table[] = {
    { "gmt",	tZONE,     HOUR( 0) },	/* Greenwich Mean */
    { "ut",	tZONE,     HOUR( 0) },	/* Universal */
    { "utc",	tZONE,     HOUR( 0) },	/* Universal Coordinated */
    { "cut",	tZONE,     HOUR( 0) },	/* Coordinated Universal */
    { "z",	tZONE,     HOUR( 0) },	/* Greenwich Mean */
    { "wet",	tZONE,     HOUR( 0) },	/* Western European */
    { "bst",	tDAYZONE,  HOUR( 0) },	/* British Summer */
    { "nst",	tZONE,     HOUR(3)+30 }, /* Newfoundland Standard */
    { "ndt",	tDAYZONE,  HOUR(3)+30 }, /* Newfoundland Daylight */
    { "ast",	tZONE,     HOUR( 4) },	/* Atlantic Standard */
    { "adt",	tDAYZONE,  HOUR( 4) },	/* Atlantic Daylight */
    { "est",	tZONE,     HOUR( 5) },	/* Eastern Standard */
    { "edt",	tDAYZONE,  HOUR( 5) },	/* Eastern Daylight */
    { "cst",	tZONE,     HOUR( 6) },	/* Central Standard */
    { "cdt",	tDAYZONE,  HOUR( 6) },	/* Central Daylight */
    { "mst",	tZONE,     HOUR( 7) },	/* Mountain Standard */
    { "mdt",	tDAYZONE,  HOUR( 7) },	/* Mountain Daylight */
    { "pst",	tZONE,     HOUR( 8) },	/* Pacific Standard */
    { "pdt",	tDAYZONE,  HOUR( 8) },	/* Pacific Daylight */
    { "yst",	tZONE,     HOUR( 9) },	/* Yukon Standard */
    { "ydt",	tDAYZONE,  HOUR( 9) },	/* Yukon Daylight */
    { "akst",	tZONE,     HOUR( 9) },	/* Alaska Standard */
    { "akdt",	tDAYZONE,  HOUR( 9) },	/* Alaska Daylight */
    { "hst",	tZONE,     HOUR(10) },	/* Hawaii Standard */
    { "hast",	tZONE,     HOUR(10) },	/* Hawaii-Aleutian Standard */
    { "hadt",	tDAYZONE,  HOUR(10) },	/* Hawaii-Aleutian Daylight */
    { "ces",	tDAYZONE,  -HOUR(2) },	/* Central European Summer */
    { "cest",	tDAYZONE,  -HOUR(2) },	/* Central European Summer */
    { "mez",	tZONE,     -HOUR(1) },	/* Middle European */
    { "mezt",	tDAYZONE,  -HOUR(2) },	/* Middle European Summer */
    { "cet",	tZONE,     -HOUR(1) },	/* Central European */
    { "met",	tZONE,     -HOUR(1) },	/* Middle European */
    { "eet",	tZONE,     -HOUR(2) },	/* Eastern Europe */
    { "msk",	tZONE,     -HOUR(3) },	/* Moscow Winter */
    { "msd",	tDAYZONE,  -HOUR(4) },	/* Moscow Summer */
    { "wast",	tZONE,     -HOUR(8) },	/* West Australian Standard */
    { "wadt",	tDAYZONE,  -HOUR(9) },	/* West Australian Daylight */
    { "hkt",	tZONE,     -HOUR(8) },	/* Hong Kong */
    { "cct",	tZONE,     -HOUR(8) },	/* China Coast */
    { "jst",	tZONE,     -HOUR(9) },	/* Japan Standard */
    { "kst",	tZONE,     -HOUR(9) },	/* Korean Standard */
    { "kdt",	tZONE,     -HOUR(10) },	/* Korean Daylight */
    { "cast",	tZONE,     -(HOUR(9)+30) }, /* Central Australian Standard */
    { "cadt",	tDAYZONE,  -(HOUR(9)+30) }, /* Central Australian Daylight */
    { "east",	tZONE,     -HOUR(10) },	/* Eastern Australian Standard */
    { "eadt",	tDAYZONE,  -HOUR(10) },	/* Eastern Australian Daylight */
    { "nzst",	tZONE,     -HOUR(12) },	/* New Zealand Standard */
    { "nzdt",	tDAYZONE,  -HOUR(13) },	/* New Zealand Daylight */
    { NULL,	tDAYZONE,  0 }	/* End of table */
};

int calendar_lib_tzone_find(char *s)
{
	char bf[100];
	int i;
	ncpy(bf,s,30);
	strlwr(bf);
	if (lib_strlen(bf)==1) { /* Military time zones, could have sign wrong*/
		if (*bf>='a' && *bf<='m') return -(bf[0]-'a'+1)*60;
		if (*bf>='n' && *bf<='y') return (bf[0]-'n'+1)*60;
		return 0;
	}
	for (i=0; tzone_table[i].name!=NULL; i++) {
		if (strcmp(bf,tzone_table[i].name)==0) 
			return tzone_table[i].value*60;
	}
	return 0;
}



/* fmsg */
