//
//  Library containing CSDate object
//
//  Author: Giles Mullen
//  Date:   10/12/2000
//
//  Requires: CS00_Lib_<Language> (i.e. CS00_Lib_English)
//            CS10_Lib_Shared
//            CS20_Lib_Parser
//            CS25_Lib_CSErr
//


//
//  array for determining the number of days in a month.
//
var CS_DATETIMEDAYARRAY = [ 31, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]; 


var CS_DATEKWDS    = "Y+|M+|D+|H+|h+|m+|s+|T|t";
var CS_DATEKWDSRGX = new RegExp( CS_DATEKWDS, "g" );
var CS_DATETMPRGX  = new RegExp( "(\\d\\d\\d\\d)[\\-\\/](\\d\\d)[\\-\\/](\\d\\d)\\ (\\d\\d)\\:(\\d\\d)\\:(\\d\\d)" );
var CS_DTTMRGX     = /((\d{4})\-(\d{2})\-(\d{2}))?T?((\d{2})\:(\d{2})\:(\d{2}))?/;
var CS_DATESEPS    = "/-.:_, ";
var CS_DATERGXSEPS = "\\/\\-\\.\\:\\_\\,\\ ";

//
//    CSDate
//
CSDate.ERR_FAM                   = 0x2000;

CSDate.ERR_YEAR_OUTOFRANGE       = CSDate.ERR_FAM | 0x0002;
CSDate.ERR_MONTH_OUTOFRANGE      = CSDate.ERR_FAM | 0x0004;
CSDate.ERR_DAY_OUTOFRANGE        = CSDate.ERR_FAM | 0x0008;
CSDate.ERR_HOUR_OUTOFRANGE       = CSDate.ERR_FAM | 0x0010;
CSDate.ERR_MINUTE_OUTOFRANGE     = CSDate.ERR_FAM | 0x0020;
CSDate.ERR_SECOND_OUTOFRANGE     = CSDate.ERR_FAM | 0x0040;
CSDate.ERR_TEMPLATE_INVALID      = CSDate.ERR_FAM | 0x0080;
CSDate.ERR_FORMAT_INVALID        = CSDate.ERR_FAM | 0x0100;
CSDate.ERR_MONTH_UNRECOGNIZABLE  = CSDate.ERR_FAM | 0x0200;
CSDate.ERR_DATE_OUTOFRANGE       = CSDate.ERR_FAM | 0x0400;

CSDate.ERR_YEAR_OUTOFRANGE_STR   = CS_DTOBJ_ERR_YEAR_OUTOFRANGE_STR
CSDate.ERR_MONTH_OUTOFRANGE_STR  = CS_DTOBJ_ERR_MONTH_OUTOFRANGE_STR;
CSDate.ERR_DAY_OUTOFRANGE_STR    = CS_DTOBJ_ERR_DAY_OUTOFRANGE_STR;
CSDate.ERR_HOUR_OUTOFRANGE_STR   = CS_DTOBJ_ERR_HOUR_OUTOFRANGE_STR;
CSDate.ERR_MINUTE_OUTOFRANGE_STR = CS_DTOBJ_ERR_MINUTE_OUTOFRANGE_STR;
CSDate.ERR_SECOND_OUTOFRANGE_STR = CS_DTOBJ_ERR_SECOND_OUTOFRANGE_STR;
CSDate.ERR_TEMPLATE_INVALID_STR  = CS_DTOBJ_ERR_TEMPLATE_INVALID_STR;
CSDate.ERR_FORMAT_INVALID_STR    = CS_DTOBJ_ERR_FORMAT_INVALID_STR;
CSDate.ERR_MONTH_UNRECOGNIZABLE_STR = CS_DTOBJ_ERR_MONTH_UNRECOGNIZABLE_STR;
CSDate.ERR_DATE_OUTOFRANGE_STR   = CS_DTOBJ_ERR_DATE_OUTOFRANGE_STR;
CSDate.ERR_DATE_OUTOFRANGE_R_STR = CS_DTOBJ_ERR_DATE_OUTOFRANGE_R_STR;
CSDate.ERR_DATE_OUTOFRANGE_A_STR = CS_DTOBJ_ERR_DATE_OUTOFRANGE_A_STR;
CSDate.ERR_DATE_OUTOFRANGE_B_STR = CS_DTOBJ_ERR_DATE_OUTOFRANGE_B_STR;

