//******************************************************************************
//*	File		: date.c
//*	Project		: VTech	OS (Ver	2.0)
//*	Company		: VTech	Info-Tech Ltd.
//*	By			: Kam Ching
//*	Revision	: 1.0
//*	Description	: Universal calender function subroutines. This universal
//*				  calender can be configured in date.h file to calculate day of
//*				  week from either A.D. 0000 or 1600 to 3099.
//* Terminology	: year - absolute A.D. year (0 ~ 3099) or (1600 ~ 3099)
//*				  month - Jan to Dec range from 0 ~ 11
//*				  day / mday - day is assumed as day of month; 1 to 31 range from
//*				  			   1 ~ 31
//*				  wday - day of week; Sun to Sat range from 0 ~ 6
//*				  yday - day of year range from 0 ~ 365
//*				  mweek - week of month range from 1 ~ 6 (no week round off)
//*				  mrweek - week of month range from 1 ~ 6 (with week round off)
//*				  yweek - week of year range from 0 ~ 53 while 0 means last year's
//*						  last week (use week round off; i.e. first week of year
//*						  is count only when it occupies > 4 days of the week
//*						  since start of week. Ex, assume start day of week is
//*						  Sunday, New Year on Sun to Wed is count as first week
//*						  of the year; otherwise, it is week 0 of the year or
//*						  last week of the year before)
//*				  m7day - order of 7-day of month range from 1 ~ 5 (eg. 2nd Tue
//*						  of Jan)
//*				  y7day - order of 7-day of year range from 1 ~ 53 (eg. 33rd Sat
//*						  of 1999)
//*	NOTE		: to work around the round off week of week of year (ISO standard),
//*				  every return date must include year, month & mday in case week
//*				  round off.
//*	----------------------------------------------------------------------------
//*	History:
//*	Jan. 5,2000	Kam		- Initial design
//******************************************************************************

// Include files ---------------------------------------------------------------
#include "datatype.h"
#include "tmrapi.h"
#include "date.h"

// Constants defines -----------------------------------------------------------
#define	DAYS_4YEAR		(3 * 365 + 366)			// number of days in 4 years

// Global variables	------------------------------------------------------------

extern SHORT norm_mday[12];						// days in months in normal year
extern SHORT leap_mday[12];						// days in months in leap year

#if		CLDR == YR_0000	////////////////////////////////////////////////////////

#define	LY				(BYTE)0x80

#define	GET_YRNUM(a)	((a) & ~LY)				// get year number (mask out leap year bit)
#define	LP_BIT(a)		((a) & LY)				// get leap year bit (mask out year number)

// this is YrNum's X domain lookup
//	0   1   2   3   4   5   6   7   8   9				<-- 2nd digit of year
const BYTE YrMsb_YrNumX[] = {
	0,  1,  2,  3,  4,  5,  6,  0,  1,  2,		//0x	<-- first digit of year
	3,  4,  5,  6,  0,  1,  8,  9, 10,  7,		//1x
	8,  9, 10,  7,  8,  9, 10,  7,  8,  9,		//2x
   10, -1, -1, -1, -1, -1, -1, -1, -1, -1};		//3x

// this is YrNum's Y domain lookup
//	0   1   2   3   4   5   6   7   8   9				<-- last digit of year
const BYTE YrLsb_YrNumY[] = {
	0,  1,  2,  3,  4,  5,  6,  7,  8,  9,		//0x	<-- 3rd digit of year
   10, 11, 12, 13, 14, 15, 16, 17, 18, 19,		//1x
   20, 21, 22, 23, 24, 25, 26, 27, 28,  1,		//2x
	2,  3,  4,  5,  6,  7,  8,  9, 10, 11,		//3x
   12, 13, 14, 15, 16, 17, 18, 19, 20, 21,		//4x
   22, 23, 24, 25, 26, 27, 28,  1,  2,  3,		//5x
	4,  5,  6,  7,  8,  9, 10, 11, 12, 13,		//6x
   14, 15, 16, 17, 18, 19, 20, 21, 22, 23,		//7x
   24, 25, 26, 27, 28,  1,  2,  3,  4,  5,		//8x
	6,  7,  8,  9, 10, 11, 12, 13, 14, 15};		//9x

