// Javascript Event Calendar JRK 12 January 2009
// external functions:
//
//  getToday (currently used on top of "mini-calendar" on calendar.html)
//    arguments: none
//    returns: string of the type "Thursday, 15 January 2009"
//
//  getMonthlyEvents (currently used as caption of a "mini-calendar" on calendar.html)
//    arguments: year (YYYY), month (MM), showYM (true or false)
//    returns: a string containing all events for the month, each in a <p>...</p> tag, of the form
//      "Wednesday 21: EventDescription" if showYM is false
//      "Wednesday, 21 January 2009: EventDescription" if showYM is true
//
//  getGroupMeetings (currently used in sidebar on calendar.html)
//    arguments: year (YYYY), month (MM)
//    returns: a string containing all group meetings for the month, each in a <p>...</p> tag,
//     of the form "Wed 21: Group 175 Long Beach"
//
//  getNextGroupMeeting (currently used on "join.html" or "meetings.html" page for local group)
//    arguments: year (YYYY), month (MM), date (DD), group number
//    returns a string of the form
//        <p>Our next meeting will be at:</p>
//        <p>Date: Day-of-week DD Month YYYY</p>
//        <p>Time: HH24:MI (HH:30 PM)</p>
//
//  getEvtListStartDate
//    returns the eventCal date (defaults to current date, changed by calBackwards/calFowards)
//
//  calMove
//    arguments: increment = -1 to go back one month, +1 to go forward 1 month, 0 to go to today
//    move forward or backward in time, prevents going more than maxScroll months 
//
//  eventCal (monthly "mini-calendar", currently used at top of calendar.html)
//    arguments: year (YYYY), month (MM), scoll back function name, scroll now function name,
//               scroll forward function name
//    returns: a string containing
//           navigation tools: back button, today button, forward button
//              (in a 'p class="details" tag')
//           an HTML table showing a monthly calendar. Dates where events
//             occur will be highlighted with 'th class="top"'
//
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//  Only modify the user-customizable section! Nothing else!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//

// -----------------------------------------------------------------------------------------------
// Begin user-customizable section
// -----------------------------------------------------------------------------------------------

// ++++++++++++++++++++++++++++++++++++
// Event Calendar Array
// Add an event to the array below, with the proper index
// For optional values, enter a null if they are not specified.
//  Year: 4-digit year. Month: 1 = January, 12 = December.
//  The first 3 columns are for one-time events: year, month, day
//  The following 4 columns are for recurring events: day of week, week of month,
//        start year, start month, end year, end month
//    For recurring events: day of week is number from 0 to 6 (0 = Sunday, 1 = Monday, etc.)
//                          week of month is number from 1 to 5 (1 = first week of month
//                                                               5 = last week of month)
// Following that are start time and end time (start time required, end time optional)
// Last two columns are event description (required) and event URL (optional).
// On each line, you should include either the first three columns (for a one-time event)
//  or the next 4 columns (for a recurring event) - not both. If one-time event, then the
//  "recurring" fields should be null. If recurring event, then the "one-time" fields would be null.
// All the fields for the same event must be on the same line - no carriage return between fields.
//
//  Examples:
//   1) Group 175 meets on third Wednesday of every month except December. at 7 PM.
//       Enter day of week = 3 (Wednesday)  week of month = 3  start month = 1 (January) end month = 11
// calEvents[10] = new eventInfo (null, null, null, 3, 3, 1, 11, 1900, null, 'Long Beach Group Meeting', null) ;
//
//   2) One-time Special event by Group 141 on 29 February 2009 at 8:15 PM
// calEvents[5] = new eventInfo (2009, 2, 29, null, null, null, null, 2015, null, 'Group 141 concert',
//    'http://www.thepond.com/concert141.html') ;
//      If this replaces the regular meeting, you will also need to create an entry in the
//       Special Group Meeting table to cancel the regular group meeting.
//