function CSDate_getErrorStringStat( num, ex ) 
{
  var ret = CSErr.ERR_UNDEFINED_STR;
  if (num == CSDate.ERR_YEAR_OUTOFRANGE) {
    ret = CSDate.ERR_YEAR_OUTOFRANGE_STR;
  } else if (num == CSDate.ERR_MONTH_OUTOFRANGE) {
    ret = CSDate.ERR_MONTH_OUTOFRANGE_STR;
  } else if (num == CSDate.ERR_MONTH_UNRECOGNIZABLE) {
    ret = CSDate.ERR_MONTH_UNRECOGNIZABLE_STR;
  } else if (num == CSDate.ERR_DAY_OUTOFRANGE) {
    ret = CSDate.ERR_DAY_OUTOFRANGE_STR;
  } else if (num == CSDate.ERR_HOUR_OUTOFRANGE) {
    ret = CSDate.ERR_HOUR_OUTOFRANGE_STR;
  } else if (num == CSDate.ERR_MINUTE_OUTOFRANGE) {
    ret = CSDate.ERR_MINUTE_OUTOFRANGE_STR;
  } else if (num == CSDate.ERR_SECOND_OUTOFRANGE) {
    ret = CSDate.ERR_SECOND_OUTOFRANGE_STR;
  } else if (num == CSDate.ERR_TEMPLATE_INVALID) {
    ret = CSDate.ERR_TEMPLATE_INVALID_STR;
  } else if (num == CSDate.ERR_FORMAT_INVALID) {
    ret = CSDate.ERR_FORMAT_INVALID_STR;
    if (ex != null)
      ret = ret.replace( /\%s/, ex );
  } else if (num == CSDate.ERR_DATE_OUTOFRANGE) {
    if (ex.b != null && ex.e != null) {
      ret = CSDate.ERR_DATE_OUTOFRANGE_R_STR.replace( /\%s/, ex.b);
      ret = ret.replace( /\%s/, ex.e );
    } else if (ex.b != null && ex.e == null) {
      ret = CSDate.ERR_DATE_OUTOFRANGE_A_STR.replace( /\%s/, ex.b);
    } else if (ex.b == null && ex.e != null) {
      ret = CSDate.ERR_DATE_OUTOFRANGE_B_STR.replace( /\%s/, ex.e);
    } else {
      ret = CSDate.ERR_DATE_OUTOFRANGE_STR;
    }
  }
  return ret;
}

function CSDate_getErrorString() 
{
  return CSDate_getErrStrStat( this.err );
}

function CSDate_getErrStrStat( err ) 
{
  return CSDate_getErrorStringStat( err.no, err.ex );
}

function CSDate_buildRegExpFromTokens( tokens )
{
  var ret = null;
  var rgx = null;
  do {
    if (tokens == null) {
      this.err =  new CSErr( CSDate.ERR_TEMPLATE_INVALID );
      break;
    }
    var len = tokens.length;
    var i = 0;
    rgx = "^";
    for (i = 0; i < len; i++) {
      var tok = tokens[i];
      if (tok.type == CS_KWD) {
        switch (tokens[i].chunk) {
          case "D":
          case "M":
          case "m":
          case "s":
          case "h":
          case "hh":
          case "H":
          case "HH":
            rgx += "(\\d{1,2})";
            break;
          case "DD":
          case "MM":
          case "mm":
          case "ss":
            rgx += "(\\d{2})";
            break;
          case "MMM":
            rgx += "([A-Za-z]{1,12})";
            break;
          case "YY":
            rgx += "(\\d{2})";
            break;
          case "YYYY":
            rgx += "(\\d{2,4})";
            break;
          case "t":
          case "T":
            rgx += "([AaMmPp]{0,2})";
            break;
          default:
            break;
        }
      } else if (tok.type == CS_SEP) {
        rgx += "([" + CS_DATERGXSEPS + "]*)";
      }
    }
    rgx += "$";
    ret = rgx;
  } while (false);
  return ret;
}

