Date.prototype.plusDays = function (days)
{
  var temp = date (this);
  temp.setDate (temp.getDate () + days);
  return temp;
}

Date.prototype.plusWeeks = function (weeks)
{
  return this.plusDays (weeks * 7);
}

Date.prototype.plusMonths = function (months)
{
  var temp = date (this);
  temp.setMonth (temp.getMonth () + months);
  return temp;
}

Date.prototype.plusYears = function (years)
{
  var temp = date (this);
  temp.setFullYear (temp.getFullYear () + years);
  return temp;
}

Date.prototype.getRealMonth = function ()
{
  return this.getMonth () + 1;
}

Date.prototype.dmy = function (redIfLate)
{
  var text = this.getDate () + "/" + this.getRealMonth () + "/" + this.getFullYear ().twoDigits ();
  return text.tagged ("<span style=color:red>", (typeof (redIfLate) != "undefined") && redIfLate && (this.yyyymmdd () < today.yyyymmdd ()));
}

Date.prototype.yyyymmdd = function ()
{
  return this.getFullYear () + "/" + this.getRealMonth ().twoDigits () + "/" + this.getDate ().twoDigits ();
}

Date.prototype.daysInMonth = function ()
{
  var month = this.getRealMonth ();
  var year = this.getFullYear ();
  if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12)
    return 31;
  else if (month == 4 || month == 6 || month == 9 || month == 11)
    return 30;
  else if (((year % 4) == 0 && (year % 100) != 0) || (year % 400) == 0)
    return 29;
  else
    return 28;
}

Date.prototype.monthName = function ()
{
  var monthNameArray = Array ("January", "February", "March", "April", "May", "June",
                              "July", "August", "September", "October", "November", "December");
  return monthNameArray[this.getMonth ()];
}

Date.prototype.dayName = function ()
{
  var dayNameArray = Array ("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday");
  return dayNameArray[this.getDay ()];
}

Date.prototype.fullDate = function ()
{
  return this.dayName () + " " + this.getDate () + " " + this.monthName () + " " + this.getFullYear ();
}

//xx remove
Date.prototype.hms = function ()
{
  return this.getHour ().twoDigits () + ":" + this.getMinutes ().twoDigits () + ":" + this.getSeconds ().twoDigits ();
}

Date.prototype.hhmmss = function ()
{
  return this.getHour ().twoDigits () + ":" + this.getMinutes ().twoDigits () + ":" + this.getSeconds ().twoDigits ();
}

//xx remove
Date.prototype.hm = function ()
{
  return this.getHours ().twoDigits () + ":" + this.getMinutes ().twoDigits ();
}

Date.prototype.hhmm = function ()
{
  return this.getHours ().twoDigits () + ":" + this.getMinutes ().twoDigits ();
}

Date.prototype.ddmmmyyyy = function ()
{
  return this.getDate () + " " + this.monthName () + " " + this.getFullYear ();
}

Date.prototype.fullDate = function ()
{
  return this.dayName () + " " + this.ddmmmyyyy ();
}

Date.prototype.fullDateAndTime = function ()
{
  return this.fullDate () + " " + this.hhmm ();
}

if (typeof (Date.prototype.getFullYear) == "undefined")
{
  alert ("getFullYear being supplied for Date - presumably you're on Opera");
  Date.prototype.getFullYear = function ()
  {
    var theYear = this.getYear();
    if (theYear < 1900)
      return theYear + 1900;
    else
      return theYear;
  }
}

Number.prototype.floor = function ()
{
  return Math.floor (this);
}

Number.prototype.comma = function ()
{
  var to = "";
  var from = String (this);
  if (from == "")
    return "";
  else
  {
    while (from.length > 3)
    {
      to = "," + from.last (3) + to;
      from = from.first (from.length - 3);
    }
    return from + to;
  }
}

Number.prototype.hex2 = function ()
{
  if (this > 255)
    return "FF";
  else
  {
    var hex = "0123456789ABCDEF";
    return hex.charAt (this / 16) + hex.charAt (this % 16);
  }
}

Number.prototype.zeroFill = function (size)
{
  var partial = String (this);
  while (partial.length < size)
    partial = "0" + partial;
  return partial;
}

Number.prototype.twoDigits = function ()
{
  return (this % 100).zeroFill (2);
}