// events. Data items are
// year, month, day, day-of-week (0=Sun, 1=Mon, etc.),
// week-of-month (1 = first week of the month, 5 = last week of the month),
// start-year, start-month, end-year, end-month,
// start-time (24-hour time), end-time (24-hour time), description, URL
//
// "line numbers" (array indexes) should be increasing sequentially with no duplicates
var calEvents = new Array() ; 

// group meetings
calEvents[0] = new eventInfo (null, null, null, 2, 4, null, 1, null, 11, 1900, null, 'Orange Group Meeting', 'http://www.aiusaoc.org/orange/join.html') ;  
calEvents[1] = new eventInfo (null, null, null, 3, 3, null, 1, null, 12, 1900, null, 'Long Beach Group Meeting', 'http://www.aiusaoc.org/longbeach/join.html') ; 
calEvents[2] = new eventInfo (null, null, null, 4, 5, null, 1, null, 10, 1930, 2100, 'Irvine Group Meeting', 'http://www.aiusaoc.org/irvine/meetings.html') ;  
calEvents[3] = new eventInfo (null, null, null, 4, 1, null, 12, null, 12, 1930, 2100, 'Irvine Group Meeting', 'http://www.aiusaoc.org/irvine/meetings.html') ;

// special events
calEvents[4] = new eventInfo (2009, 1, 29, null, null, null, null, null, null, 1930, null, 'Irvine Group Meeting—City of Lost Children', 'http://www.aiusaoc.org/common/blog.html#city_of_lost_children') ;
calEvents[5] = new eventInfo (2009, 2, 10, null, null, null, null, null, null, 1200, 1300, '(non-AI) Is Proposition 8 Legal? A Forum on Marriage Equality', null) ;
calEvents[6] = new eventInfo (2009, 2, 11, null, null, null, null, null, null, 1930, 2100, 'Violence Against Women in Darfur (Film Screening)', 'http://www.aiusaoc.org/common/blog.html#one_night_one_voice') ;
calEvents[7] = new eventInfo (2008, 12, 10, null, null, null, null, null, null, null, null, 'Human Rights Day Write-a-thon', 'http://www.aiusaoc.org/common/blog.html#writeathon') ;
calEvents[8] = new eventInfo (2008, 10, 18, null, null, null, null, null, null, null, null, 'Walk For Hope', 'http://www.aiusaoc.org/common/blog.html#walk_for_hope_2008') ;
calEvents[9] = new eventInfo (2009, 2, 18, null, null, null, null, null, null, 1900, null, 'Long Beach Group Meeting—Free the Slaves', 'http://www.aiusaoc.org/common/blog.html#free_the_slaves') ;
calEvents[10] = new eventInfo (2009, 3, 4, null, null, null, null, null, null, 1730, 1900, 'Eve of Justice—Costa Mesa', 'http://www.aiusaoc.org/common/blog.html#eve_of_justice') ;
calEvents[11] = new eventInfo (2009, 3, 4, null, null, null, null, null, null, 1800, null, 'Eve of Justice—Long Beach', 'http://www.aiusaoc.org/common/blog.html#eve_of_justice') ;
calEvents[12] = new eventInfo (2009, 3, 4, null, null, null, null, null, null, 1930, 2100, 'Never Again? A Refugee from Darfur Speaks', 'http://www.aiusaoc.org/archives/common/past_announcements_2009.html#darfur_refugee_2009') ;
calEvents[13] = new eventInfo (2009, 3, 5, null, null, null, null, null, null, 1600, 1800, 'Equal Marriage Rights Candlelight Vigil', 'http://www.aiusaoc.org/common/blog.html#eve_of_justice') ;
calEvents[14] = new eventInfo (2009, 3, 6, null, null, null, null, null, null, 1400, 2000, 'Second Annual Gender and Justice Conference (Day 1)', 'http://www.aiusaoc.org/archives/common/past_announcements_2009.html#gender_and_justice_2009') ;
calEvents[15] = new eventInfo (2009, 3, 7, null, null, null, null, null, null, 930, 1700, 'Second Annual Gender and Justice Conference (Day 2)', 'http://www.aiusaoc.org/archives/common/past_announcements_2009.html#gender_and_justice_2009') ;
calEvents[16] = new eventInfo (2009, 4, 4, null, null, null, null, null, null, 930, 1300, 'Great American Write-In', 'http://www.aiusaoc.org/common/blog.html#great_american_writein') ;
calEvents[17] = new eventInfo (2009, 4, 4, null, null, null, null, null, null, 1000, null, '11th Annual Cesar Chavez Walk and Festival', 'http://www.aiusaoc.org/archives/common/past_announcements_2009.html#cesar_chavez_walk') ;
calEvents[18] = new eventInfo (2009, 3, 26, null, null, null, null, null, null, 1930, 2100, 'Irvine Group Meeting—Human Trafficking', 'http://www.aiusaoc.org/common/blog.html#oc_human_trafficking') ;
calEvents[19] = new eventInfo (2009, 3, 14, null, null, null, null, null, null, 1500, 1900, '4Darfur Awareness Concert', 'http://www.aiusaoc.org/common/blog.html#darfur_concert') ;
calEvents[20] = new eventInfo (2009, 3, 15, null, null, null, null, null, null, 1830, null, 'In Darfur (Play)', 'http://www.aiusaoc.org/archives/common/past_announcements_2009.html#in_darfur') ;
calEvents[21] = new eventInfo (2009, 3, 21, null, null, null, null, null, null, 1800, null, 'In Darfur (Play)', 'http://www.aiusaoc.org/archives/common/past_announcements_2009.html#in_darfur') ;
calEvents[22] = new eventInfo (2009, 4, 16, null, null, null, null, null, null, 1830, 2100, 'Ghosts of Rwanda (Film)', 'http://www.aiusaoc.org/common/blog.html#ghosts_of_rwanda') ;
calEvents[23] = new eventInfo (2009, 4, 19, null, null, null, null, null, null, 1800, null, 'In Darfur (Play)', 'http://www.aiusaoc.org/archives/common/past_announcements_2009.html#in_darfur') ;
calEvents[24] = new eventInfo (2009, 4, 30, null, null, null, null, null, null, 1830, null, 'In Darfur (Play)', 'http://www.aiusaoc.org/archives/common/past_announcements_2009.html#in_darfur') ;
calEvents[25] = new eventInfo (2009, 7, 18, null, null, null, null, null, null, 0700, 1100, 'Group 175 (Long Beach) Yard Sale', 'http://www.aiusaoc.org/common/blog.html#long_beach_yard_sale_2009') ;
calEvents[26] = new eventInfo (2009, 9, 19, null, null, null, null, null, null, 1900, 2100, 'Movie Showing: The Reckoning', 'http://www.aiusaoc.org/archives/common/past_announcements_2009.html#reckoning') ;
calEvents[27] = new eventInfo (2009, 9, 15, null, null, null, null, null, null, 1100, 1300, 'The War on Terror… and the Law', 'http://www.aiusaoc.org/archives/common/past_announcements_2009.html#dean_chemerinsky') ;
calEvents[28] = new eventInfo (2009, 10, 9, null, null, null, null, null, null, 1900, null, 'Shake Hands With The Devil: The Journey of Roméo Dallaire', 'http://www.aiusaoc.org/archives/common/past_announcements_2009.html#rwanda_movie_talk') ;
calEvents[29] = new eventInfo (2009, 10, 10, null, null, null, null, null, null, 1900, null, 'An Evening With Carl Wilkens', 'http://www.aiusaoc.org/archives/common/past_announcements_2009.html#rwanda_movie_talk') ;
calEvents[30] = new eventInfo (2009, 10, 17, null, null, null, null, null, null, 0900, 1300, 'Walk For Hope', 'http://www.aiusaoc.org/longbeach/walk_for_hope_2009.html') ;
calEvents[31] = new eventInfo (2009, 10, 29, null, null, null, null, null, null, 1930, null, 'Irvine Group Meeting—San Salvador Atenco', 'http://www.aiusaoc.org/irvine/atenco.html') ;
calEvents[32] = new eventInfo (2009, 11, 5, null, null, null, null, null, null, 0900, 1130, 'Murder in the Name of Honor', 'http://www.aiusaoc.org/archives/common/past_announcements_2009.html#rana_husseini') ;
calEvents[33] = new eventInfo (2009, 12,12, null, null, null, null, null, null, 1200, 1500, 'Write-at-thon, Long Beach (Zephyr Café)', 'http://www.aiusaoc.org/common/writeathon_2009.html#write_a_thon_long_beach_1212') ;
calEvents[34] = new eventInfo (2009, 12, 13, null, null, null, null, null, null, 0900, 1300, 'Write-at-thon, Long Beach (UU church)', 'http://www.aiusaoc.org/common/writeathon_2009.html#write_a_thon_long_beach_1213') ;
calEvents[35] = new eventInfo (2009, 12, 13, null, null, null, null, null, null, 0930, 1330, 'Write-at-thon, Irvine (IUCC)', 'http://www.aiusaoc.org/common/writeathon_2009.html#write_a_thon_irvine_1213') ;
calEvents[36] = new eventInfo (2010, 12, 11, null, null, null, null, null, null, 1200, 1500, 'Write-at-thon, Long Beach (Zephyr Café)', 'http://www.aiusaoc.org/writeathon.html#longbeach_writeathon') ;
calEvents[37] = new eventInfo (2010, 12, 12, null, null, null, null, null, null, 0930, 1300, 'Write-at-thon, Irvine (IUCC)', 'http://www.aiusaoc.org/writeathon.html#irvine_writeathon') ;
calEvents[38] = new eventInfo (2011, 12, 10, null, null, null, null, null, null, 1300, 1800, 'Write-at-thon, Long Beach (Zephyr Café)', 'http://www.aiusaoc.org/writeathon.html#longbeach_writeathon') ;
calEvents[39] = new eventInfo (2011, 12, 11, null, null, null, null, null, null, 1000, 1300, 'Write-at-thon, Irvine (IUCC)', 'http://www.aiusaoc.org/writeathon.html#irvine_writeathon') ;



