//----------------------------------------------------------------------------
// ObjectWindows
// Copyright (c) 1993, 1997 by Borland International, All Rights Reserved
//
/// \file
/// TDate class implementation
//----------------------------------------------------------------------------
#include <owl/pch.h>
#include <owl/date.h>
#include <owl/time.h>
#include <owl/profile.h>
#include <owl/wsyscls.h>
 
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <ctype.h>
#include <tchar.h>
 
namespace owl {
 
/****************************************************************
 *                                                              *
 *                      static constants                        *
 *                                                              *
 ****************************************************************/
 
const uint32 SECONDS_IN_DAY  = 86400L;
 
 
static const _TUCHAR sDaysInMonth[12] =
    { 31,28,31,30,31,30,31,31,30,31,30,31 };
static const DayTy sFirstDayOfEachMonth[12] =
    { 1,32,60,91,121,152,182,213,244,274,305,335 };
 
// default names
static const tchar* DefMonthNames[12] =
    { _T("January"),_T("February"),_T("March"),_T("April"),_T("May"),_T("June"),
      _T("July"),_T("August"),_T("September"),_T("October"),_T("November"),_T("December") };
static const tchar* DefUCMonthNames[12] =
    { _T("JANUARY"),_T("FEBRUARY"),_T("MARCH"),_T("APRIL"),_T("MAY"),_T("JUNE"),
      _T("JULY"),_T("AUGUST"),_T("SEPTEMBER"),_T("OCTOBER"),_T("NOVEMBER"),_T("DECEMBER") };
 
static const tchar* DefWeekDayNames[7] =
    { _T("Monday"),_T("Tuesday"),_T("Wednesday"),
      _T("Thursday"),_T("Friday"),_T("Saturday"),_T("Sunday") };
static const tchar* DefUCWeekDayNames[7] =
    { _T("MONDAY"),_T("TUESDAY"),_T("WEDNESDAY"),
      _T("THURSDAY"),_T("FRIDAY"),_T("SATURDAY"),_T("SUNDAY") };
 
 
static const tchar** MonthNames          = DefMonthNames;
static const tchar**  UCMonthNames        = DefUCMonthNames;
static const tchar** WeekDayNames        = DefWeekDayNames;
static const tchar**  UCWeekDayNames      = DefUCWeekDayNames;
 
_OWLSTATICFUNC(static int) FindMatch( const tchar *str, const tchar**candidates, int icand );
 
/***************************************************************************/
 
//      constructors
 
/***************************************************************************/
 
//
/// Constructs a TDate object with the current date.
//
TDate::TDate()
{
  //long clk = time(0);
  //struct tm *now = localtime(&clk);
  // We no longer need to default to adding 1900 - (GSF) ???????????
  //long _Julnum = Jday(now->tm_mon+1, now->tm_mday, now->tm_year+1900);
  TSystemTime _clk(TSystemTime::LocalTime());
  Julnum = Jday( _clk.GetMonth(), _clk.GetDay(), _clk.GetYear());
}
 
//
/// Constructs a TDate object with the given day and year. The base date for
/// this computation is December 31 of the previous year. If year == 0, it
/// constructs a TDate with January 1, 1901, as the "day zero." For example,
/// TDate(-1,0) = December 31, 1900, and TDate(1,0) = January 2, 1901.
//
TDate::TDate(DayTy day, YearTy year)
{
  if( year )
    Julnum = Jday( 12, 31, year-1 ) + (JulTy)day;
  else
    Julnum = jul1901                + (JulTy)day;
}
 
//
///   Construct a TDate for the given day, monthName, and year.
//
TDate::TDate( DayTy day, const tstring& monthName, YearTy year )
{
  Julnum = Jday( IndexOfMonth(monthName), day, year );
}
 
//
///   Construct a TDate for the given day, month, and year.
//
TDate::TDate( DayTy day, MonthTy month, YearTy year )
{
  Julnum = Jday( month, day, year );
}
 
//
/// Type conversion to date.
//
TDate::TDate( const TTime  & t )
{
  Julnum = t.IsValid() ? jul1901 + (JulTy)(t.LocalSecs()/SECONDS_IN_DAY) : 0;
}
 
//
/// Construct a TDate from a TSystemTime
//
TDate::TDate( const TSystemTime & t)
{
  Julnum = Jday( t.GetMonth(), t.GetDay(), t.GetYear());
}
 
//
//
//
TSystemTime
TDate::GetSystemTime() const
{
  MonthTy m; DayTy d; YearTy y;
  Mdy(m, d, y);
  return TSystemTime(y, m, d, 0, 0, 0, WeekDay());
}
 
/***************************************************************************/
 
//                      static member functions
 
/***************************************************************************/
 
//
/// Returns a string name for the weekday number.
/// Monday == 1, ... , Sunday == 7
/// Return 0 for weekday number out of range
//
const tchar * TDate::DayName( DayTy weekDayNumber )
{
    return AssertWeekDayNumber(weekDayNumber) ? WeekDayNames[weekDayNumber-1] : 0;
}
 
//
/// Return the number, 1-7, of the day of the week named nameOfDay.
/// Return 0 if name doesn't match.
//
DayTy TDate::DayOfWeek( const tstring& nameOfDay )
{
  return (DayTy)(FindMatch( nameOfDay.c_str(), UCWeekDayNames, 7 )+1);
}
 
// Return the number of days of a given month
// Return 0 if "month" is outside of the range 1 through 12, inclusive.
int TDate::DaysInMonth(MonthTy month) const
{
  uint days = 0;
 
  if (month == 0)
    month = Month();
 
  if (AssertIndexOfMonth(month)) {
    days = sDaysInMonth[month-1];
    if(Leap() && month == 2)
      days++;
  }
 
  return days;
}
 
//
/// Returns 1 if the given day (1-31) is within the given month for the given year.
//
int TDate::DayWithinMonth( MonthTy month, DayTy day, YearTy year )
{
  if( day <= 0 || !AssertIndexOfMonth(month) )
      return 0;
  unsigned d = sDaysInMonth[month-1];
  if( LeapYear(year) && month == 2 )
      d++;
  return day <= d;
}
 
//
/// Returns the number of days in the year specified (365 or 366).
//
DayTy TDate::DaysInYear( YearTy year )
{
  return LeapYear(year) ? 366 : 365;
}
 
//
/// Returns the number, 1-12, of the month named nameOfMonth.
/// Return 0 for no match.
//
MonthTy TDate::IndexOfMonth(const tstring& nameOfMonth)
{
  return (MonthTy)(FindMatch(nameOfMonth.c_str(), UCMonthNames, 12 )+1);
}
 
//
/// Convert Gregorian calendar date to the corresponding Julian day
/// number j.  Algorithm 199 from Communications of the ACM, Volume 6, No.
/// 8, (Aug. 1963), p. 444.  Gregorian calendar started on Sep. 14, 1752.
/// This function not valid before that.
/// Returns 0 if the date is invalid.
//
JulTy TDate::Jday( MonthTy m, DayTy d, YearTy y )
{
  uint32 c, ya;
  // We now always default to adding 1900 like the TDate CTOR - (GSF)
  // if( y <= 99 ).
  if( y <= 137 ) // year 2037 problem (long overflow)
    y += 1900;
  if( !DayWithinMonth(m, d, y) )
    return (JulTy)0;
 
  if( m > 2 )
    m -= 3;
  else{
    m += 9;
    y--;
  }
 
  c = y / 100;
  ya = y - 100*c;
  return ((146097L*c)>>2) + ((1461*ya)>>2) + (153*m + 2)/5 + d + 1721119L;
}
 
//
// Algorithm from K & R, "The C Programming Language", 1st ed.
//
int TDate::LeapYear( YearTy year )
{
  return ((year & 3) == 0 && (year % 100) != 0) || (year % 400) == 0;
}
 
//
/// Returns the string name for the given monthNumber (1-12). Returns 0 for an
/// invalid monthNumber.
//
const tchar  *TDate::MonthName( MonthTy monthNumber )
{
    return AssertIndexOfMonth(monthNumber) ? MonthNames[monthNumber-1] : 0;
}
 
//
// Return index of case-insensitive match; -1 if no match.
//
_OWLSTATICFUNC(static int)
FindMatch( const tchar* str, const tchar** candidates, int icand )
{
  unsigned len = static_cast<unsigned>(::_tcslen(str));
 
  while(icand--){
    if( _tcsnicmp(str, candidates[icand], len) == 0)
      break;
  }
  return icand;
}
 
/****************************************************************
 *                                                              *
 *                      Member functions                        *
 *                                                              *
 ****************************************************************/
 
//
/// Returns 1 if the target TDate is greater than parameter TDate, -1 if the target
/// is less than the parameter, and 0 if the dates are equal.
//
int TDate::CompareTo( const TDate  &d ) const
{
  if( Julnum < d.Julnum )
    return -1;
  else if( Julnum > d.Julnum )
    return 1;
  else
    return 0;
}
 
//
/// Returns the day of the year (1-365).
//
DayTy TDate::Day() const
{
  return DayTy(Julnum - Jday( 12, 31, Year()-1 ));
}
 
//
/// Returns the day (1-31) of the month of this TDate.
//
DayTy TDate::DayOfMonth() const
{
  MonthTy m; DayTy d; YearTy y;
  Mdy( m, d, y );
  return d;
}
 
//
/// Return the number of the first day of a given month
/// Return 0 if "month" is outside of the range 1 through 12, inclusive.
//
DayTy TDate::FirstDayOfMonth( MonthTy month ) const
{
  if ( !AssertIndexOfMonth(month) )
    return 0;
  unsigned firstDay = sFirstDayOfEachMonth[month-1];
  if (month > 2 && Leap())
    firstDay++;
  return firstDay;
}
 
/// Returns a hash value for the date.
unsigned TDate::Hash() const
{
  return (unsigned)Julnum;
}
 
//
/// Convert a Julian day number to its corresponding Gregorian calendar
/// date.  Algorithm 199 from Communications of the ACM, Volume 6, No. 8,
/// (Aug. 1963), p. 444.  Gregorian calendar started on Sep. 14, 1752.
/// This function not valid before that.
//
void TDate::Mdy(MonthTy& m, DayTy& d, YearTy& y) const
{
  uint32 _d;
  JulTy j = Julnum - 1721119L;
  y = (YearTy) (((j<<2) - 1) / 146097L);
  j = (j<<2) - 1 - 146097L*y;
  _d = (j>>2);
  j = ((_d<<2) + 3) / 1461;
  _d = (_d<<2) + 3 - 1461*j;
  _d = (_d + 4)>>2;
  m = (MonthTy)(5*_d - 3)/153;
  _d = 5*_d - 3 - 153*m;
  d = (DayTy)((_d + 5)/5);
  y = (YearTy)(100*y + j);
 
  if( m < 10 )
    m += 3;
  else{
    m -= 9;
    y++;
  }
}
 
//
/// Compares this TDate with dt and returns the date with the greater Julian number.
//
TDate
TDate::Max( const TDate  & dt ) const
{
  return dt.Julnum > Julnum ? dt : *this;
}
 
//
/// Compares this TDate with dt and returns the date with the lesser Julian number.
//
TDate
TDate::Min( const TDate  & dt ) const
{
  return dt.Julnum < Julnum ? dt : *this;
}
 
//
/// Returns the month of this TDate.
//
MonthTy
TDate::Month() const
{
  MonthTy m; DayTy d; YearTy y;
  Mdy(m, d, y);
  return m;
}
 
//
/// Returns the TDate of the previous dayName.
//
TDate
TDate::Previous(const tstring& dayName) const
{
  return Previous( DayOfWeek(dayName) );
}
 
//
/// Returns the TDate of the previous day.
//
TDate
TDate::Previous( DayTy desiredDayOfWeek ) const
{
  //    Renumber the desired and current day of week to start at 0 (Monday)
  //    and end at 6 (Sunday).
 
  desiredDayOfWeek--;
  DayTy thisDayOfWeek = WeekDay() - 1;
  JulTy j = Julnum;
 
  //    Have to determine how many days difference from current day back to
  //    desired, if any.  Special calculation under the 'if' statement to
  //    effect the wraparound counting from Monday (0) back to Sunday (6).
  if( desiredDayOfWeek > thisDayOfWeek )
    thisDayOfWeek += 7 - desiredDayOfWeek;
  else
    thisDayOfWeek -= desiredDayOfWeek;
  j -= thisDayOfWeek; // Adjust j to set it at the desired day of week.
  return  TDate(j);
}
 
//
/// Returns 1 (Monday) through 7 (Sunday).
//
DayTy TDate::WeekDay(DayTy day) const
{
  JulTy julnum = (day == 0 ? Julnum : Jday(day, Month(), Year()));
  return DayTy(((((julnum+1)%7)+6)%7)+1);
}
 
//
/// Returns the year of this TDate.
//
YearTy TDate::Year() const
{
  MonthTy m; DayTy d; YearTy y;
  Mdy(m, d, y);
  return y;
}
 
namespace
{
 