// this YrNum number is X domain lookup of MthNum; -ve means it is leap year
//	 0     1     2     3     4     5     6     7     8     9    10				<-- YrMsb_YrNumX
const BYTE YrNum[29][11] = {
	{3|LY, 2|LY, 1|LY, 0|LY, 6|LY, 5|LY, 4|LY, 6,    5|LY, 3,    1},		//0	<-- YrLsb_YrNumY
	{4,    3,    2,    1,    0,    6,    5,    0,    6,    4,    2},		//1
	{5,    4,    3,    2,    1,    0,    6,    1,    0,    5,    3},		//2
	{6,    5,    4,    3,    2,    1,    0,    2,    1,    6,    4},		//3
	{1|LY, 0|LY, 6|LY, 5|LY, 4|LY, 3|LY, 2|LY, 4|LY, 3|LY, 1|LY, 6|LY},		//4
	{2,    1,    0,    6,    5,    4,    3,    5,    4,    2,    0},		//5
	{3,    2,    1,    0,    6,    5,    4,    6,    5,    3,    1},		//6
	{4,    3,    2,    1,    0,    6,    5,    0,    6,    4,    2},		//7
	{6|LY, 5|LY, 4|LY, 3|LY, 2|LY, 1|LY, 0|LY, 2|LY, 1|LY, 6|LY, 4|LY},		//8
	{0,    6,    5,    4,    3,    2,    1,    3,    2,    0,    5},		//9
	{1,    0,    6,    5,    4,    3,    2,    4,    3,    1,    6},		//10
	{2,    1,    0,    6,    5,    4,    3,    5,    4,    2,    0},		//11
	{4|LY, 3|LY, 2|LY, 1|LY, 0|LY, 6|LY, 5|LY, 0|LY, 6|LY, 4|LY, 2|LY},		//12
	{5,    4,    3,    2,    1,    0,    6,    1,    0,    5,    3},		//13
	{6,    5,    4,    3,    2,    1,    0,    2,    1,    6,    4},		//14
	{0,    6,    5,    4,    3,    2,    1,    3,    2,    0,    5},		//15
	{2|LY, 1|LY, 0|LY, 6|LY, 5|LY, 4|LY, 3|LY, 5|LY, 4|LY, 2|LY, 0|LY},		//16
	{3,    2,    1,    0,    6,    5,    4,    6,    5,    3,    1},		//17
	{4,    3,    2,    1,    0,    6,    5,    0,    6,    4,    2},		//18
	{5,    4,    3,    2,    1,    0,    6,    1,    0,    5,    3},		//19
	{0|LY, 6|LY, 5|LY, 4|LY, 3|LY, 2|LY, 1|LY, 3|LY, 2|LY, 0|LY, 5|LY},		//20
	{1,    0,    6,    5,    4,    3,    2,    4,    3,    1,    6},		//21
	{2,    1,    0,    6,    5,    4,    3,    5,    4,    2,    0},		//22
	{3,    2,    1,    0,    6,    5,    4,    6,    6,    3,    1},		//23
	{5|LY, 4|LY, 3|LY, 2|LY, 1|LY, 0|LY, 6|LY, 1|LY, 0|LY, 5|LY, 3|LY},		//24
	{6,    5,    4,    3,    2,    1,    0,    2,    1,    6,    4},		//25
	{0,    6,    5,    4,    3,    2,    1,    3,    2,    0,    5},		//26
	{1,    0,    6,    5,    4,    3,    2,    4,    3,    1,    6},		//27
	{3|LY, 2|LY, 1|LY, 0|LY, 6|LY, 5|LY, 4|LY, 6|LY, 5|LY, 3|LY, 1|LY}		//28
};

// this Mth_MthNum00 number is Y domain lookup of MthNum
//	 1   2   3   4   5   6   7   8   9  10  11  12		<-- month
const BYTE Mth_MthNum00[] = {
	 0,  1,  1,  2,  3,  4,  2,  5,  6,  0,  1,  6};

// this MthNum number is X domain lookup of DayWeek
//	 0   1   2   3   4   5   6							<-- YrNum
const BYTE MthNum[7][7] = {
	{2,  3,  4,  5,  6,  0,  1},				//0		<-- Mth_MthNum00
	{5,  6,  0,  1,  2,  3,  4},				//1
	{1,  2,  3,  4,  5,  6,  0},				//2
	{3,  4,  5,  6,  0,  1,  2},				//3
	{6,  0,  1,  2,  3,  4,  5},				//4
	{4,  5,  6,  0,  1,  2,  3},				//5
	{0,  1,  2,  3,  4,  5,  6}					//6
};

// this Day_DayNum number is Y domain lookup of DayWeek
//	 0   1   2   3   4   5   6   7   8   9				<-- 2nd digit of day
const BYTE Day_DayNum[] = {
	 0,  1,  2,  3,  4,  5,  6,  0,  1,  2,		//0x	<-- 1st digit of day
	 3,  4,  5,  6,  0,  1,  2,  3,  4,  5,		//1x
	 6,  0,  1,  2,  3,  4,  5,  6,  0,  1,		//2x
	 2, -1, -1, -1, -1, -1, -1, -1, -1, -1};	//3x

