/* This file contains a set of functions used with the date/time selector.
 */

/********************************************************************
 * remember_duration
 *
 * Remembers the current duration (difference between start and end time)
 * and stores it in the window.duration global.  This lets us preserve
 * the duration through start time changes.
 ********************************************************************/
function remember_duration() {
  startD = get_date('start');
  endD = get_date('end');
  window.duration = endD - startD;
}

/********************************************************************
 * end_from_duration
 *
 * Sets the end date from the (presumably new) start date and
 * duration.
 ********************************************************************/
function end_from_duration() {
  startD = get_date('start');
  endD = startD;
  endD.setMilliseconds(endD.getMilliseconds() + window.duration);
  set_date('end',endD);
}

/********************************************************************
 * start_month_filter
 *
 * A keystroke filter which limits input to numbers with a proper 
 * range of values.
 ********************************************************************/
function start_month_filter(evt) {

  /* Get the field object so we can select it later.
   */
  field = document.getElementById('start_month');

  /* Get the key that was pressed.
   */
  evt = (evt) ? evt : event;
  charCode = (evt.charCode) ? evt.charCode : 
    ((evt.keyCode) ? evt.keyCode : ((evt.which) ? event.which : 0));

  /* Remember the offset between start and end date.
   */
  startD = get_date('start');
  endD = get_date('end');
  offset = endD - startD;

  /* Up arrow or '+' or '=' increments.
   */
  if(charCode == 38 || charCode == 63232 || charCode == 43 || charCode == 61) {
    startD.setMonth(startD.getMonth() + 1);
    set_date('start',startD);
    endD = startD;
    endD.setMilliseconds(endD.getMilliseconds() + offset);
    set_date('end',endD);
    field.select();
    return false;
  }
  
  /* Down arrow or '-' decrements.
   */
  else if(charCode == 40 || charCode == 63233 || charCode == 45) {
    startD.setMonth(startD.getMonth() - 1);
    set_date('start',startD);
    endD = startD;
    endD.setMilliseconds(endD.getMilliseconds() + offset);
    set_date('end',endD);
    field.select();
    return false;
  }

  /* Numbers are allowed.
   */
  if(charCode >= 48 && charCode <= 57) {
    return true;
  }

  /* Letters, symbols and space are blocked.
   */
  if((charCode>=32 && charCode<=47) ||
      (charCode>=58 && charCode<=126)) {
    return false;
  }

  /* Any other character we allow (this lets tabs, return, enter, etc.
   * continue to work correctly.
   */
  return true;
}

/********************************************************************
 * start_month_postfix
 *
 * Called at the "blur" event.  Checks the month, making it two digits.
 ********************************************************************/
function start_month_postfix() {

  /* Get the field object.
   */
  field = document.getElementById('start_month');

  /* If it has more than 2 digits, truncate it.
   */
  if(field.value.length > 2) {
    field.value = field.value.substr(field.value.length - 2,2);
  }

  /* Value must 1 or greater.
   */
  if(Number(field.value) < 1) {
    field.value = 1;
  }

  /* Value must not be over 12.
   */
  if(Number(field.value) > 12) {
    field.value = 12;
  }

  /* Force the number to be 2 digits.
   */
  if(field.value.length < 2) {
    field.value = '0' + field.value;
  }

  /* Set the end time using the duration.
   */
  end_from_duration();

  /* If there is a user callback, call it.
   */
  if(window.timeChangeCallback) {
    window.timeChangeCallback();
  }
}

/********************************************************************
 * start_day_filter
 *
 * A keystroke filter which limits input to numbers with a proper 
 * range of values.
 ********************************************************************/
function start_day_filter(evt) {

  /* Get the field object so we can select it later.
   */
  field = document.getElementById('start_day');

  /* Get the key that was pressed.
   */
  evt = (evt) ? evt : event;
  charCode = (evt.charCode) ? evt.charCode : 
    ((evt.keyCode) ? evt.keyCode : ((evt.which) ? event.which : 0));

  /* Remember the offset between start and end date.
   */
  startD = get_date('start');
  endD = get_date('end');
  offset = endD - startD;

  /* Up arrow or '+' or '=' increments.
   */
  if(charCode == 38 || charCode == 63232 || charCode == 43 || charCode == 61) {
    startD.setDate(startD.getDate() + 1);
    set_date('start',startD);
    endD = startD;
    endD.setMilliseconds(endD.getMilliseconds() + offset);
    set_date('end',endD);
    field.select();
    return false;
  }
  
  /* Down arrow or '-' decrements.
   */
  else if(charCode == 40 || charCode == 63233 || charCode == 45) {
    startD.setDate(startD.getDate() - 1);
    set_date('start',startD);
    endD = startD;
    endD.setMilliseconds(endD.getMilliseconds() + offset);
    set_date('end',endD);
    field.select();
    return false;
  }

  /* Numbers are allowed.
   */
  if(charCode >= 48 && charCode <= 57) {
    return true;
  }

  /* Letters, symbols and space are blocked.
   */
  if((charCode>=32 && charCode<=47) ||
      (charCode>=58 && charCode<=126)) {
    return false;
  }

  /* Any other character we allow (this lets tabs, return, enter, etc.
   * continue to work correctly.
   */
  return true;
}

/********************************************************************
 * start_day_postfix
 *
 * Called at the "blur" event.  Checks the day, making it two digits.
 ********************************************************************/
