// David Blackledge's Calendar implementation
// (c)2000 David L. Blackledge
// Written on 8/2/00
// You may use this if you keep this copyright notice intact
// --------------------
// Calendar Object
// --------------------
// Constructor Accepts:
//  nothing for current calendar, or
//  a month for a particular month in the current year,
//  a year to specify a different year for the month,
//  and a day to have that date be the default selected day.
// You may set the "selectedDate" Date to change which day gets the "SelectedDay" class
// You may set the "date" Date to change which year/month is current
//  (day portion is not used)
// Use .toString() to render as a table.
// Set .action to a URL with $DATE$ $MONTH$ $MONTHNAME$ and $YEAR$
//  substitution variables to make dates clickable.
// Set .action back to null to not use URLs
// .events is a CalendarEvents object.  See description below.
//  Calendar.toString() renders events based on time ("'",":","." or "|" for all day)
// Use Calendar.prototype.monthNames array to get a name for a month [0-11]
// Use Calendar.prototype.standardStyle(); to set some good default styles, OR
// Make your own Stylesheet using
//  TABLE.CalendarTable,
//  TR.CalendarMonthHeader,
//  TR.CalendarMonthHeader, SPAN.CalendarYearHeader,
//  TR.CalendarDayHeader,
//  TD.Saturday,
//  TD.Sunday,
//  #Today,
//  #SelectedDay,
//  #SelectedToday /* when selectedDay and Today are the same day */
// Use the style "display:none" to hide things like the year.
// ----------------
// Recommended code:
//<!-- allow Netscape to use document.all(id) and .innerHTML (and .style) -->
//<SCRIPT language="JavaScript" SRC="IE4Netscape.js"></SCRIPT>
//<SCRIPT language="JavaScript" SRC="dlbCalendar.js"></SCRIPT>
//<DIV style="position:absolute;top:0;height:20;">
//<A href="javascript:theCal.date.setMonth(theCal.date.getMonth()-1);theCal.selectedDate=new Date(theCal.date);document.all('cal').innerHTML=theCal.toString();void(0);">&lt;&lt;</a>
//<A href="javascript:theCal.date = new Date();theCal.selectedDate=new Date(theCal.date);document.all('cal').innerHTML=theCal.toString();void(0);">Today</a>
//<A href="javascript:theCal.date.setMonth(theCal.date.getMonth()+1);theCal.selectedDate=new Date(theCal.date);document.all('cal').innerHTML=theCal.toString();void(0);">&gt;&gt;</a>
//</DIV>
//<DIV id=cal style="position:absolute;top:20">
//<!-- DIVs must be absolute so they work in Netscape with the .all(id) function -->
//<SCRIPT language="JavaScript">
//Calendar.prototype.standardStyle();
//theCal = new Calendar();
//theCal.selectedDate = new Date(x.date);
//theCal.action="javascript:theCal.selectedDate=new Date($YEAR$,$MONTH$,$DAY$);document.all('cal').innerHTML=theCal.toString();void(0);";
//document.writeln(theCal);
//</SCRIPT>
//</DIV>