Number.prototype.tagged = function (tag, test)
{
  return String (this).tagged (tag, test);
}

Number.prototype.twoDecimals = function (number)
{
  return Math.round (100 * this) / 100;
}

String.prototype.first = function (number)
{
  return this.substr (0, number);
}

String.prototype.last = function (number)
{
  //return this.substr (-number);  does not work with IE !!
  return this.substr (this.length - number);
}

String.prototype.contains = function (candidate)
{
  return this.toLowerCase ().indexOf (candidate.toLowerCase ()) >= 0;
}

String.prototype.begins = function (candidate)
{
  return this.first (candidate.length).toLowerCase () == candidate.toLowerCase ();
}

String.prototype.ends = function (candidate)
{
  return this.last (candidate.length).toLowerCase () == candidate.toLowerCase ();
}

String.prototype.matches = function (candidate)
{
  return this.toLowerCase () == candidate.toLowerCase ();
}

String.prototype.comma = function ()
{
  if (this != "")
    return Number (this).comma ();
  else
    return "";
}

String.prototype.replaced = function (from, to)
{
  var regular = new RegExp (from, "gi");
  return this.replace (regular, to);
}

String.prototype.replacedSS = function (start, size, text)
{
  return this.first (start) + text + this.substr (start + size);
}

String.prototype.alphaNumeric = function ()
{
  return this.replaced ("[^a-z0-9]", "");
}

String.prototype.digits = function ()
{
  return this.replaced ("[^0-9]", "");
}

String.prototype.initialCaps = function ()
{
  // Note that this one triggers initial caps after each non-alphanumeric.
  var work = this;
  var convert = true;
  for (var i = 0; i < work.length; i++)
  {
    var character = work.charAt (i);
    if ((character >= "a") && (character <= "z"))
    {
      if (convert)
      {
        work = work.replacedSS (i, 1, character.toUpperCase ());
        convert = false;
      }
    }
    else if (((character >= "A") && (character <= "Z")) || ((character >= "0") && (character <= "9")))
      convert = false;
    else
      convert = true;
  }
  return work;
}

String.prototype.sentenceCaps = function ()
{
  // Arbitrary code that gets it reasonably right.
  var work = this;
  var convert = (work.charAt (0) != work.charAt (1)) || (work.charAt (2) != " ");
  for (var i = 0; i < work.length; i++)
  {
    var character1 = work.charAt (i);
    var character2 = work.charAt (i + 1);
    if ((character1 >= "a") && (character1 <= "z"))
    {
      if (convert)
      {
        work = work.replacedSS (i, 1, character1.toUpperCase ());
        convert = false;
      }
    }
    else if (((character1 >= "A") && (character1 <= "Z")) || ((character1 >= "0") && (character1 <= "9")))
      convert = false;
    else if (((character1 == ".") || (character1 == "?") || (character1 == "!")) && (character2 == " "))
      convert = true;
  }
  return work;
}

String.prototype.tagged = function (tag, test)
{
  if ((tag != "") && ((typeof (test) == "undefined") || test))
  {
    var tagKey = tag.substr (1, tag.length - 2).split (" ");
    return tag + this + "</" + tagKey[0] + ">";
  }
  else
    return this;
}

String.prototype.noCommas = function ()
{
  return this.replaced (",", "");
}

String.prototype.noSemicolons = function ()
{
  return this.replaced (";", "");
}

String.prototype.nonBreaking = function ()
{
  return this.replaced (" ", "&nbsp;");
}

String.prototype.noQuotes = function ()
{
  return this.replaced ("[\"']", "");
}

String.prototype.noSpaces = function ()
{
  return this.replaced (" ", "");
}

String.prototype.sqDecode = function ()   // "sq" == "single-quote"
{
  if (this == "")
    return "";
  else
    return this.replaced ("&#039;", "'");
}

String.prototype.fullURL = function ()
{
  if (this == "")
    return "";
  else if (this.begins ("http://"))
    return this;
  else if (this.begins ("//"))
    return "http:" + this;
  else
    return "http://" + this;
}

Array.prototype.find = function (findValue)
{
  for (var i = 0; i < this.length; i++)
    if (this[i] == findValue)
      return i;
  return -1;
}

////////////////////////////////////////////////////////////
//
// The following is provided for IE5 JavaScript and will be ignored
// for later versions.