function start_day_postfix() {

  /* Get the field object.
   */
  field = document.getElementById('start_day');

  /* If it has more than 2 digits, truncate it.
   */
  if(field.value.length > 2) {
    field.value = field.value.substr(field.value.length - 2,2);
  }

  /* Value must 1 or greater.
   */
  if(Number(field.value) < 1) {
    field.value = 1;
  }

  /* Value must not be greater than the number of days in this month.
   */
  month = document.getElementById('start_month').value;
  year = document.getElementById('start_year').value;
  var first_day = new Date(year, month-1, 1, 0, 0, 0);
  var month_days = days_in_month(first_day);
  if(Number(field.value) > month_days) {
    field.value = month_days;
  }

  /* Force the number to be 2 digits.
   */
  if(field.value.length < 2) {
    field.value = '0' + field.value;
  }

  /* Set the end time using the duration.
   */
  end_from_duration();

  /* If there is a user callback, call it.
   */
  if(window.timeChangeCallback) {
    window.timeChangeCallback();
  }
}

/********************************************************************
 * start_year_filter
 *
 * A keystroke filter which limits input to numbers with a proper 
 * range of values.
 ********************************************************************/
function start_year_filter(evt) {

  /* Get the field object so we can select it later.
   */
  field = document.getElementById('start_year');

  /* Get the key that was pressed.
   */
  evt = (evt) ? evt : event;
  charCode = (evt.charCode) ? evt.charCode : 
    ((evt.keyCode) ? evt.keyCode : ((evt.which) ? event.which : 0));

  /* Remember the offset between start and end date.
   */
  startD = get_date('start');
  endD = get_date('end');
  offset = endD - startD;

  /* Up arrow or '+' or '=' increments.
   */
  if(charCode == 38 || charCode == 63232 || charCode == 43 || charCode == 61) {
    startD.setYear(startD.getFullYear() + 1);
    set_date('start',startD);
    endD = startD;
    endD.setMilliseconds(endD.getMilliseconds() + offset);
    set_date('end',endD);
    field.select();
    return false;
  }
  
  /* Down arrow or '-' decrements.
   */
  else if(charCode == 40 || charCode == 63233 || charCode == 45) {
    startD.setYear(startD.getFullYear() - 1);
    set_date('start',startD);
    endD = startD;
    endD.setMilliseconds(endD.getMilliseconds() + offset);
    set_date('end',endD);
    field.select();
    return false;
  }

  /* Numbers are allowed.
   */
  if(charCode >= 48 && charCode <= 57) {
    return true;
  }

  /* Letters, symbols and space are blocked.
   */
  if((charCode>=32 && charCode<=47) ||
      (charCode>=58 && charCode<=126)) {
    return false;
  }

  /* Any other character we allow (this lets tabs, return, enter, etc.
   * continue to work correctly.
   */
  return true;
}

/********************************************************************
 * start_year_postfix
 *
 * Called at the "blur" event.  Checks the year, making it a four-
 * digit year.
 ********************************************************************/
function start_year_postfix() {

  /* Get the field object.
   */
  field = document.getElementById('start_year');

  /* If it has more than 4 digits, truncate it.
   */
  if(field.value.length > 4) {
    field.value = field.value.substr(field.value.length - 4,4);
  }

  /* Numbers between 0 and 50 become 20XX.  Numbers 50 and over
   * become 19XX.  Numbers over 100 become 2XXX.
   */
  if(Number(field.value) < 50) {
    field.value = Number(field.value) + 2000;
  } else if(Number(field.value) < 100) {
    field.value = Number(field.value) + 1900;
  } else if(Number(field.value) < 1000) {
    field.value = Number(field.value) + 2000;
  }

  /* Set the end time using the duration.
   */
  end_from_duration();

  /* If there is a user callback, call it.
   */
  if(window.timeChangeCallback) {
    window.timeChangeCallback();
  }
}

/********************************************************************
 * start_hour_filter
 *
 * A keystroke filter which limits input to numbers with a proper 
 * range of values.
 ********************************************************************/
function start_hour_filter(evt) {

  /* Get the field object so we can select it later.
   */
  field = document.getElementById('start_hour');

  /* Get the key that was pressed.
   */
  evt = (evt) ? evt : event;
  charCode = (evt.charCode) ? evt.charCode : 
    ((evt.keyCode) ? evt.keyCode : ((evt.which) ? event.which : 0));

  /* Remember the offset between start and end date.
   */
  startD = get_date('start');
  endD = get_date('end');
  offset = endD - startD;

  /* Up arrow or '+' or '=' increments.
   */
  if(charCode == 38 || charCode == 63232 || charCode == 43 || charCode == 61) {
    startD.setHours(startD.getHours() + 1);
    set_date('start',startD);
    endD = startD;
    endD.setMilliseconds(endD.getMilliseconds() + offset);
    set_date('end',endD);
    field.select();
    return false;
  }
  
  /* Down arrow or '-' decrements.
   */
  else if(charCode == 40 || charCode == 63233 || charCode == 45) {
    startD.setHours(startD.getHours() - 1);
    set_date('start',startD);
    endD = startD;
    endD.setMilliseconds(endD.getMilliseconds() + offset);
    set_date('end',endD);
    field.select();
    return false;
  }

  /* Numbers are allowed.
   */
  if(charCode >= 48 && charCode <= 57) {
    return true;
  }

  /* Letters, symbols and space are blocked.
   */
  if((charCode>=32 && charCode<=47) ||
      (charCode>=58 && charCode<=126)) {
    return false;
  }

  /* Any other character we allow (this lets tabs, return, enter, etc.
   * continue to work correctly.
   */
  return true;
}