function CSDate_monthNumberLookupStat( str )
{
  var ret = -2;
  var i   = 0;
  for (i = 0; i < CS_DATETIMEMONTHARRAY.length; i++) {
    if (str.toUpperCase() == CS_DATETIMEMONTHARRAY[i][0].toUpperCase()) {
      ret = CS_DATETIMEMONTHARRAY[i][1];
      break;
    }
  }
  return ret;
}

function CSDate_monthNameLookupStat( num )
{
  if (num < 1 || num > CS_DATETIMEMONTHARRAY.length) 
    return "";
  return CS_DATETIMEMONTHARRAY[num - 1][0];
}

function CSDate_yearHorizonStat( year )
{
  ret = parseInt( year, 10 );
  if (year.length == 2 && ret >= 50 && ret < 100) {
    ret += 1900;
  } else if (year.length == 2 && ret >= 0 && ret < 50) {
    ret += 2000;
  }
  return ret;
}

//
//    builds a date time object from tokens array and rgx results.
//
//    tokens - tokens array.
//    rgxres - regexp results.
//
function CSDate_buildDateObj( tokens, rgxres )
{
  do {
    if (tokens == null) {
      this.err = new CSErr( CSDate.ERR_TEMPLATE_INVALID );
      break;
    } 
    if (rgxres == null) {
      var de = new CSDate( 2001, 12, 31, 16, 45, 30 );
      var r = de.formatDate( tokens, true );
      this.err = new CSErr( CSDate.ERR_FORMAT_INVALID, r[0] );
      break;
    }
    var i = 0;
    var j = 0;
    var ap = "";
    for (i = 0; i < tokens.length; i++) {
      if (tokens[i].type == CS_SEP ) {
        j++;
      } else if (tokens[i].type == CS_KWD) {
        j++;
        switch (tokens[i].chunk) {
          case "D":
          case "DD":
            this.day = parseInt( rgxres[j], 10 );
            break;
          case "M":
          case "MM":
            this.month = parseInt( rgxres[j], 10 );
            break;
          case "MMM":
            this.month = CSDate_monthNumberLookupStat( rgxres[j] );
            break;
          case "YY":
          case "YYYY":
            this.year = CSDate_yearHorizonStat( rgxres[j] );
            break;
          case "H":
          case "HH":
          case "h":
          case "hh":
            this.hour = parseInt( rgxres[j], 10 );
            break;
          case "m":
          case "mm":
            this.minute = parseInt( rgxres[j], 10 );
            break;
          case "s":
          case "ss":
            this.second = parseInt( rgxres[j], 10 );
            break;
          case "t":
          case "T":
            ap = rgxres[j].toUpperCase();
            if (ap == "PM" || ap == "P") {
              ap = "PM";
            } else if (ap == "AM" || ap == "A") {
              ap = "AM";
            }
            break;
        } 
      }
    }
    if (ap == "PM" && this.hour <= 11 && this.hour >= 0) {
      this.hour += 12;
    } else if (ap == "AM" && this.hour == 12) {
      this.hour = 0;
    }
    this.verify();
  } while (false);
}

//
//    dt - CSDate, b - begin, e - end, ta - token array
//
function CSDate_validateRange( dt, b, e, ta )
{
  var ret = true;
  if ((b != null && dt < b) || (e != null && dt > e)) {
    this.err = new CSErr( CSDate.ERR_DATE_OUTOFRANGE, new Object() );
    this.err.ex.b = b != null ? b.formatDate( ta, true )[0] : null;
    this.err.ex.e = e != null ? e.formatDate( ta, true )[0] : null;
    ret = false;
  }
  return ret;
}

