#include "StdAfx.h"
#include <iomanip>



#include "SysTime.h"

using namespace std;

const char* SysTime::s_months[] = { "January", "February", "March", "April", 
                                  "May", "June", "July", "August", 
                                  "September", "October", "November", "December" };
const int SysTime::s_mdays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };



SysTime::SysTime() 
  // : m_nanoseconds(0)
{
	Update();
}

inline void SysTime::Update(void)
{
	SYSTEMTIME SystemTime;
	GetLocalTime( &SystemTime );

 	Set( (UINT8)SystemTime.wHour, (UINT8)SystemTime.wMinute, (UINT8)SystemTime.wSecond, (UINT16)SystemTime.wMilliseconds, (UINT16)0 );
	Set( (UINT16)SystemTime.wYear, (UINT8)SystemTime.wMonth, (UINT8)SystemTime.wDay );
}


/************************************************************************************
METHOD      :  IsValid
DESCRIPTION :  Returns true if the date and time fields are valid.
ARGUMENTS   :  none
RETURNS     :  bool
*************************************************************************************/
bool SysTime::IsValid(void)
{
   if (m_tm.tm_year > 3000)   // Y3K bug
      return false;
   if (m_tm.tm_mon < 0 || m_tm.tm_mon > 11)
      return false;
   if (m_tm.tm_mday < 1 || m_tm.tm_mday > 31)
      return false;
   if (m_tm.tm_yday < 1 || m_tm.tm_yday > 366)
      return false;
   if (m_tm.tm_hour < 0 || m_tm.tm_hour > 23)
      return false;
   if (m_tm.tm_min < 0 || m_tm.tm_min > 59)
      return false;
   if (m_tm.tm_sec < 0 || m_tm.tm_sec > 59)
      return false;
   if (m_tm.m_nanoseconds < 0 || m_tm.m_nanoseconds >= 1e+9)
      return false;

   return true;
}


/************************************************************************************
METHOD      :  Set
DESCRIPTION :  Sets the time from day or time of day parts.
ARGUMENTS   :  UINT16 year                   -- year to set
               UINT8 month                   -- month to set
               UINT8 day                     -- day to set

               or

               UINT8 hour                    -- hour to set
               UINT8 minute                  -- minute to set
               UINY8 second                  -- second to set
               UINT16 millisecond            -- millisecond to set
               UINT16 microsecond            -- microsecond to set
RETURNS     :  none
*************************************************************************************/
inline void SysTime::Set(UINT16 year, UINT8 month, UINT8 day)
{
   m_tm.tm_year = year - 1900;
   m_tm.tm_mon = month - 1;
   m_tm.tm_mday = day;
   ConvertMDayToYDay();
}
inline void SysTime::Set(UINT8 hour, UINT8 minute, UINT8 second, 
                UINT16 millisecond, UINT16 microsecond)
{
   m_tm.tm_hour = hour;
   m_tm.tm_min = minute;
   m_tm.tm_sec = second;
   m_tm.m_nanoseconds = millisecond * 1000000 + microsecond * 1000;
}



/************************************************************************************
METHOD      :  ConvertYDayToMDay and ConvertMDayToYDay
DESCRIPTION :  Converts tm_yday to tm_mon and tm_mday and reverse.
ARGUMENTS   :  none
RETURNS     :  none
*************************************************************************************/
void SysTime::ConvertYDayToMDay(void)
{
   int yday = m_tm.tm_yday;                     // tm_yday is 1-365

   for (m_tm.tm_mon = 0; m_tm.tm_mon < 12; ++m_tm.tm_mon)
   {
      int mday = s_mdays[m_tm.tm_mon];
      if (m_tm.tm_mon == 1 && IsLeapYear())
         ++mday;                                // 29
      if (yday <= mday)
         break;
      yday -= mday;
   }
   m_tm.tm_mday = yday;                         // tm_mday is 1-31
}

void SysTime::ConvertMDayToYDay(void)
{
   m_tm.tm_yday = m_tm.tm_mday;
   
   for (int mon = 0; mon < m_tm.tm_mon; ++mon)
   {
      int mday = s_mdays[mon];
      if (mon == 1 && IsLeapYear())
         ++mday;                                // 29
      m_tm.tm_yday += mday;
   }
}