/********************************************************************
 * start_hour_postfix
 *
 * Called at the "blur" event.  Checks the hour, making it two digits.
 ********************************************************************/
function start_hour_postfix() {

  /* Get the field object.
   */
  field = document.getElementById('start_hour');

  /* If it has more than 2 digits, truncate it.
   */
  if(field.value.length > 2) {
    field.value = field.value.substr(field.value.length - 2,2);
  }

  /* Value must not be negative.
   */
  if(Number(field.value) < 0) {
    field.value = 0;
  }

  /* Value must not be over 23.
   */
  if(Number(field.value) > 23) {
    field.value = 12;
  }

  /* If the value is zero, it's a 24 hour clock and it means
   * am.
   */
  if(Number(field.value == 0)) {
    ampm = document.getElementById('start_ampm');
    ampm.selectedIndex = 0;
    field.value = 12;
  }

  /* If value is greater than 12, it's a 24 hour clock, so we
   * set the am/pm status accordingly.
   */
  if(Number(field.value > 12)) {
    ampm = document.getElementById('start_ampm');
    ampm.selectedIndex = 1;
    field.value = Number(field.value) - 12;
  }

  /* Force the number to be 2 digits.
   */
  if(field.value.length < 2) {
    field.value = '0' + field.value;
  }

  /* Set the end time using the duration.
   */
  end_from_duration();

  /* If there is a user callback, call it.
   */
  if(window.timeChangeCallback) {
    window.timeChangeCallback();
  }
}

/********************************************************************
 * start_minute_filter
 *
 * A keystroke filter which limits input to numbers with a proper 
 * range of values.
 ********************************************************************/
function start_minute_filter(evt) {

  /* Get the field object so we can select it later.
   */
  field = document.getElementById('start_minute');

  /* Get the key that was pressed.
   */
  evt = (evt) ? evt : event;
  charCode = (evt.charCode) ? evt.charCode : 
    ((evt.keyCode) ? evt.keyCode : ((evt.which) ? event.which : 0));

  /* Remember the offset between start and end date.
   */
  startD = get_date('start');
  endD = get_date('end');
  offset = endD - startD;

  /* Up arrow or '+' or '=' increments.
   */
  if(charCode == 38 || charCode == 63232 || charCode == 43 || charCode == 61) {
    startD.setMinutes(startD.getMinutes() + 1);
    set_date('start',startD);
    endD = startD;
    endD.setMilliseconds(endD.getMilliseconds() + offset);
    set_date('end',endD);
    field.select();
    return false;
  }
  
  /* Down arrow or '-' decrements.
   */
  else if(charCode == 40 || charCode == 63233 || charCode == 45) {
    startD.setMinutes(startD.getMinutes() - 1);
    set_date('start',startD);
    endD = startD;
    endD.setMilliseconds(endD.getMilliseconds() + offset);
    set_date('end',endD);
    field.select();
    return false;
  }

  /* Numbers are allowed.
   */
  if(charCode >= 48 && charCode <= 57) {
    return true;
  }

  /* Letters, symbols and space are blocked.
   */
  if((charCode>=32 && charCode<=47) ||
      (charCode>=58 && charCode<=126)) {
    return false;
  }

  /* Any other character we allow (this lets tabs, return, enter, etc.
   * continue to work correctly.
   */
  return true;
}

/********************************************************************
 * start_minute_postfix
 *
 * Called at the "blur" event.  Checks the minute, making it two digits.
 ********************************************************************/
function start_minute_postfix() {

  /* Get the field object.
   */
  field = document.getElementById('start_minute');

  /* If it has more than 2 digits, truncate it.
   */
  if(field.value.length > 2) {
    field.value = field.value.substr(field.value.length - 2,2);
  }

  /* Value must not be negative.
   */
  if(Number(field.value) < 0) {
    field.value = 0;
  }

  /* Value must not be over 59.
   */
  if(Number(field.value) > 59) {
    field.value = 59;
  }

  /* Force the number to be 2 digits.
   */
  if(field.value.length < 2) {
    field.value = '0' + field.value;
  }

  /* Set the end time using the duration.
   */
  end_from_duration();

  /* If there is a user callback, call it.
   */
  if(window.timeChangeCallback) {
    window.timeChangeCallback();
  }
}

/********************************************************************
 * end_month_filter
 *
 * A keystroke filter which limits input to numbers with a proper 
 * range of values.
 ********************************************************************/