// this DayWeek number is the day-of-week range from 0 to 6 of Sunday to Saturaday
//	 0   1   2   3   4   5   6							<-- MthNum
const BYTE DayWeek[7][7] = {
	{0,  1,  2,  3,  4,  5,  6},				//0		<-- Day_DayNum
	{1,  2,  3,  4,  5,  6,  0},				//1
	{2,  3,  4,  5,  6,  0,  1},				//2
	{3,  4,  5,  6,  0,  1,  2},				//3
	{4,  5,  6,  0,  1,  2,  3},				//4
	{5,  6,  0,  1,  2,  3,  4},				//5
	{6,  0,  1,  2,  3,  4,  5}					//6
};

// this lookup is used to minus day of week by one (i.e. sunday minus one is saturaday)
//	 0   1   2   3   4   5   6
const BYTE DecDayWeek[] = {
	 6,  0,  1,  2,  3,  4,  5};

#elif	CLDR == YR_1600 ////////////////////////////////////////////////////////

// this century number
//	16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  	<-- century number since 1900
const BYTE CentNum[] = {
	 5,  3,  1,  6,  5,  3,  1,  6,  5,  3,  1,  6,  5,  3,  1};

// this Mth_MthNum16/LpMth_MthNum is used to lookup the MthNum
//	 1   2   3   4   5   6   7   8   9  10  11  12		<-- month
const BYTE Mth_MthNum16[] = {
	 1,  4,  4,  0,  2,  5,  0,  3,  6,  1,  4,  6};
const BYTE LpMth_MthNum[] = {
	 0,  3,  4,  0,  2,  5,  0,  3,  6,  1,  4,  6};

#else	////////////////////////////////////////////////////////////////////////

#error	SYSTR22

#endif	CLDR ///////////////////////////////////////////////////////////////////

//==============================================================================
//=Prototype:	SHORT Dt_Date_WDay (SHORT Year, SHORT Month, SHORT Day);
//=Purpose:		Return the day of week by the input's date (YMD)
//=Scope:		Date library function
//=Param:		Year, Month, MDay = date in year, month & day of month (while the
//=				Year is an absolute year A.D.)
//=Return:		SHORT = >=0, day of week (0~6, sunday to saturaday)
//=						< 0, invlaid date input
//==============================================================================
SHORT Dt_Date_WDay (SHORT Year, SHORT Month, SHORT Day)
{
#if		CLDR == YR_0000 ////////////////////////////////////////////////////////

	SHORT YearNo, MonthNo;
	SHORT YearHi = Year / 100;					// get upper 2 digits of year
	SHORT YearLo = Year % 100;					// get lower 2 digits of year

	if (! Dt_IsValidDate (Year, Month, Day))
		return -1;
	YearNo = YrNum[YrLsb_YrNumY[YearLo]][YrMsb_YrNumX[YearHi]];
	MonthNo = MthNum[Mth_MthNum00[Month]][GET_YRNUM(YearNo)];
	if (LP_BIT(YearNo) && (Month <= 1))			// Q: Jan & Feb in leap year ?
		MonthNo = DecDayWeek[MonthNo];			// Y: decreace MonthNo by 1
	return DayWeek[Day_DayNum[Day-1]][MonthNo];

#elif	CLDR == YR_1600 ////////////////////////////////////////////////////////

	SHORT YearNo, MonthNo;
	SHORT YearHi = Year / 100 - (SHORT)(DT_YEAR_MIN / 100);// get upper 2 digits of year (offset 16)
	SHORT YearLo = Year % 100;					// get lower 2 digits of year

	if (! Dt_IsValidDate (Year, Month, Day))
		return -1;
	YearNo = YearLo + (YearLo / 4) + CentNum[YearHi];
	MonthNo = Dt_IsLeapYear(Year) ? LpMth_MthNum[Month] : Mth_MthNum16[Month];
	return (YearNo + MonthNo + (Day)) % 7;		// +1 is used to inc all Day from 0~30 to 1~31

#endif	CLDR ///////////////////////////////////////////////////////////////////
}

//==============================================================================
//=Prototype:	SHORT Dt_Date_YDay (SHORT Year, SHORT Month, SHORT Day)
//=Purpose:		Convert date to day of year
//=Scope:		Date library function
//=Param:		Year, Month, Day = date in year(A.D.), month & day of month
//=Return:		SHORT = >=0, day of year (0~365)
//=						< 0, invlaid date input
//==============================================================================
SHORT Dt_Date_YDay (SHORT Year, SHORT Month, SHORT Day)
{
	SHORT yday = 0;

	if (! Dt_IsValidDate (Year, Month, Day))
		return -1;
	if (Dt_IsLeapYear (Year))					// get day of year (offset 0)
		while (Month != 0)						//   |
			yday += leap_mday[--Month];			//   |
	else										//   |
		while (Month != 0)						//   |
			yday += norm_mday[--Month];			//   |
	return (yday + Day - 1);					//   |
}