/************************************************************************************
METHOD      :  IsLeapYear
DESCRIPTION :  Based on standard formula, determines if given year is a leap year
ARGUMENTS   :  none
RETURNS     :  true if a leap year, false otherwise
*************************************************************************************/
bool SysTime::IsLeapYear(void)
{
   UINT16 year = Year();                        // Real year (e.g. 2006)

   return year % 4 == 0 &&                      // Julian
         (year % 100 != 0 || year % 400 == 0);  // Gregorian
}


/************************************************************************************
METHOD      :  CalendarTime
DESCRIPTION :  NOTE:  there's not enough precision in a double to hold time to the nanosecond.
ARGUMENTS   :  none
RETURNS     :  Returns calendar time in a double:  DayOfYear.Seconds/possible seconds
*************************************************************************************/
double SysTime::CalendarTime(void)                // Good until Y2.1K bug
{
   // Time
   double td = SecondsToday() + double(Nanosecond32()) * 1e-9;
   td /= (60 * 60 * 24);

   // Date
   int year = m_tm.tm_year < 1000 ? m_tm.tm_year : m_tm.tm_year - 1900;
   td += year*365 + year/4 + 1;                 // Julian only
   ConvertMDayToYDay();
   td += m_tm.tm_yday;

   return td;
}


/************************************************************************************
METHOD      :  SecondsToday
DESCRIPTION :  Set the time (just hours, minutes, and seconds) to s seconds.  Mod s
               by 86400 (24 * 60 * 60).
ARGUMENTS   :  UINT32 s                      -- seconds of day to set the object to
RETURNS     :  none
*************************************************************************************/
void SysTime::SecondsToday(UINT32 s)
{
   m_tm.tm_hour = s / 3600 % 24;                // in case s >= 86400
   m_tm.tm_min = s / 60 % 60;
   m_tm.tm_sec = s % 60;
}


/************************************************************************************
METHOD      :  operator-
DESCRIPTION :  Subtract time from this object
ARGUMENTS   :  const SysTime& rvalue          -- time to subtract, must be less (earlier) than this object.
RETURNS     :  TTime - difference
NOTE        :  The time difference has a maximum of one day.
*************************************************************************************/
SysTime SysTime::operator-(const SysTime& rvalue) const
{
   SysTime difference = *this;                    // Copy
   difference -= rvalue;                        // operator -=

   return difference;
}


/************************************************************************************
METHOD      :  operator-=
DESCRIPTION :  Subtract time from this object
ARGUMENTS   :  const SysTime& rvalue          -- time to subtract, must be less (earlier) than this object.
RETURNS     :  TTime - difference
*************************************************************************************/
SysTime& SysTime::operator-=(const SysTime& rvalue)
{
   int seconds = (int)SecondsToday() - (int)rvalue.SecondsToday();
   if (m_tm.m_nanoseconds < rvalue.m_tm.m_nanoseconds)
   {
      seconds--;                                // borrow
      m_tm.m_nanoseconds += (UINT32) 1e9;
   }
   m_tm.m_nanoseconds -= rvalue.m_tm.m_nanoseconds;

   int yday = (int)m_tm.tm_yday - (int)rvalue.m_tm.tm_yday + 1;
   if (seconds < 0)
   {
      yday--;                                   // borrow
      seconds += 86400;
   }
   SecondsToday(seconds);

   int year = (int)Year() - (int)rvalue.Year();
   if (yday <= 0)                               // yday is 1-365
   {
      year--;                                   // borrow
      yday += IsLeapYear() ? 366 : 365;
   }
   m_tm.tm_yday = yday;
   ConvertYDayToMDay();
   m_tm.tm_year = year;                         // tm_year is 0 for 1900

   return *this;
}


/************************************************************************************
METHOD      :  operator+
DESCRIPTION :  Add time from this object
ARGUMENTS   :  const SysTime& rvalue          -- time to add
RETURNS     :  TTime - sum
*************************************************************************************/
SysTime SysTime::operator+(const SysTime& rvalue) const
{
   SysTime sum = *this;                           // Copy
   sum += rvalue;                               // operator +=

   return sum;
}