function end_month_filter(evt) {

  /* Get the field object so we can select it later.
   */
  field = document.getElementById('end_month');

  /* Get the key that was pressed.
   */
  evt = (evt) ? evt : event;
  charCode = (evt.charCode) ? evt.charCode : 
    ((evt.keyCode) ? evt.keyCode : ((evt.which) ? event.which : 0));

  /* Get the current start and end dates.
   */
  startD = get_date('start');
  endD = get_date('end');

  /* Up arrow or '+' or '=' increments.
   */
  if(charCode == 38 || charCode == 63232 || charCode == 43 || charCode == 61) {
    endD.setMonth(endD.getMonth() + 1);
    if(endD >= startD) {
      set_date('end',endD);
    }
    field.select();
    return false;
  }
  
  /* Down arrow or '-' decrements.
   */
  else if(charCode == 40 || charCode == 63233 || charCode == 45) {
    endD.setMonth(endD.getMonth() - 1);
    if(endD >= startD) {
      set_date('end',endD);
    }
    field.select();
    return false;
  }

  /* Numbers are allowed.
   */
  if(charCode >= 48 && charCode <= 57) {
    return true;
  }

  /* Letters, symbols and space are blocked.
   */
  if((charCode>=32 && charCode<=47) ||
      (charCode>=58 && charCode<=126)) {
    return false;
  }

  /* Any other character we allow (this lets tabs, return, enter, etc.
   * continue to work correctly.
   */
  return true;
}

/********************************************************************
 * end_month_postfix
 *
 * Called at the "blur" event.  Checks the month, making it two digits.
 ********************************************************************/
function end_month_postfix() {

  /* Get the field object.
   */
  field = document.getElementById('end_month');

  /* If it has more than 2 digits, truncate it.
   */
  if(field.value.length > 2) {
    field.value = field.value.substr(field.value.length - 2,2);
  }

  /* Value must 1 or greater.
   */
  if(Number(field.value) < 1) {
    field.value = 1;
  }

  /* Value must not be over 12.
   */
  if(Number(field.value) > 12) {
    field.value = 12;
  }

  /* If the end date is before the start date, set it back.
   */
  startD = get_date('start');
  endD = get_date('end');
  if(endD < startD) {
    var allday = document.getElementById('all_day');
    if(allday.checked) {
      endD.setHours(startD.getHours()+1);
      endD.setMinutes(0);
    }
    endD.setMonth(startD.getMonth());
    if(endD < startD) {
      endD.setMonth(startD.getMonth()+1);
    }
    set_date('end',endD);
  }

  /* Force the number to be 2 digits.
   */
  if(field.value.length < 2) {
    field.value = '0' + field.value;
  }

  /* If there is a user callback, call it.
   */
  if(window.timeChangeCallback) {
    window.timeChangeCallback();
  }
}

/********************************************************************
 * end_day_filter
 *
 * A keystroke filter which limits input to numbers with a proper 
 * range of values.
 ********************************************************************/
function end_day_filter(evt) {

  /* Get the field object so we can select it later.
   */
  field = document.getElementById('end_day');

  /* Get the key that was pressed.
   */
  evt = (evt) ? evt : event;
  charCode = (evt.charCode) ? evt.charCode : 
    ((evt.keyCode) ? evt.keyCode : ((evt.which) ? event.which : 0));

  /* Get the current start and end dates.
   */
  startD = get_date('start');
  endD = get_date('end');

  /* Up arrow or '+' or '=' increments.
   */
  if(charCode == 38 || charCode == 63232 || charCode == 43 || charCode == 61) {
    endD.setDate(endD.getDate() + 1);
    if(endD >= startD) {
      set_date('end',endD);
    }
    field.select();
    return false;
  }
  
  /* Down arrow or '-' decrements.
   */
  else if(charCode == 40 || charCode == 63233 || charCode == 45) {
    endD.setDate(endD.getDate() - 1);
    if(endD >= startD) {
      set_date('end',endD);
    }
    field.select();
    return false;
  }

  /* Numbers are allowed.
   */
  if(charCode >= 48 && charCode <= 57) {
    return true;
  }

  /* Letters, symbols and space are blocked.
   */
  if((charCode>=32 && charCode<=47) ||
      (charCode>=58 && charCode<=126)) {
    return false;
  }

  /* Any other character we allow (this lets tabs, return, enter, etc.
   * continue to work correctly.
   */
  return true;
}

/********************************************************************
 * end_day_postfix
 *
 * Called at the "blur" event.  Checks the day, making it two digits.
 ********************************************************************/
function end_day_postfix() {

  /* Get the field object.
   */
  field = document.getElementById('end_day');

  /* If it has more than 2 digits, truncate it.
   */
  if(field.value.length > 2) {
    field.value = field.value.substr(field.value.length - 2,2);
  }

  /* Value must 1 or greater.
   */
  if(Number(field.value) < 1) {
    field.value = 1;
  }

  /* Value must not be greater than the number of days in this month.
   */
  month = document.getElementById('end_month').value;
  year = document.getElementById('end_year').value;
  var first_day = new Date(year, month-1, 1, 0, 0, 0);
  var month_days = days_in_month(first_day);
  if(Number(field.value) > month_days) {
    field.value = month_days;
  }

  /* If the end date is before the start date, set it back.
   */
  startD = get_date('start');
  endD = get_date('end');
  if(endD < startD) {
    var allday = document.getElementById('all_day');
    if(allday.checked) {
      endD.setHours(startD.getHours()+1);
      endD.setMinutes(0);
    }
    endD.setDate(startD.getDate());
    if(endD < startD) {
      endD.setDate(startD.getDate()+1);
    }
    set_date('end',endD);
  }

  /* Force the number to be 2 digits.
   */
  if(field.value.length < 2) {
    field.value = '0' + field.value;
  }

  /* If there is a user callback, call it.
   */
  if(window.timeChangeCallback) {
    window.timeChangeCallback();
  }
}