function Calendar(month,year,date) {
 var today = new Date();
 if(year == null)
  year = today.getFullYear();
 if(month == null)
  month = today.getMonth();
 if(date == null) {
  this.selectedDate = null;
  date = today.getDate();
 } else
  this.selectedDate = new Date(year,month,date);// =this.date would SHARE a reference.
 this.date = new Date(year,month,date);

 this.action = null;

 this.events = new CalendarEvents();
 this.events.putEvent(null,"January",1,"Holiday",null,"New Year's Day");
 this.events.putEvent(null,"February",10,"Birthday",null,"Calendar Author's Birthday.  Send him presents.");
 this.events.putEvent(null,"February",29,"Special",null,"Leap Year Day.");
 this.events.putEvent(null,"July",4,"Holiday",null,"Independence Day (U.S.A.)");
 this.events.putEvent(null,"October",31,"Holiday",null,"Halloween");
 this.events.putEvent(null,"December",25,"Holiday",null,"Christmas Day");

 function render() {
  var year = this.date.getFullYear();
  var month = this.date.getMonth();
  var selectedDay = null;
  if(this.selectedDate != null &&
     this.selectedDate.getFullYear() == year &&
     this.selectedDate.getMonth() == month)
   selectedDay = this.selectedDate.getDate();
  var selectedDayID = " id='SelectedDay'";
  var today = null;
  var todayDate = new Date();
  if(todayDate.getFullYear() == year &&
     todayDate.getMonth() == month)
   today = todayDate.getDate();
  var todayID = " id='Today'";
  var selectedTodayID = " id='SelectedToday'";

  // calculate days in month
  var calDay = new Date(year,month,1);
  calDay.setDate(31);
  while(calDay.getMonth() != month)
   calDay.setDate(calDay.getDate()-1);
  var daysInMonth = calDay.getDate();
  var endDOW = calDay.getDay();
  calDay.setDate(1);
  var startDOW = calDay.getDay();

  // render header.
  var returnstring = "<TABLE class='CalendarTable' CELLPADDING=0 CELLSPACING=1 border=0>";
  returnstring +="<TR class='CalendarMonthHeader'><TH colspan='7'>"+this.monthNames[month]+" <SPAN class='CalendarYearHeader'>"+year+"</SPAN></TH></TR>";
  returnstring +="<TR class='CalendarDayHeader'><TH class='Sunday'>S</TH><TH>M</TH><TH>T</TH><TH>W</TH><TH>T</TH><TH>F</TH><TH class='Saturday'>S</TH></TR>";

  var date = 1;
  // render selected and today with special IDs
  function getID() {
    if(date == selectedDay) {
     if(date == today)
      return selectedTodayID;
     else
      return selectedDayID;
    }
    if(date == today)
     return todayID;
    return "";
  }
  function dateWithNote(it) {
  var a = it.events;
   var ev = a.getEvent(year,month,date);
   if(ev == null)
    return (date++);
   if(ev.isAllDay())
    return (date++)+"|";
   if(ev.isAM()) {
    if(ev.isPM())
     return (date++)+":";
    return (date++)+"'";
   }
   return (date++)+".";
  }
  // render first week.
  if(startDOW != 0) {
   returnstring+= "<TR><TD colspan='"+startDOW+"'></TD>";
   for(var weekday = startDOW ; weekday <= 5 ; ++weekday) {
    returnstring += "<TD"+getID()+">"+dateWithNote(this)+"</TD>";
   }
   returnstring += "<TD class='Saturday'"+getID()+">"+dateWithNote(this)+"</TD>";
   returnstring += "</TR>";
  }
  // render middle weeks.
  // number of weeks is days minus first incomplete week and last incomplete week.
  var numFullWeeks = (daysInMonth - ((7-startDOW)%7) - ((endDOW+1)%7)) / 7
  for(var fullweek = 1 ; fullweek <= numFullWeeks ; ++fullweek) {
   returnstring += "<TR>";
   returnstring += "<TD class='Sunday'"+getID()+">"+dateWithNote(this)+"</TD>";
   for(var weekday = 1 ; weekday <= 5 ; ++weekday) {
    returnstring += "<TD"+getID()+">"+dateWithNote(this)+"</TD>";
   }
   returnstring += "<TD class='Saturday'"+getID()+">"+dateWithNote(this)+"</TD>";
   returnstring += "</TR>";
  }
  // render last week.
  if(endDOW != 6) {
   returnstring += "<TR>";
   returnstring += "<TD class='Sunday'"+getID()+">"+dateWithNote(this)+"</TD>";
   for(var weekday = 1 ; weekday <= endDOW ; ++weekday) {
    returnstring += "<TD"+getID()+">"+dateWithNote(this)+"</TD>";
   }
   // sunday will never happen (endDOW != 6)
   returnstring += "</TR>";
  }
  // render footer
  returnstring += "</TABLE>";

  // add urls if needed.
  if(this.action != null) {
   var action = this.action.replace(/\$MONTH\$/gi,this.date.getMonth());
   action = action.replace(/\$MONTHNAME\$/gi,this.monthNames[this.date.getMonth()]);
   action = action.replace(/\$YEAR\$/gi,this.date.getFullYear());
   // make heavy use of regular expressions.
   //var dayRE = new RegExp(">(\d?\d)</TD>","g"); // match 1 or 2 numbers in TD area
   for(var i = 1 ; i <= 31 ; ++i)
    returnstring = returnstring.replace(new RegExp(">"+i+"([.:'|]?)</TD>"), 
      "><A HREF=\""+action.replace(/\$DAY\$/gi,i)+"\">"+i+"$1</TD>");
  }

  // return;
  return returnstring;
 }
 this.toString = render;  
}
Calendar.prototype.monthNames = new Array("January","February","March","April","May","June","July","August","September","October","November","December");
/* provide a JS-based stylesheet if they don't want to make their own */
function CalendarStandardStyle() {
 if(document.styleSheets) {
 if(document.styleSheets.length < 1)
  return;
 document.styleSheets(0).addRule("TR.CalendarMonthHeader TH","font-size:small");
 document.styleSheets(0).addRule("TABLE.CalendarTable TD","text-align:right;background-color:#EEEEEE;font-size:small");
 document.styleSheets(0).addRule("TABLE.CalendarTable TD A","text-decoration:none");
 document.styleSheets(0).addRule("TABLE.CalendarTable TD.Saturday","background-color:#CCCCCC");
 document.styleSheets(0).addRule("TABLE.CalendarTable TD.Sunday","background-color:#CCCCCC");
 document.styleSheets(0).addRule("TABLE.CalendarTable #Today","font-weight:bold");
 document.styleSheets(0).addRule("TABLE.CalendarTable #SelectedDay","background-color:yellow");
 document.styleSheets(0).addRule("TABLE.CalendarTable #SelectedToday","font-weight:bold");
 document.styleSheets(0).addRule("TABLE.CalendarTable #SelectedToday","background-color:yellow");
 } else if(document.contextual) {
 document.contextual(document.classes.CalendarMonthHeader.TR, document.tags.TH).fontSize="small";
 //document.classes.CalendarYearHeader.whatever
 document.contextual(document.classes.CalendarDayHeader.TR, document.tags.TH).fontSize="xx-small";
 with(document.contextual(document.classes.CalendarTable.TABLE, document.tags.TD)) {
  textAlign="right";
  backgroundColor="#EEEEEE";
  fontSize="small";
  document.contextual(document.tags.A).textDecoration="none";
 }
 document.contextual(document.classes.CalendarTable.TABLE, document.classes.Saturday.TD).backgroundColor="#CCCCCC";
 document.contextual(document.classes.CalendarTable.TABLE, document.classes.Sunday.TD).backgroundColor="#CCCCCC";
 document.contextual(document.classes.CalendarTable.TABLE, document.ids.Today).fontWeight="bold";
 document.contextual(document.classes.CalendarTable.TABLE, document.ids.SelectedDay).backgroundColor="yellow";
 document.contextual(document.classes.CalendarTable.TABLE, document.ids.SelectedToday).fontWeight="bold";
 document.contextual(document.classes.CalendarTable.TABLE, document.ids.SelectedToday).backgroundColor="yellow";
 }
}
Calendar.prototype.standardStyle = CalendarStandardStyle;