//==============================================================================
//=Prototype:	SHORT Dt_YDay_Date (SHORT Year, SHORT YDay, RTM *pDate)
//=Purpose:		Convert YDay of a Year to date (RTM)
//=Scope:		Date library function
//=Param:		Year, YDay = year & day of year (-31 ~ 365+31)
//=Return:		SHORT = > 0, success, output valid
//=						==0, false, invalid input, invalid output
//=						< 0, success but output out of year bound
//==============================================================================
SHORT Dt_YDay_Date (SHORT Year, SHORT YDay, RTM *pDate)
{
	SHORT mon = 0;
	SHORT rtn = TRUE;

	if ((YDay < -31) && (YDay > (365+31)))		// Q: within +/- one month of this year ?
		return 0;								// N: return FALSE
	if (YDay < 0) {											// Q: YDay -ve ?
		Year--;												// Y: goto last year
		YDay = (Dt_IsLeapYear (Year) ? 366 : 365) + YDay;	// minus the YDay
		rtn = -1;											// return -ve, out of year bound
	}
	if (Dt_IsLeapYear (Year)) {					// get the month & mday (offset 0)
		while(YDay >= leap_mday[mon])			//   |
			YDay -= leap_mday[mon++];			//   |
	} else {									//   |
		while(YDay >= norm_mday[mon])			//   |
			YDay -= norm_mday[mon++];			//   |
	}
	if (mon / 12) {								// Q: more than 1 year
		Year++;									// Year++
		rtn = -1;								// return -ve, out of year bound
	}
	pDate->year = Year;							// return year offset 0
	pDate->mon = mon % 12;						// return month offset 0
	pDate->mday = YDay + 1;						// return mday offset 1
	return rtn;
}

//==============================================================================
//=Prototype:	SHORT Dt_Date_MWeek (SHORT Year, SHORT Month, SHORT Day)
//=Purpose:		Convert date to week of month (no week round off)
//=Scope:		Date library function
//=Param:		Year, Month, Day = date in year(A.D.), month & day of month
//=Return:		SHORT = > 0, week of month, 1 ~ 6 (no week round off)
//=						< 0, invlaid date input
//==============================================================================
SHORT Dt_Date_MWeek (SHORT Year, SHORT Month, SHORT Day)
{
	SHORT wday;
	SHORT day_month = (Day-1)/7+1;

	if ((wday = Dt_Date_WDay (Year, Month, Day)) < 0)	// get day of week on the input date
		return wday;									// invalid date input, return false
	return (wday < (Day-1)%7) ? ++day_month : day_month;
}

//==============================================================================
//=Prototype:	SHORT Dt_MWeek_Date (SHORT Year, SHORT Month, SHORT MWeek, SHORT WDay, RTM *pDate)
//=Purpose:		Convert mweek & wday to date
//=Scope:		Date library function
//=Param:		Year, Month & WDay = inputs
//=				MWeek = >0, 1~6, week of month; <=0, last MWeek's WDay of the month
//=				*pDate = return pointer of the RTM structure
//=Return:		SHORT = > 0, success, output valid
//=						==0, false, invalid input, invalid output
//=						< 0, success but output out of year bound
//==============================================================================
SHORT Dt_MWeek_Date (SHORT Year, SHORT Month, SHORT MWeek, SHORT WDay, RTM *pDate)
{
	SHORT yday, mday, m_days, mon1st_wday;

	if ((mon1st_wday = Dt_Date_WDay (Year, Month, 1)) < 0)	// get wday of 1st day of the month
		return FALSE;
	if (MWeek > 6)
		return FALSE;
	if (MWeek > 0) {							// Q: MWeek specified num of mweek ?
		mday = (MWeek - 1) * 7 + WDay - mon1st_wday;// get mday (offset 0) of the MWeek's WDay
	} else {
		m_days = Dt_DaysOfMon (Year, Month);	// get mday (offset 0) of the
		mday = MOD_WK(WDay, mon1st_wday) + 1;	//   last MWeek's WDay
		while (mday + 7 <= m_days)				//   |
			mday += 7;							//   |
		mday--;									//   |
	}
	yday = Dt_Date_YDay (Year, Month, 1) + mday;// then add on the days of all the month before

	return Dt_YDay_Date (Year, yday, pDate);	// convert yday to date
}