/********************************************************************
 * end_year_filter
 *
 * A keystroke filter which limits input to numbers with a proper 
 * range of values.
 ********************************************************************/
function end_year_filter(evt) {

  /* Get the field object so we can select it later.
   */
  field = document.getElementById('end_year');

  /* Get the key that was pressed.
   */
  evt = (evt) ? evt : event;
  charCode = (evt.charCode) ? evt.charCode : 
    ((evt.keyCode) ? evt.keyCode : ((evt.which) ? event.which : 0));

  /* Get the current start and end dates.
   */
  startD = get_date('start');
  endD = get_date('end');

  /* Up arrow or '+' or '=' increments.
   */
  if(charCode == 38 || charCode == 63232 || charCode == 43 || charCode == 61) {
    endD.setYear(endD.getFullYear() + 1);
    if(endD >= startD) {
      set_date('end',endD);
    }
    field.select();
    return false;
  }
  
  /* Down arrow or '-' decrements.
   */
  else if(charCode == 40 || charCode == 63233 || charCode == 45) {
    endD.setYear(endD.getFullYear() - 1);
    if(endD >= startD) {
      set_date('end',endD);
    }
    field.select();
    return false;
  }

  /* Numbers are allowed.
   */
  if(charCode >= 48 && charCode <= 57) {
    return true;
  }

  /* Letters, symbols and space are blocked.
   */
  if((charCode>=32 && charCode<=47) ||
      (charCode>=58 && charCode<=126)) {
    return false;
  }

  /* Any other character we allow (this lets tabs, return, enter, etc.
   * continue to work correctly.
   */
  return true;
}


/********************************************************************
 * end_year_postfix
 *
 * Called at the "blur" event.  Checks the year, making it a four-
 * digit year.
 ********************************************************************/
function end_year_postfix() {

  /* Get the field object.
   */
  field = document.getElementById('end_year');

  /* If it has more than 4 digits, truncate it.
   */
  if(field.value.length > 4) {
    field.value = field.value.substr(field.value.length - 4,4);
  }

  /* Numbers between 0 and 50 become 20XX.  Numbers 50 and over
   * become 19XX.  Numbers over 100 become 2XXX.
   */
  if(Number(field.value) < 50) {
    field.value = Number(field.value) + 2000;
  } else if(Number(field.value) < 100) {
    field.value = Number(field.value) + 1900;
  } else if(Number(field.value) < 1000) {
    field.value = Number(field.value) + 2000;
  }

  /* If the end date is before the start date, set it back.
   */
  startD = get_date('start');
  endD = get_date('end');
  if(endD < startD) {
    var allday = document.getElementById('all_day');
    if(allday.checked) {
      endD.setHours(startD.getHours()+1);
      endD.setMinutes(0);
    }
    endD.setFullYear(startD.getFullYear());
    if(endD < startD) {
      endD.setFullYear(startD.getFullYear()+1);
    }
    set_date('end',endD);
  }

  /* If there is a user callback, call it.
   */
  if(window.timeChangeCallback) {
    window.timeChangeCallback();
  }
}

/********************************************************************
 * end_hour_filter
 *
 * A keystroke filter which limits input to numbers with a proper 
 * range of values.
 ********************************************************************/
function end_hour_filter(evt) {

  /* Get the field object so we can select it later.
   */
  field = document.getElementById('end_hour');

  /* Get the key that was pressed.
   */
  evt = (evt) ? evt : event;
  charCode = (evt.charCode) ? evt.charCode : 
    ((evt.keyCode) ? evt.keyCode : ((evt.which) ? event.which : 0));

  /* Get the current start and end dates.
   */
  startD = get_date('start');
  endD = get_date('end');

  /* Up arrow or '+' or '=' increments.
   */
  if(charCode == 38 || charCode == 63232 || charCode == 43 || charCode == 61) {
    endD.setHours(endD.getHours() + 1);
    if(endD >= startD) {
      set_date('end',endD);
    }
    field.select();
    return false;
  }
  
  /* Down arrow or '-' decrements.
   */
  else if(charCode == 40 || charCode == 63233 || charCode == 45) {
    endD.setHours(endD.getHours() - 1);
    if(endD >= startD) {
      set_date('end',endD);
    }
    field.select();
    return false;
  }

  /* Numbers are allowed.
   */
  if(charCode >= 48 && charCode <= 57) {
    return true;
  }

  /* Letters, symbols and space are blocked.
   */
  if((charCode>=32 && charCode<=47) ||
      (charCode>=58 && charCode<=126)) {
    return false;
  }

  /* Any other character we allow (this lets tabs, return, enter, etc.
   * continue to work correctly.
   */
  return true;
}

/********************************************************************
 * end_hour_postfix
 *
 * Called at the "blur" event.  Checks the hour, making it two digits.
 ********************************************************************/