// ++++++++++++++++++++++++++++++++++++
// Group Meeting Array
// group meeting: entries per group. For each group I list the calEvents table entries applicable
//                for regular group meetings. Other things would be considered "special events".
//                For each group, the list of group meeting calendar entries is an array, not just a number.
//                 Example: Right now Group 178 has two schedules, one for most of the year, one for November/December.
var grpList = [141, 'Orange', [0], 175, 'Long Beach', [1], 178, 'Irvine', [2, 3]] ;

// ++++++++++++++++++++++++++++++++++++
// group meeting customization

var grpMeetSpecial = new Array() ;


//  in this list I have "overrides" for group meetings. It is NOT to be used for a case where
//   your group is having its usual monthly meeting and another event in the same month.
//  case 1: your group cancels one monthly meeting. You don't want to have to modify the official
//          schedule. You can cancel it here.
//  case 2: your group has a special event at a monthly meeting. You want to put in a special
//          description (and maybe a special URL) for that one meeting. You specify that here.
//  Data items are: group number, year-and-month (YYYYMM), index to calEvent entry, regular-schedule
//   - index to calEvent entry is null to say "group meeting is cancelled for that month
//   - "regular-schedule" data item is either true or false and indicates if the special event is
//      happening at the group's regular meeting time
// Example 1: Group 178 has a special event in January 2009. The special event takes place at the
//          regular meeting date/time. The special event was entered in calEvents array entry 4.
//  grpMeetSpecial new specialInfo (178, 200901, 4, true) ;
// Example 2: Group 141 cancels its July 2009 meeting because the group leader is going to Hawaii.
//  grpMeetSpecial new specialInfo (141, 200907, null, null) ;
//
// "line numbers" (array indexes) should be increasing sequentially with no duplicates