//==============================================================================
//=Prototype:	SHORT Dt_Date_MRWeek (SHORT Year, SHORT Month, SHORT Day, SHORT StartWDay, RTM *pDate)
//=Purpose:		Convert date to week of month (mrweek, with week round off)
//=Scope:		Date library function
//=Param:		Year, Month, Day = date in year(A.D.), month & day of month
//=Return:		SHORT = > 0, week of month, 1 ~ 6 (no week round off)
//=						< 0, invlaid date input
//==============================================================================
SHORT Dt_Date_MRWeek (SHORT Year, SHORT Month, SHORT Day, SHORT StartWDay, RTM *pDate)
{
	SHORT mon1st_wday;							// day of week on the new year
	SHORT mweek;								// mod days (first week's remain days to start day of week)
	SHORT mday, mrweek, last_mrweek, hwday;

	if ((mon1st_wday = Dt_Date_WDay (Year, Month, 1)) < 0)	// find day of week on 1st day of the month
		return mon1st_wday;
	mweek = MOD_WK(StartWDay, mon1st_wday);
	hwday = (mweek >= 4) ? (7 - mweek) : -mweek;
	mrweek = (SHORT)((Day + hwday + 6)/7);			// +6 round up one week constantly
	last_mrweek = Dt_DaysOfMon (Year, Month) - 4;			// get last yday of last mrweek	(offset 0)
	last_mrweek = (SHORT)((last_mrweek + hwday + 7)/7);
	if (mrweek == 0) {
		if ((--Month) < 0) {
			Year--;
			Month = 11;
		}
		mday = Dt_DaysOfMon (Year, Month);
		mrweek = Dt_Date_MRWeek (Year, Month, mday, StartWDay, NULL);	// last mrweek of previous month
	} else if (mrweek > last_mrweek) {
		mrweek = 1;										// first mrweek of next year
		if (++Month > 11) {
			Year++;
			Month = 0;
		}
	}
	if (pDate) {
		pDate->year = Year;
		pDate->mon = Month;
	}
	return mrweek;
}

//==============================================================================
//=Prototype:	SHORT Dt_MRWeek_Date (SHORT Year, SHORT Month, SHORT MWeek, SHORT WDay, SHORT StartWDay, RTM *pDate)
//=Purpose:		Convert mrweek & wday to date
//=Scope:		Date library function
//=Param:		Year, Month & WDay = inputs
//=				MWeek = >0, 1~6, week of month; <=0, last MWeek's WDay of the month
//=				*pDate = return pointer of the RTM structure
//=Return:		SHORT = > 0, success, output valid
//=						==0, false, invalid input, invalid output
//=						< 0, success but output out of year bound
//==============================================================================
SHORT Dt_MRWeek_Date (SHORT Year, SHORT Month, SHORT MWeek, SHORT WDay, SHORT StartWDay, RTM *pDate)
{
	SHORT mon1st_wday, mweek, hwday;
	SHORT yday = 0;
	SHORT month = Month;

	if (Dt_IsLeapYear (Year))					// get day of year (offset 0)
		while (month != 0)						//   |
			yday += leap_mday[--month];			//   |
	else										//   |
		while (month != 0)						//   |
			yday += norm_mday[--month];			//   |
	if (MWeek <= 0) {							// Q: MWeek < 0 ? (last MWeek in last/this month?)
		if (MWeek == 0)							// Q: MWeek == 0 ? (last MWeek in last month?)
			if ((--Month) < 0) {
				Year--;
				Month = 11;
			}
		MWeek = Dt_Date_MRWeek (Year, Month, 31, StartWDay, NULL);
	}
	mon1st_wday = Dt_Date_WDay (Year, Month, 1);	// find day of week on 1st day of this month
	mweek = MOD_WK(StartWDay, mon1st_wday);			// get number of day(s) remaining
	hwday = (mweek >= 4) ? (mweek - 7) : mweek;		// get head wday
	WDay = (WDay < StartWDay) ? (7 + WDay) : WDay;
	yday += (MWeek - 1) * 7 + hwday - StartWDay  + WDay;	// offset 0

	return Dt_YDay_Date (Year, yday, pDate);		// convert yday to date
}

//==============================================================================
//=Prototype:	SHORT Dt_Date_YWeek (SHORT Year, SHORT Month, SHORT Day, SHORT StartWDay, SHORT *pYear)
//=Purpose:		Convert date to week of year (week round off)
//=Scope:		Date library function
//=Param:		Year, Month, Day = date in year(A.D.), month & day of month
//=				pYear = return pointer of input year (in case of round up/down of year)
//=						NULL, no modification of the year
//=Return:		SHORT = > 0, week of year, 1 ~ 53(week round off)
//=						< 0, invlaid date input
//=				*pYear = the year will only be modified if there is year change
//==============================================================================
SHORT Dt_Date_YWeek (SHORT Year, SHORT Month, SHORT Day, SHORT StartWDay, SHORT *pYear)
{
	SHORT newyr_wday;							// day of week on the new year
	SHORT yday, last_yday;						// day of year of the input's date
	SHORT mweek;								// mod days (first week's remain days to start day of week)
	SHORT yweek, last_yweek, hwday;

	newyr_wday = Dt_Date_WDay (Year, 0, 1);		// find day of week on the New Year
	if ((yday = Dt_Date_YDay (Year, Month, Day)) < 0)	// get day of year
		return yday;									// invalid date input, return false
	mweek = MOD_WK(StartWDay, newyr_wday);
	hwday = (mweek >= 4) ? (7 - mweek) : -mweek;
//	yweek = (SHORT)(((yday + 1) + hwday + 6)/7);// +1 makes yday offset 1, +6 round up one week constantly
	yweek = (SHORT)((yday + hwday + 7)/7);
	last_yday = (Dt_IsLeapYear (Year) ? 366 : 365) - 4;	// get last yday of last yweek (offset 0)
	last_yweek = (SHORT)((last_yday + hwday + 7)/7);
	if (yweek == 0)
		yweek = Dt_Date_YWeek (--Year, 11, 31, StartWDay, NULL);	// last yweek of previous year
	else if (yweek > last_yweek) {
		yweek = 1;										// first yweek of next year
		Year++;
	}
	if (pYear)
		*pYear = Year;
	return yweek;
}