function end_hour_postfix() {

  /* Get the field object.
   */
  field = document.getElementById('end_hour');

  /* If it has more than 2 digits, truncate it.
   */
  if(field.value.length > 2) {
    field.value = field.value.substr(field.value.length - 2,2);
  }

  /* Value must not be negative.
   */
  if(Number(field.value) < 0) {
    field.value = 0;
  }

  /* Value must not be over 23.
   */
  if(Number(field.value) > 23) {
    field.value = 12;
  }

  /* If the value is zero, it's a 24 hour clock and it means
   * am.
   */
  if(Number(field.value == 0)) {
    ampm = document.getElementById('end_ampm');
    ampm.selectedIndex = 0;
    field.value = 12;
  }

  /* If value is greater than 12, it's a 24 hour clock, so we
   * set the am/pm status accordingly.
   */
  if(Number(field.value > 12)) {
    ampm = document.getElementById('end_ampm');
    ampm.selectedIndex = 1;
    field.value = Number(field.value) - 12;
  }

  /* If the end date is before the start date, set it back.
   */
  startD = get_date('start');
  endD = get_date('end');
  if(endD < startD) {
    endD.setHours(startD.getHours());
    if(endD < startD) {
      endD.setHours(startD.getHours()+1);
    }
    set_date('end',endD);
  }

  /* Force the number to be 2 digits.
   */
  if(field.value.length < 2) {
    field.value = '0' + field.value;
  }

  /* If there is a user callback, call it.
   */
  if(window.timeChangeCallback) {
    window.timeChangeCallback();
  }
}

/********************************************************************
 * end_minute_filter
 *
 * A keystroke filter which limits input to numbers with a proper 
 * range of values.
 ********************************************************************/
function end_minute_filter(evt) {

  /* Get the field object so we can select it later.
   */
  field = document.getElementById('end_minute');

  /* Get the key that was pressed.
   */
  evt = (evt) ? evt : event;
  charCode = (evt.charCode) ? evt.charCode : 
    ((evt.keyCode) ? evt.keyCode : ((evt.which) ? event.which : 0));

  /* Get the current start and end dates.
   */
  startD = get_date('start');
  endD = get_date('end');

  /* Up arrow or '+' or '=' increments.
   */
  if(charCode == 38 || charCode == 63232 || charCode == 43 || charCode == 61) {
    endD.setMinutes(endD.getMinutes() + 1);
    if(endD >= startD) {
      set_date('end',endD);
    }
    field.select();
    return false;
  }
  
  /* Down arrow or '-' decrements.
   */
  else if(charCode == 40 || charCode == 63233 || charCode == 45) {
    endD.setMinutes(endD.getMinutes() - 1);
    if(endD >= startD) {
      set_date('end',endD);
    }
    field.select();
    return false;
  }

  /* Numbers are allowed.
   */
  if(charCode >= 48 && charCode <= 57) {
    return true;
  }

  /* Letters, symbols and space are blocked.
   */
  if((charCode>=32 && charCode<=47) ||
      (charCode>=58 && charCode<=126)) {
    return false;
  }

  /* Any other character we allow (this lets tabs, return, enter, etc.
   * continue to work correctly.
   */
  return true;
}

/********************************************************************
 * end_minute_postfix
 *
 * Called at the "blur" event.  Checks the minute, making it two digits.
 ********************************************************************/
function end_minute_postfix() {

  /* Get the field object.
   */
  field = document.getElementById('end_minute');

  /* If it has more than 2 digits, truncate it.
   */
  if(field.value.length > 2) {
    field.value = field.value.substr(field.value.length - 2,2);
  }

  /* Value must not be negative.
   */
  if(Number(field.value) < 0) {
    field.value = 0;
  }

  /* Value must not be over 59.
   */
  if(Number(field.value) > 59) {
    field.value = 59;
  }

  /* If the end date is before the start date, set it back.
   */
  startD = get_date('start');
  endD = get_date('end');
  if(endD < startD) {
    endD.setMinutes(startD.getMinutes());
    if(endD < startD) {
      endD.setMinutes(startD.getMinutes()+1);
    }
    set_date('end',endD);
  }

  /* Force the number to be 2 digits.
   */
  if(field.value.length < 2) {
    field.value = '0' + field.value;
  }

  /* If there is a user callback, call it.
   */
  if(window.timeChangeCallback) {
    window.timeChangeCallback();
  }
}

/********************************************************************
 * get_date
 *
 * Given a part identifier (either 'start' or 'end'), returns a
 * Date element corresponding to the datetime.
 ********************************************************************/
function get_date(part) {

  /* Get the elements corresponding to the correct date.
   */
  month = document.getElementById(part + '_month').value;
  day = document.getElementById(part + '_day').value;
  year = document.getElementById(part + '_year').value;
  if(document.getElementById(part + '_hour')) {
    hour = document.getElementById(part + '_hour').value;
    minute = document.getElementById(part + '_minute').value;
  } else {
    hour = 0;
    minute = 0;
  }
  ampm = document.getElementById(part + '_ampm');

  /* Date uses months in 0-11 range.
   */
  month -= 1;

  /* Convert from 12 to 24 hour clock.
   */
  if(ampm) {
    if(ampm.selectedIndex == 0) {
      if(hour == 12) {
        hour = 0;
      }
    } else {
      if(hour != 12) {
        hour = Number(hour) + 12;
      }
    }
  }

  /* Make the new date object.
   */
  return new Date(year, month, day, hour, minute, 0);
}

/********************************************************************
 * get_date_string
 *
 * Given a part identifier (either 'start' or 'end'), returns an
 * SQL time string (yyyy-mm-dd hh:mm:ss) corresponding to the datetime.
 ********************************************************************/