grpMeetSpecial[0] = new specialInfo (178, 200903, 18, true) ;
grpMeetSpecial[1] = new specialInfo (175, 200902, 9, true) ; 
grpMeetSpecial[2] = new specialInfo (178, 200901, 4, true) ;
grpMeetSpecial[3] = new specialInfo (178, 200907, null, null)
grpMeetSpecial[4] = new specialInfo (141, 200907, null, null)
grpMeetSpecial[5] = new specialInfo (178, 200910, 31, true)
grpMeetSpecial[6] = new specialInfo (178, 201005, null, null)
grpMeetSpecial[7] = new specialInfo (178, 201107, null, null)


// -----------------------------------------------------------------------------------------------
// End user-customizable section
// -----------------------------------------------------------------------------------------------


var daysOfWeek = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'] ;
var monthNames = ['January','February','March','April','May','June','July','August','September','October','November','December'] ;

var evtListStartDate = new Date () ;
var maxScroll = 12 ;

function eventInfo ( evtYear, evtMonth, evtDay, evtdDayofweek, evtWeekofmonth, evtStartYear, evtStartMonth, evtEndYear, evtEndMonth, evtStartTime, evtEndTime, evtDescription, evtUrl )
{  
    this.evtYear = evtYear ;  
    this.evtMonth = evtMonth ;  
    this.evtDay = evtDay ;  
    this.evtdDayofweek = evtdDayofweek ;  
    this.evtWeekofmonth = evtWeekofmonth ;  
    this.evtStartYear = evtStartYear ;  
    this.evtStartMonth = evtStartMonth ;  
    this.evtEndYear = evtEndYear ;  
    this.evtEndMonth = evtEndMonth ;  
    this.evtStartTime = evtStartTime ;  
    this.evtEndTime = evtEndTime ;  
    this.evtDescription = evtDescription ;  
    this.evtUrl = evtUrl ;  
} 