//==============================================================================
//=Prototype:	SHORT Dt_YWeek_Date (SHORT Year, SHORT YWeek, SHORT WDay, SHORT StartWDay, RTM *pDate)
//=Purpose:		Convert week of year (week round off) with start wday to date
//=Scope:		Date library function
//=Param:		Year, WDay & StartWDay = inputs
//=				YWeek = >0, 1~53 week of year
//=					   ==0, last YWeek's YDay of last year
//=					    <0, last YWeek's YDay of this year
//=				*pDate = return pointer of the RTM structure
//=Return:		SHORT = > 0, success, output valid
//=						==0, false, invalid input, invalid output
//=						< 0, success but output out of year bound
//==============================================================================
SHORT Dt_YWeek_Date (SHORT Year, SHORT YWeek, SHORT WDay, SHORT StartWDay, RTM *pDate)
{
	SHORT newyr_wday, yday, mweek, hwday;

	if (YWeek <= 0) {							// Q: YWeek < 0 ? (last YWeek in last/this year ?)
		if (YWeek == 0)							// Q: YWeek == 0 ? (last YWeek in last year ?)
			Year--;								// set to last year
		YWeek = Dt_Date_YWeek (Year, 11, 31, StartWDay, NULL);// get New Year Eve's week of year
	}
	newyr_wday = Dt_Date_WDay (Year, 0, 1);		// find day of week on the New Year
	mweek = MOD_WK(StartWDay, newyr_wday);		// get number of day(s) remaining
	hwday = (mweek >= 4) ? (mweek - 7) : mweek;	// get head wday
	WDay = (WDay < StartWDay) ? (7 + WDay) : WDay;
	yday = (YWeek - 1) * 7 + hwday - StartWDay + WDay;

	return Dt_YDay_Date (Year, yday, pDate);	// convert yday to date
}

//==============================================================================
//=Prototype:	SHORT Dt_Date_M7day (SHORT Year, SHORT Month, SHORT Day)
//=Purpose:		Convert date to order of 7-day of month
//=Scope:		Date library function
//=Param:		Year, Month, Day = date in year(A.D.), month & day of month
//=Return:		SHORT = > 0, order of 7-day of the month 1 ~ 5
//=						< 0, invlaid date input
//==============================================================================
SHORT Dt_Date_M7day (SHORT Year, SHORT Month, SHORT Day)
{
	if (! Dt_IsValidDate (Year, Month, Day))
		return -1;
	return (SHORT)((Day - 1) / 7 + 1);
}

//==============================================================================
//=Prototype:	SHORT Dt_M7day_Date (SHORT Year, SHORT Month, SHORT M7day, SHORT WDay, RTM *pDate)
//=Purpose:		Convert order of 7-day & wday to date. Use this SR to find the
//=				n-th occurance wday of the month (e.g. first Sunday of May, 1999)
//=Scope:		Date library function
//=Param:		Year, Month, M7day & WDay = inputs
//=				M7day = >0, 1~5, order of 7-day of the month; <=0, last WDay of the month
//=				*pDate = return pointer of the RTM structure
//=Return:		SHORT = > 0, success, output valid
//=						==0, false, invalid input, invalid output
//=						< 0, success but output out of year bound
//==============================================================================
SHORT Dt_M7day_Date (SHORT Year, SHORT Month, SHORT M7day, SHORT WDay, RTM *pDate)
{
	SHORT yday, mday, m_days, mon1st_wday;

	if ((mon1st_wday = Dt_Date_WDay (Year, Month, 1)) < 0)	// get wday of 1st day of the month
		return FALSE;
	mday = MOD_WK(WDay, mon1st_wday);			// first mday on WDay
	if (M7day > 6)
		return FALSE;
	if (M7day > 0)
		mday += (M7day - 1) * 7;				// get mday (offset 0) of the WDay
	else {
		m_days = Dt_DaysOfMon (Year, Month);	// get mday (offset 0) of the
		mday++;									//   last WDay
		while (mday + 7 <= m_days)				//   |
			mday += 7;							//   |
		mday--;									//   |
	}
	yday = Dt_Date_YDay (Year, Month, 1) + mday;// then add on the days of all the month before

	return Dt_YDay_Date (Year, yday, pDate);	// convert yday to date
}

