/* Object to hold a date -- created 8/26/88 acr

   11/30/88 mzu  -added a test in asString to use international
                  date format.
                 -asLongString, dayOfWeek are hard coded in english
*/!!

inherit(Magnitude, #Date, #(month   /*  Month 1-12             */
day     /*  Day   1-31             */
year    /*  Year                   */), 2, nil)!!

now(DateClass)!!

/* Create a new Date object and initialize. */
Def new(self)
{  ^init(new(self:Behavior))
}!!

/* Return current date. */
Def current(self | ds)
{  ds := new(Struct, 16);
   putMSB(ds, 0x2a, 4);
   call(ds);

   ^date(atMSB(ds, 10), atLSB(ds, 10), wordAt(ds, 8))
}!!

now(Date)!!

/* Return date in string in international date format */
Def asString(self | iDate)
{
  iDate := getProfileString("intl", "iDate"); 
  select
    case iDate = "2"
      ^asYYMMDDString(self);
    endCase
    case iDate = "1"
      ^asDDMMYYString(self);
    endCase
    default            /* "0" or not found */
      ^asMMDDYYString(self);
    endSelect;
}!!

/* Return date in abbreviated YY/MM/DD string format. */
Def asYYMMDDString(self)
{  ^asPaddedString(year mod 100, 2) + "/" +
    asString(month) + "/" +
    asString(day);
}!!

/* Return date in abbreviated MM/DD/YY string format. */
Def asMMDDYYString(self)
{  ^asString(month) + "/" +
    asString(day)   + "/" +
    asPaddedString(year mod 100, 2)
}!!

/* Return date in abbreviated DD/MM/YY string format. */
Def asDDMMYYString(self)
{  ^asString(day) + "/" +
    asString(month)   + "/" +
    asPaddedString(year mod 100, 2)
}!!

/* Return date in long string format. */
Def asLongString(self)
{  ^#("January" "February" "March" "April" "May" "June" "July"
     "August" "September" "October" "November" "December")[month-1] +
     " "  + asString(day) + ", " + asString(year)
}!!

/* Return date in 'day of the week' format. */
Def asDayString(self)
{  ^dayOfWeek(self) + ", " + asLongString(self)
}!!

/* Return number of days since 1/1/1901. */
Def asLong(self)
{  ^asLong(
      (year - 1901) * 365 +
      daysBeforeMonth(self) +
      day - 1 +
      leapDays(self)
   )
}!!

/* Included so that addition and subtraction
   will work with Longs or Ints.             */
Def asInt(self)
{  ^asLong(self)
}!!

/* Infix operation.  Return 0 if x = self, otherwise returns nil. */
Def =(self, x)
{  if class(self) == class(x)
      ^(x.day == day) cand (x.month == month) cand (x.year == year)
   else
      ^nil
   endif
}!!

/* Infix operation.  Return 0 if x < self, otherwise return nil. */
Def <(self, x)
{  select
      case (x.year < year)
         ^0
      endCase
      case (x.year == year) cand (x.month < month)
         ^0
      endCase
      case (x.year == year) cand (x.month == month) cand (x.day < day)
         ^0
      endCase
      default
         ^nil
   endSelect;
}!!

/* Infix operation.  Return 0 if x <= self, otherwise return nil. */
Def <=(self, x)
{  ^(x < self) cor (x = self)
}!!

/* Infix operation.  Return 0 if x > self, otherwise return nil. */
Def >(self, x)
{  select
      case (x.year > year)
         ^0
      endCase
      case (x.year == year) cand (x.month > month)
         ^0
      endCase
      case (x.year == year) cand (x.month == month) cand (x.day > day)
         ^0
      endCase
      default
         ^nil
   endSelect;
}!!

/* Infix operation.  Return 0 if x >= self, otherwise return nil. */
Def >=(self, x)
{  ^(x > self) cor (x = self)
}!!

/* Infix operation.  Add a Date to another Date, an Int, or a Long.
   Return a Long.  You must use asDate() to convert back to a Date.   */
Def +(self, x)
{  ^asLong(x) + asLong(self):Long
}!!

/* Infix operation.  Find difference between two dates, or
   subtract a number of days (an Int or Long) from a Date.   */
Def -(self, x)
{  ^asLong(x) - asLong(self):Long
}!!

/* Return number of days between two dates.  This
   is a more efficient than date1 - date2.         */
Def diff(self, y)
{  ^(year - y.year) * 365 +
      daysBeforeMonth(self) - daysBeforeMonth(y) + 
      day - y.day +
      leapDays(self) - leapDays(y)
}!!

/* Increment a Date by one, modifying it directly. */
Def inc(self)
{  day := day + 1;

   if day > #(31 28 31 30 31 30 31 31 30 31 30 31)[month - 1]
      if month ~= 2 cor not(year mod 4 == 0) cor day > 29
         month := month + 1;
         day := 1;
         if month > 12
            year := year + 1;
            month := 1;
         endif;
      endif;
   endif
}!!

/* Decrement a Date by one, modifying it directly. */
Def dec(self)
{  day := day - 1;

   if day == 0
      month := (month - 1);

      if month == 0
         year := year - 1;
         month := 12;
      endif;

      day := #(0 31 28 31 30 31 30 31 31 30 31 30 31)[month];

      if (month == 2) cand (year mod 4 == 0)
         day := 29
      endif;
   endif
}!!

/* Given a date, return the next date.  Preserve original date. */
Def next(self | aDate)
{  aDate := new(Date);
   aDate.day := day;
   aDate.month := month;
   aDate.year := year;
   ^inc(aDate)
}!!

/* Given a date, return the previous date.  Preserve original date. */
Def previous(self | aDate)
{  aDate := new(Date);
   aDate.day := day;
   aDate.month := month;
   aDate.year := year;
   ^dec(aDate)
}!!

/* Return number of February 29s since 1/1/1901. */
Def leapDays(self)
{  if (month < 3) cand (year mod 4 == 0)
      ^(year - 1900)/4 - 1
   else 
      ^(year - 1900)/4
   endif
}!!

/* Private method.  Return number of days in a year before this month. */
Def daysBeforeMonth(self)
{  ^#(0 31 59 90 120 151 181 212 243 273 304 334 365)[abs((month - 1) mod 13)]
}!!

/* Initialize a date to 1/1/80. */
Def init(self)
{  month := 1;
   day := 1;
   year := 1980;
}!!

/* Determine if a Date is valid.  Used by conversion
   methods to guarantee that dates are always valid.  */
Def isValid(self)
{  if self <> asDate(asLong(self))
      ^nil
   endif
}!!

/* Return day of the week. */
Def dayOfWeek(self)
{  ^#("Tuesday" "Wednesday" "Thursday"
      "Friday" "Saturday" "Sunday" "Monday")[asInt(asLong(self) mod 7)]
}!!

/* Print a date. */
Def print(self)
{  print(asString(self))
}!!

/* Put a date onto the specified Stream. */
Def printOn(self, aStrm)
{  printOn(asString(self), aStrm)
}!!