function CSDate_verifyDateObj()
{
  var ret = CSErr.ERR_UNDEFINED;
  do {
    if (this.year < 0 && this.year != null ) {
      this.err = new CSErr( CSDate.ERR_YEAR_OUTOFRANGE );
      break;
    }
    if (this.month == -2) {
      this.err = new CSErr( CSDate.ERR_MONTH_UNRECOGNIZABLE );
      break;
    }
    if ((this.month < 1 || this.month > 12) && this.month != null) {
      this.err = new CSErr( CSDate.ERR_MONTH_OUTOFRANGE );
      break;
    }
    if ((this.day < 1 || this.day > CS_DATETIMEDAYARRAY[ this.month ]) && this.day != null ) {
      this.err = new CSErr( CSDate.ERR_DAY_OUTOFRANGE );
      break;
    }
    //
    //  check leap year
    //
    if (this.year > 0 && this.month == 2) {  // month is feb && year i > 0
      if (!(this.year % 4 == 0 && (this.year % 100 == 0 ? this.year % 400 == 0 : true))) { // not leap year
        if (this.day > 28 && this.day != null) { // 
          this.err = new CSErr( CSDate.ERR_DAY_OUTOFRANGE );
          break;
        }
      }
    }  
    if ((this.hour < 0 || this.hour > 23) && this.hour != null) {
      this.err = new CSErr( CSDate.ERR_HOUR_OUTOFRANGE );
      break;
    }
    if ((this.minute < 0 || this.minute > 59) && this.minute != null) {
      this.err = new CSErr( CSDate.ERR_MINUTE_OUTOFRANGE );
      break;
    }
    if ((this.second < 0 || this.second > 59) && this.second != null) {
      this.err = new CSErr( CSDate.ERR_SECOND_OUTOFRANGE );
      break;
    }
    this.err = new CSErr( CSErr.ERR_SUCCESS );
  } while (false);
  ret = this.err.no;
  return ret;  
}

function CSDate_formatDateObj( tokens, bSeps )
{
  var ret = null;
  do {
    if (tokens == null) 
      break;
    if (bSeps == null)
      bSeps = true;
    j = 1;
    ret = new Array;
    ret[0] = ret[j] = "";
    var i = 0;
    var v = "";
    for (i = 0; i < tokens.length; i++) {
      v = "";
      if (tokens[i].type == CS_SEP) {
        v = tokens[i].chunk;
      } else if (tokens[i].type == CS_BRK) {
        j++;
        ret[j] = "";
      } else if (tokens[i].type == CS_KWD) {
        switch (tokens[i].chunk) {
          case "D":
            v = this.day.toString();      
            break;
          case "DD":
            v = CS_NumberForceLengthPrepend( this.day, 2 );
            break;
          case "M":
            v = this.month.toString();
            break;
          case "MM":
            v = CS_NumberForceLengthPrepend( this.month, 2 );
            break;
          case "MMM":
            v = CSDate_monthNameLookupStat( this.month );
            break;
          case "YY":
            v = CS_NumberForceLengthPrepend( this.year, 2 );
            break;
          case "YYYY":
            v = CS_NumberForceLengthPrepend( this.year, 4 );
            break;
          case "H":
            v = this.hour.toString();
            break;
          case "HH":
            v = CS_NumberForceLengthPrepend( this.hour.toString(), 2 );
            break;
          case "h":
            v = ((this.hour + 11) % 12 + 1).toString();
            break;
          case "hh":
            v = CS_NumberForceLengthPrepend( ((this.hour + 11) % 12 + 1).toString(), 2 );
            break;
          case "m":
            v = this.minute.toString();
            break;
          case "mm":
            v = CS_NumberForceLengthPrepend( this.minute, 2 );
            break;
          case "s":
            v = this.second.toString();
            break;
          case "ss":
            v = CS_NumberForceLengthPrepend( this.second, 2 );
            break;
          case "t":
            v = this.hour < 12 ? "am" : "pm";
            break;
          case "T":
            v = this.hour < 12 ? "AM" : "PM";
            break;
        }
      }
      ret[0] += v;
      if (tokens[i].type != CS_SEP)
        ret[j] += v;
    }
  } while (false);
  this.res = ret;
  return ret;
}

function CSDate_parseTemplate( templ )
{
  return CS_buildTokenArray( templ, CS_DATESEPS, CS_DATEKWDSRGX );
}

function CSDate_buildRegExp( tok )
{
  var regexp = null;
  do {
    var rgx = CSDate_buildRegExpFromTokens( tok );
    if (rgx == null) {
      this.err = new CSErr( CSDate.ERR_TEMPLATE_INVALID );
      break;
    }
    regexp = new RegExp( rgx );
    if (regexp == null) {
      this.err = new CSErr( CSDate.ERR_TEMPLATE_INVALID );
      break;
    }
  } while (false);
  return regexp;  
}

