/*** 
* Rtdate.c - Date/time support
*
*	Copyright (C) 1992, Microsoft Corporation.  All Rights Reserved.
*	Information Contained Herein Is Proprietary and Confidential.
*
* Purpose:
*	This source contains the date/time support that are exported
*	for use in the ole dlls.
*
* Revision History:
*
*	 [00]	17-Dec-92 bradlo:	Split off from rtdate.c
*
* Implementation Notes:
*
*****************************************************************************/

#include "oledisp.h"

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

ASSERTDATA


#define HALF_SECOND  (1.0/172800.0)

// Map from month number (0-based) to day of year month starts (0-based)
// Good for non-leap years. Last entry is 365 (days in a year)
//
const int NEARDATA mpmmdd[13] = {
    0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
};

/***
*ErrPackDate - pack a UDS into a date/time serial number
*
*Purpose:
*  Generate a date/time serial number from a UDS
*
*Entry:
*  UDS *puds = pointer to UDS to pack
*  VARIANT *pvarDate = pointer to serial date to be filled
*  int fValidDate =
*    TRUE - the UDS is known to contain a valid date
*    FALSE - perform more checks and work to pack a possibly invalid date
*  dwFlags =
*    VAR_TIMEVALUEONLY - return only time value
*    VAR_DATEVALUEONLY - return only date value
*
*Exit:
*  return value = HRESULT
*
***********************************************************************/
EXTERN_C INTERNAL_(HRESULT)
ErrPackDate(UDS FAR* puds, VARIANT FAR* pvarDate, int fValidDate, unsigned long dwFlags)
{
    long lDate;
    double dblTime;
    int fLeapYear;
    short mm, dd, yy;

    /* put uds stuff in local vars
       also, make months zero based and adjust for out of range
       months (for date calculations using DateSerial) */

    yy = puds->Year;
    mm = puds->Month - 1;

    if(!fValidDate){
      if (mm < 0) {
	yy -= 1 + (-mm / 12);
	mm = 12 - (-mm % 12);
      } else {
	yy += mm / 12;
	mm = mm % 12;
      }
    }

    if(yy < 100)
      yy += 1900;

    dd = puds->DayOfMonth;

    /* Check for leap year */

    fLeapYear = ((yy & 3) == 0) && ((yy % 100) != 0 || (yy % 400) == 0);

    /* Check if it's a valid date */

    if(yy < 0 || yy > 9999 || mm < 0 || mm > 12)
      return RESULT(DISP_E_TYPEMISMATCH); /* REVIEW: correct error? */

    if(fValidDate
     && (mm > 11 || dd < 1 || dd > mpmmdd[mm + 1] - mpmmdd[mm])
     && !(mm == 1 && dd == 29 && fLeapYear))
    {
      return RESULT(DISP_E_TYPEMISMATCH); /* REVIEW: correct error? */
    }

    /* It is a valid date; make Jan 1, 1AD be 1 */

    lDate = yy * 365L + (yy / 4) - yy/100 + yy/400 + mpmmdd[mm] + dd;

    /* If we are a leap year and it's before March, subtract 1:
       we haven't leapt yet! */

    if(mm < 2 && fLeapYear)
      --lDate;

    /* Offset so that 12/30/1899 is 0 */

    lDate -= 693959L;

    if(lDate > 2958465 || lDate < -657434)
      return RESULT(DISP_E_TYPEMISMATCH); /* REVIEW: correct error? */

    if (fValidDate
     && (puds->Hour < 0 || puds->Hour > 24
      || puds->Minute < 0 || puds->Minute > 59
      || puds->Second < 0 || puds->Second > 59))
    {
      return RESULT(DISP_E_TYPEMISMATCH); /* REVIEW: correct error? */
    }

    dblTime = (((long)puds->Hour * 3600L) +	// hrs in seconds
	       ((long)puds->Minute * 60L) +	// mins in seconds
	       ((long)puds->Second)) / 86400.;

    V_VT(pvarDate) = VT_DATE;
    if (dwFlags & VAR_TIMEVALUEONLY)
      V_DATE(pvarDate) = dblTime;
    else if (dwFlags & VAR_DATEVALUEONLY)
      V_DATE(pvarDate) = (double)lDate;
    else
      V_DATE(pvarDate) = (double)lDate + ((lDate >= 0) ? dblTime : -dblTime);

    return NOERROR;
}