// --------------------
// CalendarEvents "collection" of CalendarEvent objects
// --------------------
// Constructor accepts no parameters
// remove, get, and put accept month name (in "January" format) for month as well
// Use putEvent(year,month[0-11],day,type,time,object) to add CalendarEvent objects.
//  if year is null, event will REPEAT every year (in future and past);
//  See CalendarEvent for a description of the event added.
//  Currently can have only ONE EVENT PER DAY, so it is really only for display purposes.
//   - repeating events only show up if there is no explicit event on that date.
//  use the object reference to hold additional information.
// Use .toString([separator]) to list all stored events with seperator (or "\n")
// Use .getEvent(year,month[0-11],day) to get the CalendarEvent object or null.
// Use .removeEvent(year,month[0-11],day) to remove the the event for that day.
//  if year=null remove the repeating event for that day.
// Use .clearEvents() to remove ALL events for ALL years INCLUDING REPEATED events.
//  cleans up object unlike removeEvent.
function CalendarEvents() {
 function _putEvent(year,month,day,type,time,objectref) {
  var Y = this["Y"+year];
  if(typeof(Y) == "undefined") {
   this["Y"+year] = new Array();
   Y = this["Y"+year];
   for(var i = 0 ; i < 12 ; ++i) {
    Y[i] = new Array();
    Y[i].length = 31;
    Y[Calendar.prototype.monthNames[i]] = Y[i];
   }
  }
  Y[month][day] = new CalendarEvent(type,time,objectref);
 }
 this.putEvent=_putEvent;
 function _getEvent(year,month,day) {
  var returnval = null;
  var Y = this["Y"+year];
  if(typeof(Y) != "undefined")
   returnval = Y[month][day];
  if(returnval == null) {
   // check for any-year events.
   Y = this["Y"+null];
   if(typeof(Y) != "undefined")
    returnval = Y[month][day];
  }
  return returnval;
 }
 this.getEvent = _getEvent;

 function _removeEvent(year,month,day) {
  var Y = this["Y"+year];
  if(typeof(Y) != "undefined") {
   delete Y[month][day];
  }
 }
 this.removeEvent = _removeEvent;

 function _clearEvents() {
  for(var year in this) {
   if(year.indexOf("Y")==0) {
    delete this[year]
   }
  }
 }
 this.clearEvents = _clearEvents;

 function _toString(sep) {
  if(sep==null) sep = "\n";
  var s = "";
  for(var year in this) {
   if(year.indexOf("Y")==0) {
    if(year == "Y"+null)
     s+=sep+"Repeating:"+sep;
    else
     s+=sep+year+":"+sep;
    for(var month = 0 ; month < this[year].length ; ++month) {
     var s2 = "";
     for(var day = 0 ; day < this[year][month].length ; ++day) {
      if(this[year][month][day] != null) {
       var ones = (" "+day);
       var th = "th";
       if(ones.substring(ones.length-2,ones.length-1)!="1") { //11th,12th,13th exceptions
        ones = ones.substring(ones.length-1);
        th = (ones=="1"?"st":(ones=="2"?"nd":(ones=="3"?"rd":"th")));
       }
       s2+=day+th+" - "+this[year][month][day].toString()+sep;
      }
     }
     if(s2 != "")
      s+=Calendar.prototype.monthNames[month]+":"+sep+s2;
    }
   }
  }
  return (s!=""?s:sep+"No events defined"+sep);
 }
 this.toString = _toString;
}

