/* Holds a date as the number of days from the start of the
   Gregorian calendar */!!

inherit(Long, #Date, nil, 1, nil)!!

now(class(Date))!!

/* Return current date. */
Def current(self | ds)
{ ds := new(Struct, 18);
  putMSB(ds, 0x2a, 4);
  call(ds);
  ^date(new(self),
    atMSB(ds, 10), /* Month */
    atLSB(ds, 10), /* Day */
    wordAt(ds, 8)  /* Year */);
}!!

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

now(Date)!!

/* Converts passed number to a date. */
Def asDate(self)
{
}!!

/* Return an array with three elements - month, day, year.
  [algorithm 199, from collected algorithms from the ACM] */
Def asDateArray(self | gregorian, month, day, year)
{ gregorian := asLong(self) + 578041L;
  year := ((gregorian * 4) - 1) / 146097L;
  gregorian := ((gregorian * 4) - 1) - (year * 146097L);
  day := (gregorian / 4);
  gregorian := ((day * 4) + 3) / 1461;
  day := ((day * 4) + 3) - (gregorian * 1461);
  day := (day + 4) / 4;
  month := ((day * 5) - 3) / 153;
  day := ((day * 5) - 3) - (month * 153);
  day := (day + 5) / 5;
  year := (year * 100) + gregorian;
  if month < 10
  then month := month + 3;
  else month := month - 9;
    year := year + 1;
  endif;
  ^tuple(asInt(month), asInt(day), asInt(year));
}!!

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

/* Return self as a long number for arithmetic purposes. */
Def asLong(self | temp)
{ ^setClass(temp := copy(self), Long);
}!!

/* Return date in long string format. */
Def asLongString(self | da)
{ da := asDateArray(self);
  ^#("January" "February" "March" "April" "May" "June" "July"
    "August" "September" "October" "November" "December")[da[0]-1] +
    " " + asString(da[1]) + ", " + asString(da[2]);
}!!

/* Return date in abbreviated string format. */
Def asString(self | da)
{ da := asDateArray(self);
  ^asString(da[0]) + "-" +
    asString(da[1]) + "-" +
    asString(da[2]);
}!!

/* Return a new date object with the passed month, day and year.
  Calculated as month months plus day days from the first day of
  the year i.e. date(new(Date), 11, 31, 1990) returns 12-01-1990.
  [algorithm 199, from collected algorithms from the ACM] */
Def date(self, month, day, year | century)
{ if month > 2
  then month := month - 3;
  else month := month + 9;
    year := year - 1;
  endif;
  century := year / 100;
  year := year - (century * 100);
  ^set(self, ((asLong(century) * 146097L) / 4L)
      + ((asLong(year) * 1461L) / 4L)
      + (((asLong(month) * 153L) + 2L) / 5L)
      + (asLong(day) - 578041L));
}!!

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

/* Return the date of week number (0=Sunday...7=Saturday). */
Def dayOfWeekNumber(self)
{ ^asInt((asLong(self)+4L) mod 7);
}!!

/* Initialize all new dates to 1/1/1980. */
Def init(self)
{ ^date(self, 1, 1, 1980);
}!!

/* The limit of a date object is 2. */
Def limit(self)
{ ^2;
}!!

/* Given a date, return the next date.  Preserve original date. */
Def next(self)
{ ^asDate(self + 1);
}!!

/* Given a date, return the previous date.  Preserve original date. */
Def previous(self)
{ ^asDate(self - 1);
}!!

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

/* Private method which sets the (indexed) value of self with
   the passed value. */
Def set(self, value | temp)
{ temp := new(Struct, 4);
  putLong(temp, value, 0);
  setClass(temp, class(self));
  swapProperties(self, temp);
  swap(self, temp);
}!!

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