//==============================================================================
//=Prototype:	SHORT Dt_Date_Y7day (SHORT Year, SHORT Month, SHORT Day)
//=Purpose:		Convert date to order of 7-day of year
//=Scope:		Date library function
//=Param:		Year, Month, Day = date in year(A.D.), month & day of month
//=Return:		SHORT = > 0, order of 7-day of the year 1 ~ 53
//=						< 0, invlaid date input
//==============================================================================
SHORT Dt_Date_Y7day (SHORT Year, SHORT Month, SHORT Day)
{
	if (! Dt_IsValidDate (Year, Month, Day))
		return -1;
	return (SHORT)(Dt_Date_YDay (Year, Month, Day) / 7 + 1);
}

//==============================================================================
//=Prototype:	SHORT Dt_Y7day_Date (SHORT Year, SHORT Y7day, SHORT WDay, RTM *pDate)
//=Purpose:		Convert order of 7-day to date. Use this SR to find the n-th
//=				occurance wday of the year (e.g. 2nd Wed of 1999)
//=Scope:		Date library function
//=Param:		Year, Y7day & WDay = inputs
//=				M7day = >0, 1~53, order of 7-day of the year; <=0, last WDay of the year
//=				*pDate = return pointer of the RTM structure
//=Return:		SHORT = > 0, success, output valid
//=						==0, false, invalid input, invalid output
//=						< 0, success but output out of year bound
//==============================================================================
SHORT Dt_Y7day_Date (SHORT Year, SHORT Y7day, SHORT WDay, RTM *pDate)
{
	SHORT yday, mday, newyr_wday, endyr_wday;

	if (Y7day > 0) {
		newyr_wday = Dt_Date_WDay (Year, 0, 1);	// get day of week of the New Year
		mday = MOD_WK(WDay, newyr_wday);		// first mday on WDay
		yday = (Y7day - 1) * 7 + mday;			// get yday of the WDay
	} else {
		endyr_wday = Dt_Date_WDay (Year, 11, 31);// get day of week of the New Year Eve
		mday = MOD_WK(endyr_wday, WDay) + 1;	// days from WDay to New Year Eve
		yday = (Dt_IsLeapYear (Year) ? 366 : 365) - mday;
	}

	return Dt_YDay_Date (Year, yday, pDate);	// convert yday to date
}

//==============================================================================
//=Prototype:	SHORT Dt_CmpDate (RTM *pDateS, RTM *pDateT);
//=Purpose:		Compare source to target date (only YMD will be compared)
//=Scope:		Date library function
//=Param:		pDateS, pDateT = source & target date (YMD only) input pointers
//=Return:		SHORT = > 0, pDateS > pDateT
//=						==0, pDateS == pDateT
//=						< 0, pDateS < pDateT
//==============================================================================
SHORT Dt_CmpDate (RTM *pDateS, RTM *pDateT)
{
	SHORT diff;

	if ((diff = pDateS->year - pDateT->year) != 0)
		return diff;
	if ((diff = pDateS->mon - pDateT->mon) != 0)
		return diff;
	return (pDateS->mday - pDateT->mday);
}

//==============================================================================
//=Prototype:	SHORT Dt_DiffDays (RTM *pDateS, RTM *pDateT);
//=Purpose:		Minus source date to target date (only YMD will be minused)
//=Scope:		Date library function
//=Param:		pDateS, pDateT = source & target date (YMD only) input pointers
//=Return:		SHORT = different days range from -32767 ~ 32767
//=						> 0, pDateS > pDateT
//=						==0, pDateS == pDateT
//=						< 0, pDateS < pDateT
//=NOTE:		the bigger the different days, the longer it will take to
//=				calculate in the while loop
//==============================================================================
SHORT Dt_DiffDays (RTM *pDateS, RTM *pDateT)
{
	SHORT dyears, ddays;						// different years & days
	SHORT syday, tyday;							// source yday & target yday
	SHORT year;									// current year

	if ((dyears = pDateS->year - pDateT->year) > 0) {
		year = pDateT->year;
		for (ddays = 0; dyears != 0; dyears--)
			ddays += (Dt_IsLeapYear (year++) ? 366 : 365);
	} else {
		year = pDateS->year;
		for (ddays = 0; dyears != 0; dyears++)
			ddays -= (Dt_IsLeapYear (year++) ? 366 : 365);
	}
	syday = Dt_Date_YDay (pDateS->year, pDateS->mon, pDateS->mday);
	tyday = Dt_Date_YDay (pDateT->year, pDateT->mon, pDateT->mday);

	return (ddays + syday - tyday);
}