//debug (Array.prototype.push);
if (typeof (Array.prototype.push) == "undefined")
{
  Array.prototype.push = function (item)
  {
    this[this.length] = item;
  }

  Array.prototype.shift = function ()
  {
    var item = this[0];
    for (var i = 1; i < this.length; i++)
      this[i-1] = this[i];
    this.length -= 1;
    return item;
  }

  Array.prototype.splice = function (index, howMany, element1, element2)
  {
    if (howMany > 0)
    {
      debug ("Splice >0 not supported");
      Splice1NotSupported;
    }
    if (typeof (element2) != "undefined")
    {
      debug ("Splice multi-elements not supported");
      SpliceMultiElementsNotSupported;
    }
    for (var i = this.length; i > index; i--)
      this[i] = this[i-1];
    this[index] = element1;
  }
}
//debug (Array.prototype.push);

////////////////////////////////////////////////////////////

function header (text, size)
{
  if (netscape)
    put ("<p style='font-size:" + size + "pt;text-shadow:#004020 5px 5px 7px;color:black;width:100%;margin-top:" + (-size / 3) + "'>" + text + "<br></p>");
  else
    put ("<p style='font-size:" + size + "pt;filter:shadow(color:#408080,direction:135);color:black;width:100%;margin-top:" + (-size / 3) + "'>" + text + "<br></p>");
}

function put (text)
{
  document.write (text);
}

function putLF (text)
{
  alert ("No 'putLF' client-side, use 'put' or 'putLine'");
  putLine (text);
}

function putLine (text)
{
  put (text + "<br>");
}

function debugFireFox (text, extra)
{
  if (firefox || chrome)
    return debug (text, extra);
  else
    return ""; // in case it's embedded in a print statement
}

function debug (text, extra)
{
  if (typeof (extra) == "undefined")
    putLine ("JS==>" + String (text).replaced ("<", "~") + "<==JS");
  else
    putLine ("JS==>" + extra + " has '" + text.replaced ("<", "~") + "'<==JS");
  return ""; // in case it's embedded in a print statement
}

function debugObject (object)
{
  for (var i in object)
    alert ("object " + i + " = " + object[i]);
}

function debugObjectReturn (object)
{
  var html = "";
  for (var i in object)
    html += "object " + i + " = " + object[i] + "<br>";
  return html;
}

function namer (type, name)
{
  // Keep the names fairly simple - namer strips spaces and full stops only.

  return type + name.replaced (" |\\.", "");
}

function min () // dynamic argument list, must all be numbers
{
  var temp = arguments[0];
  for (var i = 1; i < arguments.length; i++)
    if (arguments[i] < temp)
      temp = arguments[i];
  return temp;
}

function max () // dynamic argument list, must all be numbers
{
  var temp = arguments[0];
  for (var i = 1; i < arguments.length; i++)
    if (arguments[i] > temp)
      temp = arguments[i];
  return temp;
}

function StackableText (id, first, second, third)
{
  put ("<table border=0 onresize='" + id + ".resized ();' class=centred><tr><td id=table" + id + "id>");
  put ("<span id=span" + id + "whole><span id=span" + id + "first>" + first + "</span><span id=span" + id + "second>" + second + "</span>" + third + "</span>");
  put ("</td></tr></table>");
  this.id = id;
  this.first = first;
  this.second = second;
  this.third = third;
  this.firstHeight = 0;
  this.contracted = false;
}

StackableText.prototype.resized = function (text)
{
  if (this.firstHeight == 0)
  {
    var firstSpan = eval ("span" + this.id + "first");
    var secondSpan = eval ("span" + this.id + "second");
    this.firstHeight = max (firstSpan.offsetHeight, secondSpan.offsetHeight);
  }
  if (this.firstHeight > 0)
  {
    var wholeSpan = eval ("span" + this.id + "whole");
    var height = wholeSpan.offsetHeight;
    if ((height > this.firstHeight) && ! this.contracted)
    {
      var item = eval ("table" + this.id + "id");
      item.innerHTML = "<span id=span" + this.id + "whole>" + this.first + "<br>" + this.second + "<br>" + this.third + "</span>";
      this.contracted = true;
    }
  }
}

function date (value)    // note that this is slightly different to the asp one.
{
  if ((typeof (value) == "string") && (value != ""))
    return new Date (value.replaced ("-", "/"));
  else if (typeof (value) != "undefined")
    return new Date (value);
  else
    return new Date ();
}