/************************************************************************************
METHOD      :  operator+=
DESCRIPTION :  Add time from this object
ARGUMENTS   :  const SysTime& rvalue          -- time to add
RETURNS     :  TTime - Sum
*************************************************************************************/
SysTime& SysTime::operator+=(const SysTime& rvalue)
{
   m_tm.m_nanoseconds += rvalue.m_tm.m_nanoseconds;
   if (m_tm.m_nanoseconds >= 1e9)
   {
      m_tm.tm_sec++;                            // carry
      m_tm.m_nanoseconds -= (UINT32) 1e9;
   }

   UINT32 seconds = SecondsToday() + rvalue.SecondsToday();
   if (seconds >= 86400)
   {
      m_tm.tm_yday++;                           // carry
      seconds -= 86400;
   }
   SecondsToday(seconds);

   m_tm.tm_yday += rvalue.m_tm.tm_yday - 1;
   int days_this_year = IsLeapYear() ? 366 : 365;
   if (m_tm.tm_yday > days_this_year)           // yday is 1-365
   {
      m_tm.tm_year++;                           // carry
      m_tm.tm_yday -= days_this_year;
   }
   ConvertYDayToMDay();

   return *this;
}


/************************************************************************************
METHOD      :  operator <, <= and >
DESCRIPTION :  Compare time with this object
ARGUMENTS   :  const SysTime& rvalue          -- time to compare
RETURNS     :  bool
*************************************************************************************/
bool SysTime::operator<(const SysTime& rvalue)
{
   if (m_tm.tm_year != rvalue.m_tm.tm_year)
      return (m_tm.tm_year < rvalue.m_tm.tm_year);

   if (m_tm.tm_yday != rvalue.m_tm.tm_yday)
      return (m_tm.tm_yday < rvalue.m_tm.tm_yday);

   if (SecondsToday() != rvalue.SecondsToday())
      return (SecondsToday() < rvalue.SecondsToday());

   return m_tm.m_nanoseconds < rvalue.m_tm.m_nanoseconds;
}

bool SysTime::operator>(const SysTime& rvalue)
{
   if (m_tm.tm_year != rvalue.m_tm.tm_year)
      return (m_tm.tm_year > rvalue.m_tm.tm_year);

   if (m_tm.tm_yday != rvalue.m_tm.tm_yday)
      return (m_tm.tm_yday > rvalue.m_tm.tm_yday);

   if (SecondsToday() != rvalue.SecondsToday())
      return (SecondsToday() > rvalue.SecondsToday());

   return m_tm.m_nanoseconds > rvalue.m_tm.m_nanoseconds;
}

bool SysTime::operator<=(const SysTime& rvalue)
{
   if (m_tm.tm_year != rvalue.m_tm.tm_year)
      return (m_tm.tm_year < rvalue.m_tm.tm_year);

   if (m_tm.tm_yday != rvalue.m_tm.tm_yday)
      return (m_tm.tm_yday < rvalue.m_tm.tm_yday);

   if (SecondsToday() != rvalue.SecondsToday())
      return (SecondsToday() < rvalue.SecondsToday());

   return m_tm.m_nanoseconds <= rvalue.m_tm.m_nanoseconds;
}


/****************************************************************************
METHOD      :  operator== 
DESCRIPTION :  Compare if passed in time is equal to this object
ARGUMENTS   :  const SysTime& rvalue          -- time to compare
RETURNS     :  bool
****************************************************************************/
bool SysTime::operator==(const SysTime& rvalue)
{
   bool bEqual;

   bEqual = (m_tm.tm_year == rvalue.m_tm.tm_year)
            && (m_tm.tm_yday == rvalue.m_tm.tm_yday)
            && (SecondsToday() == rvalue.SecondsToday())
            && (m_tm.m_nanoseconds == rvalue.m_tm.m_nanoseconds);

   return bEqual;
}


/****************************************************************************
METHOD      :  operator!=
DESCRIPTION :  Compare if passed in time is not equal to this object
ARGUMENTS   :  const SysTime& rvalue          -- time to compare
RETURNS     :  bool
****************************************************************************/
bool SysTime::operator!=(const SysTime& rvalue)
{
   return !(*this == rvalue);
}