//==============================================================================
//=Prototype:   void Dt_AddDays (RTM *pDateS, SHORT Days, RTM *pDateD)
//=Purpose:		Add source date & days to destination date
//=Scope:		Date library function
//=Param:		pDateS = source date pointer
//=				Days = number of add days (may be-ve, -32767 ~ 32767)
//=				pDateD = destination date that store the added date (this pDateD
//=						 may be the same as pDateS)
//=Return:		none
//==============================================================================
void Dt_AddDays (RTM *pDateS, SHORT Days, RTM *pDateD)
{
	SHORT ydays;				// total days of the year
	WORD adays;					// days to add (use WORD in case overflow after addition)
	SHORT mon = 0;
	SHORT year = pDateS->year;

	adays = Dt_Date_YDay (pDateS->year, pDateS->mon, pDateS->mday) + Days;

	// make adays less that 365/366 and inc/dec the year
	if (adays > 0) {
		while (adays >= (ydays = (Dt_IsLeapYear (year) ? 366 : 365))) {
			adays -= ydays;
			year++;
		}
	} else {
		while (adays < 0)
			adays += Dt_IsLeapYear (--year) ? 366 : 365;
	}

	if (Dt_IsLeapYear (year)) {					// get the month & mday (offset 0)
		while(adays >= leap_mday[mon])			//   |
			adays -= leap_mday[mon++];			//   |
	} else {									//   |
		while(adays >= norm_mday[mon])			//   |
			adays -= norm_mday[mon++];			//   |
	}
	pDateD->year = year;						// return Year++
	pDateD->mon = mon;							// return month offset 0
	pDateD->mday = adays + 1;					// return mday offset 1
}

//==============================================================================
//=Prototype:   void Dt_AddDate (RTM *pDateS, RTM *pDateT, RTM *pDateD)
//=Purpose:		Add source & target date to destination date
//=Scope:		Date library function
//=Param:		pDateS = source date input pointer
//=				pDateT = the add date pointer (all YMD must offset 0 include mday)
//=				pDateD = destination date that store the different date (this
//=						 pDateD may be the same as pDateS)
//=Return:		none
//==============================================================================
void Dt_AddDate (RTM *pDateS, RTM *pDateT, RTM *pDateD)
{
	SHORT ryear, rmonth, rday, nyear;

	ryear = pDateS->year + pDateT->year;
	rday = min(pDateS->mday, Dt_DaysOfMon (ryear, pDateS->mon));

	rmonth = pDateS->mon + pDateT->mon;
	if ((nyear = rmonth / 12)) {
		ryear += nyear;
		rmonth %= 12;
		if (rmonth < 0) {
			rmonth += 12;
			ryear--;
		}
	}
	rday = min(pDateS->mday, Dt_DaysOfMon (ryear, rmonth));

	rday += (pDateT->mday) - 1;					// make it offset 0
	rday += Dt_Date_YDay (ryear, rmonth, 1);
	pDateD->year = ryear;
	pDateD->mon = 0;
	pDateD->mday = 1;
	Dt_AddDays (pDateD, rday, pDateD);
}

//==============================================================================
//=Prototype:	BOOLEAN Dt_IsLeapYear (SHORT Year);
//=Purpose:		Get if the specified year is leep year or not (by lookup table)
//=Scope:		Date library function
//=Param:		Year = the year will be checked if leap year (in absolute year)
//=Return:		BOOLEAN = TRUE, leap year; FALSE, non-leap year
//==============================================================================
BOOLEAN Dt_IsLeapYear (SHORT Year)
{
#if		CLDR == YR_0000 ////////////////////////////////////////////////////////

	return LP_BIT(YrNum[YrLsb_YrNumY[(Year%100)]][YrMsb_YrNumX[(Year/100)]]);

#elif	CLDR == YR_1600 ////////////////////////////////////////////////////////

	return (Year % 100) ? !(Year % 4) : !(Year % 400);

#endif	CLDR ///////////////////////////////////////////////////////////////////
}

//==============================================================================
//=Prototype:	BOOLEAN Dt_IsValidDate (SHORT Year, SHORT Month, SHORT Day);
//=Purpose:		Check if the date is valid
//=Scope:		Date library function
//=Param:		Year, Month, Day = date will be checked if valid
//=Return:		BOOLEAN = TRUE, valid date; FALSE, invalid date
//==============================================================================
BOOLEAN Dt_IsValidDate (SHORT Year, SHORT Month, SHORT Day)
{
	if ((Year < DT_YEAR_MIN) || (Year > DT_YEAR_MAX))
		return FALSE;
	if ((Month < 0) || (Month > 11))
		return FALSE;
	return ((Day >= 1) && (Day <= Dt_DaysOfMon (Year, Month)));
}

//==============================================================================
//=Prototype:	SHORT Dt_DaysOfMon (SHORT Year, SHORT Month);
//=Purpose:		Get total days of the month
//=Scope:		Date library function
//=Param:		Year, Month = the month of the year's total days will be returned
//=Return:		SHORT = total days of the month
//==============================================================================
SHORT Dt_DaysOfMon (SHORT Year, SHORT Month)
{
	return (Dt_IsLeapYear (Year) ? leap_mday[Month] : norm_mday[Month]);
}