function specialInfo ( groupNo, yrMon, evtIdx, regularTime )
{  
    this.groupNo = groupNo ;  
    this.yrMon = yrMon ;  
    this.evtIdx = evtIdx ;  
    this.regularTime = regularTime ;  
} 

function nthWeekdayOfYM (N, weekday, yr, mon) 
{
  var D ;
  var i = 0 ;
  do
  {
     D = new Date(yr, mon - 1, 1) ;
     D.setDate (7 * (N - i) - 6 + (7 + weekday - D.getDay()) % 7);
     i++ ;
  } while (D.getMonth() == mon && N > i) ;
  return D ;
}

function getEventDayOfMonth (yr, mon, evtIdx)
{
   if (calEvents[evtIdx].evtDay !== null)
      evtDayOfMonth = calEvents[evtIdx].evtDay ;
   else
      evtDayOfMonth = nthWeekdayOfYM (calEvents[evtIdx].evtWeekofmonth, calEvents[evtIdx].evtdDayofweek, yr, mon).getDate() ;
   return evtDayOfMonth ;
}

function getEventText (yr, mon, dd, evtIdx, showYM)
{
   var evtDate = new Date (yr, mon - 1, dd) ;
   return daysOfWeek[evtDate.getDay()] + (showYM ? ', ' : ' ') + dd + (showYM ? ' ' + monthNames[mon - 1] + ' ' + yr : '') + ': ' + ( (calEvents[evtIdx].evtUrl == null) ? calEvents[evtIdx].evtDescription : '<a href="' + calEvents[evtIdx].evtUrl + '">' + calEvents[evtIdx].evtDescription + '</a>');
}