function get_date_string(part) {

  /* Get the elements corresponding to the correct date.
   */
  month = document.getElementById(part + '_month').value;
  day = document.getElementById(part + '_day').value;
  year = document.getElementById(part + '_year').value;
  hour = document.getElementById(part + '_hour').value;
  minute = document.getElementById(part + '_minute').value;
  ampm = document.getElementById(part + '_ampm');

  /* Convert from 12 to 24 hour clock.
   */
  if(ampm.selectedIndex == 0) {
    if(hour == 12) {
      hour = '00';
    }
  } else {
    if(hour != 12) {
      hour = Number(hour) + 12;
    }
  }

  /* Make the string.
   */
  return year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':00';
}

/********************************************************************
 * set_date
 *
 * Given a part identifier (either 'start' or 'end') and a Date
 * element, sets the form elements corresponding to the part to match
 * the datetime.
 ********************************************************************/
function set_date(part,d) {

  /* Get the elements we need.
   */
  month = document.getElementById(part + '_month');
  day = document.getElementById(part + '_day');
  year = document.getElementById(part + '_year');
  hour = document.getElementById(part + '_hour');
  minute = document.getElementById(part + '_minute');
  ampm = document.getElementById(part + '_ampm');

  /* Set the year to 4 digit year.
   */
  year.value = d.getFullYear();

  /* Set the month (getMonth ranges from 0 to 11, so we add one).
   */
  month.value = d.getMonth() + 1;

  /* Set the day.
   */
  day.value = d.getDate();

  /* Compute what the hours should be.  Since Date uses a 24 hour
   * representation, and we use a 12 hour one, we need to correct
   * for the differences.
   */
  h = d.getHours();

  /* If the hour is less than 12, we're in 'am', otherwise 'pm'.
   */
  if(ampm) {
    if(h < 12) {
      ampm.selectedIndex = 0;
    } else {
      ampm.selectedIndex = 1;
    }
  }

  /* If the hour is zero, the 12 hour clock uses 12 midnight.
   */
  if(hour) {
    if(h == 0) {
      hour.value = 12;
    }
    
    /* If the hour is greater than 12, then we need to subtract 12
     * to get pm hours.
     */
    else if(h > 12) {
      hour.value = h - 12;
    }
    
    /* Otherwise, the hour is the same.
     */
    else {
      hour.value = h;
    }
  }

  /* Since we use two-digit numbers, correct any that may be
   * single digits.
   */
  if(hour && hour.value < 10) { hour.value = '0' + hour.value; }
  if(month.value < 10) { month.value = '0' + month.value; }
  if(day.value < 10) { day.value = '0' + day.value; }
  if(minute) {
  if(d.getMinutes() < 10) {
    minute.value = '0' + d.getMinutes();
  } else {
    minute.value = d.getMinutes();
  }
  }

  /* And we're done.
   */
  return true;
}

/********************************************************************
 * toggle_ends
 *
 * Called by the "Ends" checkbox to toggle the enable/disable of the
 * ends fields.
 ********************************************************************/
function toggle_ends() {

  /* Get the elements for the "Ends" checkbox, along with the ones 
   * we need to enable/disable.
   */
  check = document.getElementById('ends');
  month = document.getElementById('end_month');
  day = document.getElementById('end_day');
  year = document.getElementById('end_year');
  hour = document.getElementById('end_hour');
  minute = document.getElementById('end_minute');
  ampm = document.getElementById('end_ampm');

  /* If the "Ends" checkbox is checked, enable all the items.
   */
  if(check.checked) {
    month.disabled = false;
    day.disabled = false;
    year.disabled = false;
    hour.disabled = false;
    minute.disabled = false;
    ampm.disabled = false;
  }
  
  /* If it's not checked, disable them.
   */
  else {
    month.disabled = true;
    day.disabled = true;
    year.disabled = true;
    hour.disabled = true;
    minute.disabled = true;
    ampm.disabled = true;
  }
}

/********************************************************************
 * toggle_all_day
 *
 * Called from the "All Day" check box to toggle the visibility of
 * the time portions of the start and end date/time.
 ********************************************************************/
function toggle_all_day() {

  /* Get the elements for the start and end time tables, and the
   * all_day checkbox.
   */
  var start_time = document.getElementById('start_time');
  var end_time = document.getElementById('end_time');
  var all_day = document.getElementById('all_day');

  /* If all_day is checked, hide the start & end time tables.
   */
  if(all_day.checked) {
    start_time.style.display = "none";
    end_time.style.display = "none";
  }
  
  /* If it's not checked, show the start & end time tables.
   */
  else {
    start_time.style.display = "block";
    end_time.style.display = "block";
  }
}

/********************************************************************
 * show_char_code
 *
 * A debug function to display a character code.
 ********************************************************************/
function show_char_code(evt) {

  /* Get the event.
   */
  evt = (evt) ? evt : event;

  /* Get the character code.
   */
  charCode = (evt.charCode) ? evt.charCode : 
    ((evt.keyCode) ? evt.keyCode : ((evt.which) ? event.which : 0));
  
  /* Put up an alert.
   */
  alert("Character code is: " + charCode);
}

/********************************************************************
 * days_in_month
 *
 * Returns the days in the month specified by the Date element.
 ********************************************************************/