  int accept_digits(LPCTSTR str)
  {
    int n = 0;
    while (_istdigit(str[n]))
      n++;
    return n;
  }
 
  int skip(LPCTSTR picture, tchar c)
  {
    int n = 0;
    while (_totlower(picture[n]) == c)
      n++;
    return n;
  }
 
  int parse_number(LPCTSTR& str, LPCTSTR& picture, tchar format_char)
  {
    const int n = accept_digits(str);
    int result = _ttoi(tstring(str, 0, n).c_str());
    str += n;
    picture += skip(picture, format_char);
    return result;
  }
 
} // namespace
 
//
/// Convert a string to a date according to the [Intl] section in win.ini or the
/// passed format specifier
void TDate::ParseFrom(LPCTSTR str, LPCTSTR format)
{
  int day, month, year;
  Julnum = day = month = year = 0;
 
  TProfile p(_T("intl"));
  tstring shortFmt = p.GetString(_T("sShortDate"),  _T("MM/dd/yyyy"));
  LPCTSTR picture = format ? format : shortFmt.c_str();
 
  while (*picture && *str) {
    tchar c = (tchar)_totlower(*picture);
    switch (c) {
      case _T('d'): {
        day = parse_number(str, picture, c);
        break;
      }
      case _T('m'): {
        month = parse_number(str, picture, c);
        break;
      }
      case _T('y'): {
        year = parse_number(str, picture, c);
        break;
      }
      default: {
        picture++;
        str++;
      }
    }
  }
 
  Julnum = Jday(month, day, year);
  if (Julnum < 1)
    Julnum = 0;
}
 
//
//
//
void
TDate::SetIntlNames(TDateType type, const tchar** names)
{
  const tchar*** Names[] = { &MonthNames, &UCMonthNames,
                             &WeekDayNames, &UCWeekDayNames,
                           };
  const tchar*** DefNames[] = {
    (const tchar***)&DefMonthNames, (const tchar***)&DefUCMonthNames,
    (const tchar***)&DefWeekDayNames, (const tchar***)&DefUCWeekDayNames,
  };
  if(names)
    *(Names[type])  = names;
  else
    *(Names[type])  = *(DefNames[type]);
}
 
//
//
//
const tchar*
TDate::GetIntlName(TDateType type, int index)
{
  const tchar** Names[] = { MonthNames, UCMonthNames,
                             WeekDayNames, UCWeekDayNames,
                           };
  return Names[type][index];
}
 
//
/// Returns a new TDate containing the sum of this TDate and dd.
//
_OWLFUNC(TDate) operator + ( const TDate& dt, int dd )
{
  return TDate(dt.Julnum + dd);
}
 
//
/// Returns a new TDate containing the sum of this TDate and dd.
//
_OWLFUNC(TDate) operator + ( int dd, const TDate& dt )
{
  return TDate(dt.Julnum + dd);
}
 
//
//
//
_OWLFUNC(TDate) operator - ( const TDate& dt, int dd )
{
  return TDate(dt.Julnum - dd);
}
 
//
//
//
} // OWL namespace
/* ========================================================================== */

V1048 The 'Julnum' variable was assigned the same value.