function specialGroupMeeting (yr, mon, evtIdx)
{
   var specialMeetingFound = false ;
   var newIdx = evtIdx ;
   var i ;
   var j ;
   var k ;

   for (i = 0 ; ! specialMeetingFound && i < grpList.length ; i += 3)
      for (j = 0 ; ! specialMeetingFound && j < grpList[i + 2].length ; j++)
         if (evtIdx == grpList[i + 2][j])
           for (k = 0 ; k < grpMeetSpecial.length ; k++)
              if (grpMeetSpecial[k].groupNo == grpList[i] && grpMeetSpecial[k].yrMon == yr * 100 + mon)
              {
                 specialMeetingFound = true ;
                 newIdx = grpMeetSpecial[k].evtIdx ;
              }
   return newIdx ;
}

function isEventYM (yr, mon, evtIdx)
{
   var evtExists = (calEvents[evtIdx].evtYear == yr || (calEvents[evtIdx].evtYear == null && (calEvents[evtIdx].evtStartYear <= yr || calEvents[evtIdx].evtStartYear == null) && (yr <= calEvents[evtIdx].evtEndYear || calEvents[evtIdx].evtEndYear == null))) && (calEvents[evtIdx].evtMonth == mon || (calEvents[evtIdx].evtMonth == null && calEvents[evtIdx].evtStartMonth <= mon && mon <= calEvents[evtIdx].evtEndMonth)) ;
   if (evtExists)
     evtExists = specialGroupMeeting (yr, mon, evtIdx) !== null ;
   return evtExists ;
}

function isEvent (yr, mon, dd)
{
   var i = 0 ;
   var eventDay = false ;
   var evtIdx ;
   
   while (! eventDay && i < calEvents.length)
   {
      eventDay = (isEventYM (yr, mon, i) && dd == getEventDayOfMonth (yr, mon, i)) ;
      if (eventDay)
         evtIdx = specialGroupMeeting (yr, mon, evtIdx) ;
      i++ ;
   }
   if (eventDay)
      eventDay = (evtIdx !== null) ;
   return eventDay ;
}

function getToday ()
{
   var todayte = new Date() ;
   
   return daysOfWeek [todayte.getDay()] + ', ' + todayte.getDate() + ' ' + monthNames [todayte.getMonth()] + ' ' + todayte.getFullYear() ;
}

function setEvtListStartDate (increment)
{
   var yr = evtListStartDate.getFullYear() ;
   var mm = evtListStartDate.getMonth() ;
   
   if (increment == 0)
      evtListStartDate = new Date() ;
   else
   {
      mm += increment ;
      if (mm == -1)
      {
         yr-- ;
         mm = 11 ;
      }
      else if (mm == 12)
      {
        yr++ ;
        mm = 0 ;
      }
      evtListStartDate = new Date (yr, mm, 1) ;
   }
   
}

function getEvtListStartDate ()
{
   return evtListStartDate ;
}

function getMonthlyEvents (yr, mon, showYM)
{
   var dispEvents = new Array() ;
   var evtCount = 0 ;
   var evtList = '' ;
   var i ;
   var evtIdx ;

    for (i = 0 ; i < calEvents.length ; i++)
       if (isEventYM (yr, mon, i))
       {
          evtIdx = specialGroupMeeting (yr, mon, i) ;
          if (evtIdx == i)
          {
             dispEvents[evtCount] = [evtIdx, getEventDayOfMonth (yr, mon, i)] ;
             evtCount++ ;
          }
       }
    dispEvents.sort (new Function("x","y","return x[1] - y[1] ;")) ;
    for (i = 0 ; i < evtCount ; i++)
       evtList += '<p class="details" style="text-align:center;">' + getEventText (yr, mon, dispEvents[i][1], dispEvents[i][0], showYM) + '</p>' ;
    return evtList ;
}