// --------------------
// CalendarEvent object for CalendarEvents object for the Calendar object.
// --------------------
// Constructor Accepts:
//  type is not used for anything but .toString() so far.
//  time is a string like "1pm-11:15pm" or "13:20-" or null for "All Day"
//   so, for multi day events, make a set of events like "<start>-", null, "-<end>".
//  object could be just a string for description
//   or any other object reference you want to hold the REAL event information.
// Methods:
// getStartTime() - extracts the start portion of time, or null
// getEndTime() - extracts the end portion of time, or null
// getDuration() - time elapsed between start and end in minutes
// durationToHoursMinutes([duration]) - getDuration converted to "<hrs>h<mins>m" format
// isAM() - true if start time is in the morning FALSE IF ALL DAY
// isPM() - true if end time is in the evening FALSE IF ALL DAY
// isAllDay() - true if start and end are null
// toString() - "<time/'All Day'>(<type>):<object.toString()>"
function CalendarEvent(type,time,objectref) {
 this.type = type;
 this.time = time;
 this.objectref = objectref;

 function _toString() {
  return (this.isAllDay()?"All day":this.time)+"("+this.type+"):"+objectref;
 }
 this.toString = _toString;

 function _getStartTime() {
  if(this.time == null)
   return null;
  var partTimeRE = /\s*([12]?\d[:\. h]?([0-5]\d)?[:\. m]?([0-5]\ds?)?\s*([ap]m?)?)\s*-[^\-]*/i;
  var start=this.time.match(partTimeRE);
  if(start != null)
   start = start[1];
  else
   start = "";
  //if(this.time.indexOf("-") == -1)
  // return this.time;
  //var start = this.time.substring(0,this.time.indexOf("-"));
  if(start == "")// || start == " ")
   return null;
  return start;
 }
 this.getStartTime = _getStartTime;

 function _getEndTime() {
  if(this.time == null)
   return null;
  var partTimeRE = /[^\-]*-\s*([12]?\d[:\. h]?([0-5]\d)?[:\. m]?([0-5]\ds?)?\s*([ap]m?)?)\s*/i;
  var end = this.time.match(partTimeRE)
  if(end != null)
   end = end[1];
  else
   end = "";
  //if(this.time.indexOf("-") == -1)
  // return this.time;
  //var end = this.time.substring(this.time.indexOf("-")+1,this.time.length);
  if(end == "")// || end == " ")
   return null;
  return end;
 }
 this.getEndTime = _getEndTime;

 function _getDuration() { // in minutes;
  var start = this.getStartTime()
  var end = this.getEndTime();
  if(start == end)
   return 0;
  var startH = 0;
  var startM = 0;
  if(start != null) {
   timeRE = /([12]?\d)[:\. h]?([0-5]\d)?.*/;
   start.match(timeRE);
   startH = parseInt(RegExp.$1); if(isNaN(startH))startH=0;
   startM = parseInt(RegExp.$2); if(isNaN(startM))startM=0;
   if(start.toLowerCase().indexOf("p") != -1)
    startH += 12;
   else if(start.toLowerCase().indexOf("a") != -1 && startH==12)
    startH = 0;
  }
  var endH = 24;
  var endM = 0;
  if(end != null) {
   end.match(timeRE);
   endH = parseInt("0"+RegExp.$1+"."); if(isNaN(endH))endH=0;
   endM = parseInt(RegExp.$2+"."); if(isNaN(endM))endM=0;
   if(end.toLowerCase().indexOf("p") != -1)
    endH += 12;
   else if(end.toLowerCase().indexOf("a") != -1 && endH==12)
    endH = 0;
  }
  return (endH-startH)*60 + endM-startM;
 }
 this.getDuration = _getDuration;
 function _durationToHoursMinutes(dur) {
  if(dur == null) dur = this.getDuration();
  return Math.floor(dur/60)+"h"+dur%60+"m"
 }
 this.durationToHoursMinutes = _durationToHoursMinutes;

 function _isAM() {
  var st=this.getStartTime();
  if(st==null) {
   if(this.getEndTime()!=null)
    return true;
   else
    return false;
  }
  if(st.toLowerCase().indexOf("a") != -1)
   return true;
  if(st.toLowerCase().indexOf("p") == -1) {
   if(parseInt(st) < 12)
    return true;
  }
  return false;
 }
 this.isAM = _isAM;
 function _isPM() {
  var et=this.getEndTime();
  if(et==null) {
   if(this.getStartTime()!=null)
    return true;
   else
    return false;
  }
  if(et.toLowerCase().indexOf("p") != -1)
   return true;
  if(et.toLowerCase().indexOf("a") == -1) {
   if(parseInt(et) >= 12)
    return true;
  }
  return false;
 }
 this.isPM = _isPM;
 this.isAllDay = new Function("if(this.getStartTime()==null && this.getEndTime()==null) return true; else return false;");

}