//
//    rgx - compiled regexp, toks - Token Array, val - String to be evaluated,
//    returns CSDate
//
function CSDate_evalutate( rgx, toks, val )
{ 
  var ret = null;
  var res = CS_DATETMPRGX.exec( val );
  if (res != null) {
    var d = !(res[1] == 0 && res[2] == 0 && res[3] == 0);
    var t = !(res[4] == 0 && res[5] == 0 && res[6] == 0);
    this.reset( d ? res[1] : null,
                d ? res[2] : null,
                d ? res[3] : null,
                t ? res[4] : null,
                t ? res[5] : null,
                t ? res[6] : null );  
    this.verify();
  } else {
    res = rgx.exec( val );
    this.build( toks, res );  
  }
  if (this.err.no == CSErr.ERR_SUCCESS) {
    ret = new CSDate( this.year,
                      this.month,
                      this.day,
                      this.hour,
                      this.minute,
                      this.second );
  }
  return ret;
}

//
//    dt - CSDate, ta - Token Array, bSps - Bool
//    returns formatted string array
//
function CSDate_format( dt, ta, bSps )
{
  var ret = dt.formatDate( ta, bSps );
  this.err = dt.err;
  return ret;
}

function CSDate_toString()
{
  return   "year:   " + this.year   + "\r"
         + "month:  " + this.month  + "\r"
         + "day:    " + this.day    + "\r"
         + "hour:   " + this.hour   + "\r"
         + "minute: " + this.minute + "\r"
         + "second: " + this.second + "\r"
         + "err:\r  " + this.err    + "\r"
         + "errstr: " + this.getErrorString();
}

function CSDate_valueOf()
{
  var Y = this.year   == null ? 0 : this.year;
  var M = this.month  == null ? 0 : this.month;
  var D = this.day    == null ? 0 : this.day;
  var h = this.hour   == null ? 0 : this.hour;
  var m = this.minute == null ? 0 : this.minute;
  var s = this.second == null ? 0 : this.second;
  // this does not return accurate seconds from year 0.  Use only for comparisons.
  return s + (m * 60) + (h * 3600) + (D * 86400) + (M * 2678400) + (Y * 980294400);
}

function CSDate_reset( Y, M, D, h, m, s )
{
  var i = 0;
  for (i = 0; arguments[i] != null; i++);
  if (i == 1) {
    var r = CS_DTTMRGX.exec( Y );
    if (r == null || r == "" || !r[0]) {
      r = new Array();
      if (Y != null)
        r[2] = Y;
    }
    Y = r[2]; M = r[3]; D = r[4]; h = r[6]; m = r[7]; s = r[8];
  }
  this.year     = CS_ParseIntEx( Y );
  this.month    = CS_ParseIntEx( M );
  this.day      = CS_ParseIntEx( D );
  this.hour     = CS_ParseIntEx( h );
  this.minute   = CS_ParseIntEx( m );
  this.second   = CS_ParseIntEx( s );
  this.err      = new CSErr( CSErr.ERR_UNDEFINED );
  this.res      = null;
}

function CSDate( Y, M, D, h, m, s )
{
  this.reset( Y, M, D, h, m, s );
}

CSDate.prototype.reset          = CSDate_reset;
CSDate.prototype.parseTemplate  = CSDate_parseTemplate;
CSDate.prototype.buildRegExp    = CSDate_buildRegExp;
CSDate.prototype.evaluate       = CSDate_evalutate;
CSDate.prototype.build          = CSDate_buildDateObj;
CSDate.prototype.format         = CSDate_format;
CSDate.prototype.formatDate     = CSDate_formatDateObj;
CSDate.prototype.verify         = CSDate_verifyDateObj;
CSDate.prototype.validateRange  = CSDate_validateRange;
CSDate.prototype.getErrorString = CSDate_getErrorString;
CSDate.prototype.toString       = CSDate_toString;
CSDate.prototype.valueOf        = CSDate_valueOf;

CSDate.getErrorString           = CSDate_getErrStrStat;