function days_in_month(date) {

  /* Some handy values.
   */
  var oneHour = 1000 * 60 * 60;
  var oneDay = oneHour * 24;

  /* Get dates for this month and next month on the first day
   * of the month.
   */
  var thisMonth = new Date(date.getFullYear(),date.getMonth(),1);
  var nextMonth = new Date(date.getFullYear(),date.getMonth()+1,1);

  /* The number of days in the month is the difference between
   * next month on the first and this month on the first after adjusting
   * from microseconds to days.
   */
  return Math.ceil((nextMonth.getTime() - thisMonth.getTime() - 
    oneHour)/oneDay);
}

/********************************************************************
 * toggle_calendar
 *
 * Toggles the visiblity of either the start or end calendar,
 * depending on the label value.
 ********************************************************************/
function toggle_calendar(label) {

  /* If this is the end calendar, don't toggle unless the end datetime
   * objects are enabled.
   */
  if(label == 'end') {

    /* Fetch the end_month item and if it's disabled, bail.
     */
    var mon = document.getElementById('end_month');
    if(mon.disabled) {
      return;
    }
  }

  /* Get the correct calendar object.
   */
  var theCal = document.getElementById(label + '_cal');

  /* If it's visible, make it invisible.
   */
  if(theCal.style.display == 'block') {
    theCal.style.display = 'none';
  }

  /* If it's invisible, make it visible.
   */
  else {
    theCal.style.display = "block";
  }
}

/********************************************************************
 * calendar_month_click
 *
 * Handles clicks on the arrow buttons, causing the month to change
 * in a calendar.
 ********************************************************************/
function calendar_month_click(label,month,year) {

  /* Call the fetchURL function to do the data fetch.  We call the
   * special "minical.php" script to generate the calendar data.
   * We have to tell minical which calendar it is ('start' or 'end')
   * because it generates its links with these labels.
   */  
  if(label == 'start') {
    fetchURL("/parts/minical?id=start&year=" + year +
      "&month=" + month, start_callback);
  } else {
    fetchURL("/parts/minical?id=end&year=" + year +
      "&month=" + month, end_callback);
  }
}

/********************************************************************
 * calendar_day_click
 *
 * Handles clicks on the calendar days.  This causes the appropriate
 * date fields to be updated.
 ********************************************************************/
function calendar_day_click(label,day,month,year) {

  /* Hide the calendar.
   */
  toggle_calendar(label);

  /* If this is the start calendar, remember the duration.
   */
  if(label == 'start') {
    remember_duration();
  }

  /* If this is the end calendar, and the date passed in is before
   * the start time, bail.
   */
  if(label == 'end') {

    /* Get the start date. 
     */
    var startD = get_date('start');

    /* Get the current end date.
     */
    var endD = get_date('end');

    /* Change the end date to match the input date.  This preserves
     * the original end time.
     */
    endD.setDate(day);
    endD.setMonth(month-1);
    endD.setFullYear(year);

    /* If the end date is before the start date, bail.
     */
    if(endD < startD) {
      return;
    }
  }

  /* Get the objects corresponding to the day, month & year
   * fields.
   */
  var d = document.getElementById(label+'_day');
  var m = document.getElementById(label+'_month');
  var y = document.getElementById(label+'_year');

  /* Correct the day & month values so they'll always be two digits.
   */
  if(day < 10) { day = '0' + day; }
  if(month < 10) { month = '0' + month; }

  /* Set the date values in the form.
   */
  d.value = day;
  m.value = month;
  y.value = year;

  /* If this is the start calendar, set the end datetime from the
   * start date + duration
   */
  if(label == 'start') {
    end_from_duration();
  }
}

/********************************************************************
 * fetchURL
 *
 * Uses the XMLHttpRequest method to fetch a URL, calling a custom
 * callback function when it it is complete.
 ********************************************************************/
function fetchURL(url,callback) {

  /* If the XMLHttpRequest object exists (Non-IE), use that.
   */
  if(window.XMLHttpRequest) {
    req = new XMLHttpRequest();
    req.onreadystatechange = callback;
    req.open("GET",url,true);
    req.send(null);
  }
  
  /* No XMLHttpRequest object - try the IE way.
   */
  else if(window.ActiveXObject) {
    req = new ActiveXObject("Microsoft.XMLHTTP");
    if(req) {
      req.onreadystatechange = callback;
      req.open("GET",url,true);
      req.send();
    }
  }
}

/********************************************************************
 * start_callback
 *
 * This is the callback for the start calendar.  It replaces the HTML
 * of the start_cal obj with that returned from the HTTP get.
 ********************************************************************/
function start_callback() {

  /* This function gets called for several state changes, and we only
   * want it when it's complete.
   */
  if(req.readyState == 4) {

    /* If the request was successful, replace the start calendar HTML
     * with the returned data.
     */
    if(req.status == 200) {
      var cal = document.getElementById('start_cal');
      cal.innerHTML = req.responseText;
    } else {
      alert("There was an error retrieving the data:\n" + req.statusText);
    }
  }
}

/********************************************************************
 * end_callback
 *
 * This is the callback for the end calendar.  It replaces the HTML
 * of the end_cal obj with that returned from the HTTP get.
 ********************************************************************/
function end_callback() {

  /* This function gets called for several state changes, and we only
   * want it when it's complete.
   */
  if(req.readyState == 4) {

    /* If the request was successful, replace the end calendar HTML
     * with the returned data.
     */
    if(req.status == 200) {
      var cal = document.getElementById('end_cal');
      cal.innerHTML = req.responseText;
    } else {
      alert("There was an error retrieving the data:\n" + req.statusText);
    }
  }
}