function getNextGroupMeeting (yr, mon, dd, groupNo)
{
   var groupMeetingdd = dd ;
   var groupMeetingMon = mon ;
   var groupMeetingYr = yr ;
   var groupMeetingEvtIdx ;
   var meetingFound = false ;
   var continueSearch = true ;
   var meetingText ;
   
   for (i = 0 ; i < grpList.length ; i += 3)
      if (grpList[i] == groupNo)
         while (continueSearch)
         {
            for (j = 0 ; j < grpList[i + 2].length ; j++)
               if (isEventYM (groupMeetingYr, groupMeetingMon, grpList[i + 2][j]))
               {
                  groupMeetingEvtIdx = specialGroupMeeting (groupMeetingYr, groupMeetingMon, grpList[i + 2][j]) ;
                  if (groupMeetingEvtIdx !== null)
                  {
                     groupMeetingdd = getEventDayOfMonth (groupMeetingYr, groupMeetingMon, groupMeetingEvtIdx) ;
                     meetingFound = (groupMeetingYr * 10000 + groupMeetingMon * 100 + groupMeetingdd >= yr * 10000 + mon * 100 + dd) ;
                     continueSearch = false ;
                  }
               }
            if (! meetingFound)
            {
               continueSearch = true ;
               groupMeetingMon++ ;
               if (groupMeetingMon > 12)
               {
                  groupMeetingMon = 1 ;
                  groupMeetingYr++ ;
                  if (groupMeetingYr > yr + 1)
                     continueSearch = false ;
               }
            }
         }
   if (meetingFound)
   {
      var meetingDay = new Date (groupMeetingYr, groupMeetingMon - 1, groupMeetingdd) ;
      var hh24 = (calEvents[groupMeetingEvtIdx].evtStartTime - calEvents[groupMeetingEvtIdx].evtStartTime % 100) / 100 ;
      var mi = calEvents[groupMeetingEvtIdx].evtStartTime - hh24 * 100 ;
      var hh = hh24 >= 13 ? hh24 - 12 : hh24 ;
      var meetingTime = hh24 + ':' + (mi < 10 ? '0' + mi : mi) ;
      if (hh24 >= 13)
         meetingTime += ' (' + hh + ':' + (mi < 10 ? '0' + mi : mi) + ' PM)' ;
      meetingText = '<p>Our next meeting will be at:</p><p>Date: ' + daysOfWeek[meetingDay.getDay()] + ', ' + groupMeetingdd + ' ' + monthNames[groupMeetingMon - 1] + ' ' + groupMeetingYr + '</p><p>Time: ' + meetingTime + '</p>' ;
   }
   else
      meetingText = '<p>Please contact us to inquire about the date of our next meeting.</p>' ;
      
   return meetingText ;
}


function getGroupMeetings (yr, mon)
{
   var groupMeetings = new Array() ;
   var meetCount = 0 ;
   var meetList = '' ;

   for (i = 0 ; i < grpList.length ; i += 3)
      for (j = 0 ; j < grpList[i + 2].length ; j++)
         if (isEventYM (yr, mon, grpList[i + 2][j]))
            if (specialGroupMeeting (yr, mon, grpList[i + 2][j]) !== null)
            {
               groupMeetings[meetCount] = [grpList[i + 2][j], getEventDayOfMonth (yr, mon, grpList[i + 2][j]), grpList[i], grpList[i + 1]] ;
               meetCount++ ;
            }

   groupMeetings.sort (new Function("x","y","return x[1] - y[1] ;")) ;
   for (i = 0 ; i < meetCount ; i++)
      meetList += '<p>' + getEventText (yr, mon, groupMeetings[i][1], groupMeetings[i][0], true).replace (/(.{3})([^\d]*)(\d+ .{3})([^:]*: )(<[^>]*>)?([^<]*)(<[^>]*>)?/, '<strong>$1 $3</strong> $5Group ' + groupMeetings[i][2] + ' ' + groupMeetings[i][3] + '$7') + '</p>' ;
   if (meetCount == 0)
      meetList = '<p>No group meetings in ' + monthNames [mon - 1] + ' ' + yr + '.</p>' ;
   
   return meetList ;
}