function colourAdjust (colour, adjust)
{
  return "#" +
         Math.round (parseInt (colour.substr (1, 2), 16) * adjust).hex2 () +
         Math.round (parseInt (colour.substr (3, 2), 16) * adjust).hex2 () +
         Math.round (parseInt (colour.substr (5, 2), 16) * adjust).hex2 ();
}

function absoluteTop (object)
{
  if (typeof (object) == "string")
    object = document.getElementById (object);
  if (document.layers)  // i.e. Netscape
    return object.y;
  else
  {
    var work = 0;
    while (object.offsetParent)
    {
      work += object.offsetTop;
      object = object.offsetParent;
    }
    return work;
  }
}

function absoluteBottom (object)
{
  if (typeof (object) == "string")
    object = document.getElementById (object);
  if (document.layers)  // i.e. Netscape
    return object.y + object.height;
  else
  {
    var work = object.offsetHeight;
    while (object.offsetParent)
    {
      work += object.offsetTop;
      object = object.offsetParent;
    }
    return work;
  }
}

function absoluteLeft (object)
{
  if (typeof (object) == "string")
    object = document.getElementById (object);
  if (document.layers)  // i.e. Netscape
    return object.x;
  else
  {
    var work = 0;
    while (object.offsetParent)
    {
      work += object.offsetLeft;
      object = object.offsetParent;
    }
    return work;
  }
}

function silentNumber (cloaked)
{
  var number = "";
  for (var i = 0; i < cloaked.length; i += 2)
    number += cloaked.charAt (i);
  put (number);
}

function cloakedAction (location)
{
  var url = location.replaced (":", ".").replaced ("~", "@");
  return (" action='mai" + "lto" + ":" + url + "' ");  // note that this returns rather than writing
}

function cloakedHref (location, text)
{
  var url = location.replaced (":", ".").replaced ("~", "@");
  if (arguments.length == 2)
    var tag = text;
  else
  {
    var urlParts = url.split ("?");
    var tag = urlParts[0];
  }
  put ("<a href='mai" + "lto" + ":" + url + "'>" + tag + "</a>");
}

function trapEnterAsTab ()  // called from an onKeyDown in the body statement
{
  if (window.event.keyCode == 13 /* enter */)
    window.event.keyCode = 9 /* tab */;
}

function callOnChangeEvent (field)  // called from an object storing a value in a different field
{

  // Note that this routine will not have the correct value for "this".  To compensate,
  // it does a global change of "(this)" to "(field)" before evaluating the event's code.
  // Use of the "this" value in any other form will not be converted and will cause
  // errors.

  if (field.onchange != null)
  {
    var onChangeFunction = field.onchange.toString ();
    // note that my "replaced" method won't work here - need to do it from first principals
    var onChangeCode = onChangeFunction.substr (onChangeFunction.indexOf ("{")).replace (new RegExp ("(this)", "gi"), "(field)");
    eval (onChangeCode);
  }
}

function callOnChangeAtEnter ()  // called from an onKeyDown or onKeyPress in the item involved
{

  // Handling of "this" is very limited - see "callOnChangeEvent" for information, but
  // the main thing to remember is that "(this)" will be converted to the source element object.

  if (window.event.keyCode == 13 /* enter */)
  {
    callOnChangeEvent (window.event.srcElement);
    return false;
  }
  return true;
}

function setField (fieldName, value)
{
  var field = document.getElementById (fieldName);
  field.value = value;
}

function forceUpperCase () // used from onKeyPress
{
  key = window.event.keyCode;
  if ((key > 0x60) && (key < 0x7B))
    window.event.keyCode = key - 0x20;
}

function hideSelects (field, hide)
{
  var form = field.form;
  for (var i = 0; i < form.length; i++)
    if (("" + form.elements[i].type).begins ("select-"))
      form.elements[i].style.visibility = hide ? "hidden" : "visible";
}

today = date (date ().yyyymmdd ());
thisYear = today.getFullYear ();
netscape = navigator.appName.contains ("Netscape");
mozilla = navigator.userAgent.contains ("Gecko");
firefox = navigator.userAgent.contains ("Firefox");
chrome = navigator.userAgent.contains ("Chrome");