/***
* ErrUnpackDate - unpack a date/time serial number into a UDS
*
* Purpose:
*	Generate a UDS from a packed date/time serial number
*
* Entry:
*	UDS *puds = pointer to UDS to unpack into
*	double serialDate = serial number to unpack
*
* Exit:
*	EBERR_None = *puds is the unpacked date
*	EBERR_IllegalFuncCall = serialDate is out-of-range
*
* Exceptions:
*
***********************************************************************/
EXTERN_C INTERNAL_(HRESULT)
ErrUnpackDate(UDS FAR* puds, VARIANT FAR* pvar)
{
    int	mm;
    int	nTime;
    int fLeapOly;
    int const *	lpdd;
    double serialDate, roundDate;
    long lDate, lTime;
    int	oly, nDate, y, c4, cty;

    fLeapOly = TRUE; // assume true

    if(V_VT(pvar) != VT_DATE && V_VT(pvar) != VT_R8)
      return RESULT(E_INVALIDARG);

    roundDate = serialDate = V_DATE(pvar);

    /* Local variables:
     *
     *	lDate   day based on 0 as 1/1/0 (note different from input)
     *		so 1/1/1900 is 693961 under this system.
     *	lTime   time in seconds since midnight
     *	c4	number of 400-year blocks since 1/1/0
     *	cty	century within c4 (0-3)
     *	oly	olympiad (starting with oly 0 is years AD 0-3)
     *	nDate   day number within oly (0 is 1/1 of 1st year;
     *		1460 is 12/31 of 4th)
     *	y	year in oly (0-3)
     *	fLeapOly true if the date is in a leap oly
     */

    if((long)serialDate > 2958465L || (long)serialDate < -657435L)
      return RESULT(DISP_E_TYPEMISMATCH); // REVIEW: correct error?

    // prep for round to the sec
    roundDate += (serialDate > 0.0) ? HALF_SECOND : -HALF_SECOND;

    if((long)roundDate <= 2958465L && (long)roundDate >= -657435L)
      serialDate = roundDate;

    lDate = (long)serialDate + 693959L; // Add days from 1/1/0 to 12/30/1899

    if(serialDate < 0)
      serialDate = -serialDate;

    lTime = (long)((serialDate - disp_floor(serialDate)) * 86400.);

    if (lDate < 0)
      lDate = 0;

    /* Leap years are every 4 years except centuries that are
       not multiples of 400. */

    c4 = (int)(lDate / 146097L);

    // Now lDate is day within 400-year block
    lDate %= 146097L;

    // -1 because first century has extra day
    cty = (int)((lDate - 1) / 36524L);

    if(cty != 0){ 	// Non-leap century

      // Now lDate is day within century
      lDate = (lDate - 1) % 36524L;

      // +1 because 1st oly has 1460 days
      oly = (int) ((lDate + 1) / 1461L);

      if(oly != 0){

	nDate = (int)((lDate + 1) % 1461L);

      }else{

	fLeapOly = FALSE;
	nDate = (int)lDate;

      }

    } else {		// Leap century - not special case!

      oly = (int)(lDate / 1461L);
      nDate = (int)(lDate % 1461L);

    }

    if (fLeapOly) {

      y = (nDate - 1) / 365;	// -1 because first year has 366 days
      if(y != 0)
        nDate = (nDate - 1) % 365;

    } else {

      y = nDate / 365;
      nDate %= 365;

    }

    // nDate is now 0-based day of year. Save 1-based day of year, year number

    puds->Year = c4 * 400 + cty * 100 + oly * 4 + y;

    // Handle leap year: before, on, and after Feb. 29.

    if (y == 0 && fLeapOly)	{	// Leap Year

      if (nDate == 59) {

	/* Feb. 29 */
	puds->Month = 2;
	puds->DayOfMonth = 29;
	goto DoTime;

      }

      if (nDate >= 60)
        --nDate; // Pretend it's not a leap year for month/day comp.
    }

    // Code from here to DoTime computes month/day for everything but Feb. 29.

    ++nDate;	/* Make it 1-based rather than 0-based */

    // nDate is now 1-based day of non-leap year.

    /* Month number will always be >= n/32, so save some loop time */

    for (mm = (nDate >> 5) + 1, lpdd = &mpmmdd[mm];
										    nDate > *lpdd; mm++, lpdd++);

    puds->Month = (short)mm;
    puds->DayOfMonth = (short)(nDate - lpdd[-1]);

DoTime:;

    // We have all date info in uds.

    if(lTime == 0){ 	// avoid the divisions

      puds->Second = puds->Minute = puds->Hour = 0;

    }else{
      puds->Second = (short)(lTime % 60L);
      nTime = (int)(lTime / 60L);
      puds->Minute = (short)(nTime % 60);
      puds->Hour = (short)(nTime / 60);
    }

    return NOERROR;
}

/***
* void rtGetUdsDate - fill UDS with current date and time
*
* Purpose:
*	Fills UDS with current system date and time
*
* Entry:
*	UDS puds = ptr to UDS to be filled in
*	TZ environment variable - see _tzset() for details
*
* Exit:
*	pUDS filled with current system date and time
*
*
* Exceptions:
*
***********************************************************************/

INTERNAL_(int)
GetCurrentYear(void)
{
    time_t ltime;
    struct tm *today;

    /* REVIEW: do we really need to do all of the following if
       all we want is the current year? */

    // set time zone from TZ environment variable
    _tzset();

    // get system time
    time(&ltime);

    // convert to time structure
    today = localtime(&ltime);

    return (today == NULL) ? 1980 : today->tm_year + 1900;
}