function calMove (calIncrement)
{
   // forwards and backward buttons only let you go up to 12 months away
   var evtListDate = getEvtListStartDate () ;
   var todayte = new Date() ;

   if (((evtListDate.getFullYear() * 12 + evtListDate.getMonth()) - (todayte.getFullYear() * 12 + todayte.getMonth())) * calIncrement < maxScroll)
      setEvtListStartDate (calIncrement) ;
}

function eventCal (yr, mon, backFunc, nowFunc, forwardFunc)
{
   var daysPerMon = [31,0,31,30,31,30,31,31,30,31,30,31] ;
   var i ;
   var j ;
   var k ;
   var calTab ;

   var mm = mon - 1 ;
   var firstDay = new Date (yr, mm, 1) ;
   // this calendar starts with Sunday
   var firstDayWeekday = firstDay.getDay() + 1 ;

   var todayte = new Date() ;
   var todayteDD = (yr == todayte.getFullYear() && mm == todayte.getMonth()) ? todayte.getDate() : 0 ;

   daysPerMon [1] = (((firstDay.getFullYear() %100 != 0) && (firstDay.getFullYear() % 4 == 0)) || (firstDay.getFullYear() % 400 == 0)) ? 29 : 28 ;
   
   calTab = '<p class="details">' ;
   if ((todayte.getFullYear() * 12 + todayte.getMonth()) - (yr * 12 + mm) < maxScroll)
      calTab += '<a href="#" onclick="' + backFunc + ' (); return false;">&larr;prev</a>' ;
   else
      calTab += '&larr;prev' ;
   calTab += '&nbsp;&nbsp;' ;
   if (todayte.getFullYear() != yr || todayte.getMonth() != mm)
      calTab += '<a href="#" onclick="' + nowFunc + ' (); return false;">Current Month</a>' ;
   else
      calTab += 'Current Month' ;
   calTab += '&nbsp;&nbsp;' ;
   if ((yr * 12 + mm) - (todayte.getFullYear() * 12 + todayte.getMonth()) < maxScroll)
      calTab += '<a href="#" onclick="' + forwardFunc + ' (); return false;">next&rarr;</a>' ;
   else
      calTab += 'next&rarr;' ;
   calTab += '</p>' ;

   calTab += '<table cols="7" style="width:auto; margin-left:auto; margin-right:auto; margin-bottom:2.0em"><tr>' ;
   calTab += '<td colspan="7" style="text-align:center; height:auto"><strong>' + monthNames [mm] + ' ' + yr + '</strong></td></tr><tr>' ;
   for (s = 0 ; s < 7; s++)
      calTab += '<th class="top" scope="col" style="text-align:center; height:auto">' + daysOfWeek[s].substr(0,3) + '</th>' ;
   calTab += '</tr>' ;
   k = 0 ;
   for (i = 1 ; i <= 6 ; i++)
   {
     var dayFound = false ;
     var caltd = '' ;
     for (j = 1 ; j <= 7 ; j++)
     {
        k++ ;
        var calDay = ((k >= firstDayWeekday) && (k - firstDayWeekday < daysPerMon [mm])) ;
        var thisDay = k - firstDayWeekday + 1 ;
        dayFound = dayFound || calDay ;
        if (calDay && isEvent (yr, mon, thisDay))
          caltd += '<th class="top" scope="col" style="text-align:center; height:auto">' + thisDay + '</th>' ;
        else if (calDay && thisDay == todayteDD)
           caltd += '<td style="text-align:center; height:auto"><strong>' + thisDay  + '</strong></td>' ;       
        else if (j == 1 || j == 7)
           caltd += '<td class="dark" style="text-align:center; height:auto">' + ((calDay) ? thisDay : '&nbsp;')  + '</td>' ;
        else if (calDay)
           caltd += '<td style="text-align:center; height:auto">' + thisDay + '</td>' ;
        else
           caltd += '<td style="height:auto">&nbsp;</td>' ;
     }
     if (dayFound)
       calTab += '<tr>' + caltd + '</tr>' ;
   }
   return calTab += '</table>' ;
}

