﻿//-----------------------------------------------------------------------------
// XDate
//
// Copyright 2005-2010 - Xcential Group LLC.
//
//-----------------------------------------------------------------------------

XDate.prototype = new XObject;
XDate.prototype.constructor = XDate;

//=============================================================================
// Constructor

function XDate(
   date,
   create
)
{
   date = (date == null) ? null : date;
   create = (create == null) ? true : false;

   if (create)
      return new XDate(date, false);

   //--------------------------------------------------------------------------
   // Private Interface

   //--------------------------------------------------------------------------
   // Privileged Interface

   this.valueOf = function()
   {

      return oDate;
   }

   //--------------------------------------------------------------------------

   this.setObjectValue = function(
      date
   )
   {
      date = (date == null) ? new Date() : date;

      oDate = XDate.toDate(date);

      return oDate;
   }

   //--------------------------------------------------------------------------
   // Initialization

   var oDate = this.setObjectValue(date);

}

XDate.prototype.objectClass = "XDate";

//=============================================================================
// Static Interface

XDate.JAN                              = "January";
XDate.FEB                              = "February";
XDate.MAR                              = "March";
XDate.APR                              = "April";
XDate.MAY                              = "May";
XDate.JUN                              = "June";
XDate.JUL                              = "July";
XDate.AUG                              = "August";
XDate.SEP                              = "September";
XDate.OCT                              = "October";
XDate.NOV                              = "November";
XDate.DEC                              = "December";

XDate.MONTHS = [
   XDate.JAN,
   XDate.FEB,
   XDate.MAR,
   XDate.APR,
   XDate.MAY,
   XDate.JUN,
   XDate.JUL,
   XDate.AUG,
   XDate.SEP,
   XDate.OCT,
   XDate.NOV,
   XDate.DEC
];

XDate.STYLE_MONTH_DAY_YEAR             = "month_day_year";
XDate.STYLE_MON_DAY_YEAR               = "mon_day_year";
XDate.STYLE_MON_DAY                    = "mon_day";
XDate.STYLE_MM_DD_YYYY                 = "mm_dd_yyyy";
XDate.STYLE_YYYY_MM_DD                 = "yyyy_mm_dd";
XDate.STYLE_YYYYMMDD                   = "yyyymmdd";
XDate.STYLE_TIME                       = "time";
XDate.STYLE_TIMESTAMP                  = "timestamp";
XDate.STYLE_ISO_8601                   = "iso_8601";
XDate.STYLE_RFC_3339                   = "rfc_3339";
XDate.STYLE_RFD_822                    = "rfc_822";
XDate.STYLE_SQL                        = "sql";
XDate.STYLE_SQL_DATE                   = "sql_date";
XDate.STYLE_ICAL                       = "ical";
XDate.STYLE_ICALZ                      = "icalz";
XDate.STYLE_SHELL                      = "shell";

XDate.TZ_UTC                           = true;
XDate.TZ_LOCAL                         = false;

XDate.SUN = "Sunday";
XDate.MON = "Monday";
XDate.TUE = "Tuesday";
XDate.WED = "Wednesday";
XDate.THU = "Thursday";
XDate.FRI = "Friday";
XDate.SAT = "Saturday";

XDate.DAYS = [
   XDate.SUN,
   XDate.MON,
   XDate.TUE,
   XDate.WED,
   XDate.THU,
   XDate.FRI,
   XDate.SAT
];

XDate.DAYS[XDate.SUN] = 0;
XDate.DAYS[XDate.MON] = 1;
XDate.DAYS[XDate.TUE] = 2;
XDate.DAYS[XDate.WED] = 3;
XDate.DAYS[XDate.THU] = 4;
XDate.DAYS[XDate.FRI] = 5;
XDate.DAYS[XDate.SAT] = 6;

XDate.ROUND_DOWN = true;
XDate.ROUND_UP = true;

//-----------------------------------------------------------------------------

XDate.isValidDate = function(
   value
)
{

   try
   {
      var test = XDate(value).toString();
      if (XString(test).isNothing || (/NaN/).test(test))
         return false;
   }
   catch (error)
   {
      return false;
   }

   return true;
}

//-----------------------------------------------------------------------------

XDate.toDate = function(
   value
)
{
   value = (value == undefined) ? (new Date()).toString() : value;

   if (value == null) // A null date is not now - but an undefined date is!
      return null;

   var date = new Date();
   switch (typeof(value))
   {
      case "date":
         date = new Date(String(value));
         break;
      case "string":
         value = XString(value).trim();
         value = value.replace(/-[0-9]{1,2}:[0-9]{1,2}$/,"");
         value = value.replace(/(\:[^\.]*)\.[^\.]*$/,"$1");
         value = value.replace(/([\/\-])([0-9][^0-9])/g,"$10$2");
         value = value.replace(/([\/\-])([0-9])$/,"$10$2");
         if (XMatch(value, /^([A-Z]+)\s*([0-9]+)\s*\,?$/i))
            value = XMatch.matches[1] + " " + XMatch.matches[2] + ", " + (new Date()).getFullYear();
         else if (XMatch(value, /^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9\:]+)$/)) // ISO 8601 (SQL)
            value = XMatch.matches[2] + "/" + XMatch.matches[3] + "/" + XMatch.matches[1] + " " + XMatch.matches[4];
         else if (XMatch(value, /^([0-9]{4})([0-9]{2})([0-9]{2})$/))
            value = XMatch.matches[2] + "/" + XMatch.matches[3] + "/" + XMatch.matches[1];
         else if (XMatch(value, /^([0-9]{4})[-\/]([0-9]{2})[-\/]([0-9]{2})$/))
            value = XMatch.matches[2] + "/" + XMatch.matches[3] + "/" + XMatch.matches[1];
         else if (XMatch(value, /^([4-9][0-9])[-\/]([0-9]{2})[-\/]([0-9]{2})$/))
            value = XMatch.matches[2] + "/" + XMatch.matches[3] + "/" + XMatch.matches[1];
         else if (XMatch(value, /^([0-9\-]+)T([0-9\:]+)([\-\+].+)$/))
            value = XMatch.matches[1].replace(/\-/g, "/") + " " + XMatch.matches[2] + " UTC" + XMatch.matches[3].replace(/\:/,"");
         else if (XMatch(value, /^([0-9]{2})[-/]([0-9]{2})[-/]([0-9]{4})$/))
            value = XMatch.matches[1] + "/" + XMatch.matches[2] + "/" + XMatch.matches[3];
         value = value.replace(/([0-9])(st|nd|rd|th)([\,\s])/i, "$1$3");
         value = value.replace(/-/g, "/");
         date = new Date(value);
         break;
      case "number":
         date = new Date(value);
         break;
      case "boolean":
         throw "Cannot create date from boolean.";
         break;
      case "function":
         try
         {
            date = new Date(value());
         }
         catch (error)
         {
            throw "Cannot create date from function. " + XMsg.getMsgText(error);
         }
         break;
      case "object":
         try
         {
            date = new Date(value.toString().replace(/([0-9])T([0-9])/,"$1 $2").replace(/^([0-9]{4})-([0-9]{2})-([0-9]{2})\s/, "$2-$3-$1 "));
         }
         catch (error)
         {
            try
            {
               date = new Date(value.valueOf);
            }
            catch (error)
            {
               throw "Cannot create date from object. " + XMsg.getMsgText(error);
            }
         }
         break;
   }

   return date;
}

//-----------------------------------------------------------------------------

XDate.createDate = function(
   hour,
   min,
   sec,
   month,
   day,
   year
)
{
   hour = (hour == null) ? 0 : hour;
   min = (min == null) ? 0 : min;
   sec = (sec == null) ? 0 : sec;
   month = (month == null) ? ((new Date()).getMonth()+1) : month;
   day = (day == null) ? (new Date()).getDate() : day;
   year = (year == null) ? (new Date()).getFullYear() : year;

   var dateString = month + "-" + day + "-" + year + " " + hour + ":" + min + ":" + sec;

   return XDate(dateString);
}

//-----------------------------------------------------------------------------

XDate.getNumDaysInMonth = function(
   month,
   year
)
{
   month = (month == null) ? XDate().getMonth() : Number(month);
   year = (year == null) ? XDate().getFullYear() : Number(year);

   if (month == 1)
      return ((year % 4) == 0) ? 29 : 28;

   if (month == 0 || month == 2 || month == 4 || month == 6 || month == 7 || month == 9 || month == 11)
      return 31;

   return 30;
}

//-----------------------------------------------------------------------------

XDate.prototype.normalizeTime = function(
   timeText
)
{

   timeText = timeText.replace(/[\s\.]/g,"").toLowerCase();

   var hour = 0;
   var min = 0;
   var sec = 0;

   if (XMatch(timeText,/^([0-9]+)/))
   {
      hour = Number(XMatch.matches[1]);
      timeText = XMatch.rightContext;
   }
   if (XMatch(timeText,/^\:?([0-9]+)/))
   {
      min = Number(XMatch.matches[1]);
      timeText = XMatch.rightContext;
   }
   if (XMatch(timeText,/^\:?([0-9]+)/))
   {
      sec = Number(XMatch.matches[1]);
      timeText = XMatch.rightContext;
   }
   if ((/pm/).test(timeText))
      hour += 12;

   return ("0" + hour).replace(/^.*(.{2})$/,"$1") + ":" + ("0" + min).replace(/^.*(.{2})$/,"$1") + ":" + ("0" + sec).replace(/^.*(.{2})$/,"$1");
}

//-----------------------------------------------------------------------------
// Public Interface

XDate.prototype.toString = function(
   style
)
{
   style = (style == null) ? XDate.STYLE_TIMESTAMP : style;

   var date = this.valueOf();
   if (style == XDate.STYLE_ICALZ)
   {
      var weekDay = date.getUTCDay();
      var day = date.getUTCDate();
      var month = date.getUTCMonth();
      var year = date.getUTCFullYear();
      var hour = date.getUTCHours();
      var min = date.getUTCMinutes();
      var sec = date.getUTCSeconds();
   }
   else
   {
      var weekDay = date.getDay();
      var day = date.getDate();
      var month = date.getMonth();
      var year = date.getFullYear();
      var hour = date.getHours();
      var min = date.getMinutes();
      var sec = date.getSeconds();
   }
   var offset = date.getTimezoneOffset() / 60;

   var dateText = "";
   switch (style)
   {
      case XDate.STYLE_MONTH_DAY_YEAR:
         dateText = XDate.MONTHS[month] + " " + String(day) + ", " + String(year);
         break;
      case XDate.STYLE_MON_DAY_YEAR:
         dateText = XDate.MONTHS[month].substr(0,3) + " " + String(day) + ", " + String(year);
         break;
      case XDate.STYLE_MON_DAY:
         dateText = XDate.MONTHS[month].substr(0,3) + " " + String(day);
         break;
      case XDate.STYLE_MM_DD_YYYY:
         dateText = ((month<9) ? "0" : "") + String(month+1) + "-" + ((day<10) ? "0" : "") + String(day) + "-" + String(year);
         break;
      case XDate.STYLE_YYYY_MM_DD:
      case XDate.STYLE_SQL_DATE:
         dateText = String(year) + "-" + ((month<9) ? "0" : "") + String(month+1) + "-" + ((day<10) ? "0" : "") + String(day);
         break;
      case XDate.STYLE_TIME:
         dateText = ((hour < 1) ? "12" : (hour > 12) ? (hour-12).toString() : hour.toString()) + ":" +
                    ((min < 10) ? "0" : "") + min.toString() + " " +
                    ((hour > 11) ? "PM" : "AM");
         break;
      case XDate.STYLE_YYYYMMDD:
         dateText = String(year) + ((month<9) ? "0" : "") + String(month+1) + ((day<10) ? "0" : "") + String(day);
         break
      case XDate.STYLE_TIMESTAMP:
      case XDate.STYLE_ISO_8601:
         dateText = year.toString() + "-" +
                    ((month < 9) ? "0":"") + (month+1).toString() + "-" +
                    ((day < 10) ? "0" : "") + day.toString() +
                    "T" +
                    ((hour < 10) ? "0" : "") + hour.toString() + ":" +
                    ((min < 10) ? "0" : "") + min.toString() + ":" +
                    ((sec < 10) ? "0" : "") + sec.toString();
                    //((offset > 0) ? "-" : "+") +
                    //((Math.abs(offset) < 10) ? "0" : "") + Math.abs(offset).toString() + ":" +
                    //"00";
         break;
      case XDate.STYLE_SQL:
         dateText = year.toString() + "-" +
                    ((month < 9) ? "0":"") + (month+1).toString() + "-" +
                    ((day < 10) ? "0" : "") + day.toString() +
                    " " +
                    ((hour < 10) ? "0" : "") + hour.toString() + ":" +
                    ((min < 10) ? "0" : "") + min.toString() + ":" +
                    ((sec < 10) ? "0" : "") + sec.toString();
                    //((offset > 0) ? "-" : "+") +
                    //((Math.abs(offset) < 10) ? "0" : "") + Math.abs(offset).toString() + ":" +
                    //"00";
         break;
      case XDate.STYLE_RFC_3339:
         dateText = year.toString() + "-" +
                    ((month < 9) ? "0":"") + (month+1).toString() + "-" +
                    ((day < 10) ? "0" : "") + day.toString() +
                    "T" +
                    ((hour < 10) ? "0" : "") + hour.toString() +  ":" +
                    ((min < 10) ? "0" : "") + min.toString() + ":" +
                    ((sec < 10) ? "0" : "") + sec.toString() +
                    ((offset > 0) ? "-" : "+") +
                    ((Math.abs(offset) < 10) ? "0" : "") + Math.abs(offset).toString() + ":" +
                    "00";
         break;
      case XDate.STYLE_RFC_822:
         dateText =  XDate.DAYS[weekday].substr(0,3) + ", " +
                    ((day < 10) ? "" : "") + day.toString() + " " +
                    ((month < 9) ? "":"") + (month+1).toString() + " " +
                    year.toString() + " " +
                    ((hour < 10) ? "0" : "") + hour.toString() + ":" +
                    ((min < 10) ? "0" : "") + min.toString() + ":" +
                    ((sec < 10) ? "0" : "") + sec.toString() + " " +
                    ((offset > 0) ? "-" : "+") +
                    ((Math.abs(offset) < 10) ? "0" : "") + Math.abs(offset).toString() +
                    "00";
         break;
      case XDate.STYLE_ICAL:
      case XDate.STYLE_ICALZ:
         dateText = year.toString() +
                    ((month < 9) ? "0":"") + (month+1).toString() +
                    ((day < 10) ? "0" : "") + day.toString() +
                    "T" +
                    ((hour < 10) ? "0" : "") + hour.toString() +
                    ((min < 10) ? "0" : "") + min.toString() +
                    ((sec < 10) ? "0" : "") + sec.toString() +
                    ((style == XDate.STYLE_ICALZ) ? "Z" : "");
                    //((offset > 0) ? "-" : "+") +
                    //((Math.abs(offset) < 10) ? "0" : "") + Math.abs(offset).toString() + ":" +
                    //"00";
         break;
      case XDate.STYLE_SHELL:
         dateText = (month+1).toString() + "/" +
                    day.toString() + "/" +
                    year.toString() + " " +
                    ((hour == 0) ? "12" : (hour < 13) ? hour.toString() : (hour-12).toString()) +  ":" +
                    ((min < 10) ? "0" : "") + min.toString() + ":" +
                    ((sec < 10) ? "0" : "") + sec.toString() + " " +
                    ((hour < 12) ? "AM" : "PM");
         break;
   }

   return dateText;
}

//-----------------------------------------------------------------------------

XDate.prototype.toDate = function()
{

   return new Date(this.valueOf().toString());
}

//-----------------------------------------------------------------------------

XDate.prototype.toNumber = function()
{

   return this.valueOf().valueOf();
}

//-----------------------------------------------------------------------------

XDate.prototype.getHours = function()
{

   var date = this.valueOf();
   return date.getHours();
}

//-----------------------------------------------------------------------------

XDate.prototype.getWeekday = function()
{

   var date = this.valueOf();
   return date.getDay();
}

//-----------------------------------------------------------------------------

XDate.prototype.getDay = function(
   timezone
)
{
   timezone = (timezone == null) ? XDate.TZ_LOCAL : timezone;

   var date = this.valueOf();
   return (timezone == XDate.TZ_LOCAL) ? date.getDate() : date.getUTCDate();
}

//-----------------------------------------------------------------------------

XDate.prototype.getMonth = function(
   timezone
)
{
   timezone = (timezone == null) ? XDate.TZ_LOCAL : timezone;

   var date = this.valueOf();
   return (timezone == XDate.TZ_LOCAL) ? date.getMonth() : date.getUTCMonth();
}

//-----------------------------------------------------------------------------

XDate.prototype.getFullYear = function(
   timezone
)
{
   timezone = (timezone == null) ? XDate.TZ_LOCAL : timezone;

   var date = this.valueOf();
   return (timezone == XDate.TZ_LOCAL) ? date.getFullYear() : date.getUTCFullYear();
}

//-----------------------------------------------------------------------------

XDate.prototype.getTime = function()
{

   var date = this.valueOf();

   var hours = date.getHours();
   var minutes = date.getMinutes();
   var seconds = date.getSeconds();
   var ampm = (hours > 11) ? "PM" : "AM";

   hours = (hours > 12) ? hours-12 : (hours == 0) ? 12 : hours;
   minutes = ("00" + minutes).replace(/^.*([0-9]{2})$/, "$1");
   seconds = ("00" + seconds).replace(/^.*([0-9]{2})$/, "$1");

   var time =  hours + ":" + minutes + ":" + seconds + " " + ampm;

   return time;
}

//-----------------------------------------------------------------------------

XDate.prototype.getDayNum = function()
{

   // Note: Daylight savings time throws a wrinkle into this calculation.
   //       As a result, the day number shifts off one day during the summer
   //       months. In order to correct for this, we always calculate the
   //       day num around noon - this way, the day will always be the same
   //       regardless of DST or  not.

   var date = XDate(this.toString(XDate.STYLE_YYYY_MM_DD) + "T12:00:00").toDate()

   var dayOne = new Date("1970/01/01");
   var dayNum = Math.floor((date.valueOf() - dayOne.valueOf())/1000/60/60/24);

   return dayNum;
}

//-----------------------------------------------------------------------------

XDate.prototype.isWithin = function(
   startDate,
   endDate
)
{
   startDate = (startDate == null) ? null : XDate(startDate).valueOf();
   endDate = (endDate == null) ? null : XDate(endDate).valueOf();

   var date = this.valueOf();

   if (startDate != null && date < startDate)
      return false;

   if (endDate != null && date > endDate)
      return false;

   return true;
}

//-----------------------------------------------------------------------------

XDate.prototype.isMidnight = function()
{

   var date = this.valueOf();

   if (date.getHours() != 0)
      return false;
   if (date.getMinutes() != 0)
      return false;
   if (date.getSeconds() != 0)
      return false;

   return true;
}

//-----------------------------------------------------------------------------

XDate.prototype.isToday = function()
{

   var date = this.valueOf();

   var today = new Date();
   if (date.getDate() != today.getDate())
      return false;
   if (date.getMonth() != today.getMonth())
      return false;
   if (date.getFullYear() != today.getFullYear())
      return false;

   return true;
}

//-----------------------------------------------------------------------------

XDate.prototype.isSoon = function(
   withinNumDays
)
{
   withinNumDays = (withinNumDays == null) ? 7 : withinNumDays;

   if (this.isToday())
      return true;

   var date = this.valueOf();

   var today = new Date();
   var withinMsecs = 1000 * 60 * 60 * 24 * withinNumDays;
   if ((today < date) && (date - today <= withinMsecs))
      return true;

   return false;
}

//-----------------------------------------------------------------------------

XDate.prototype.isRecent = function(
   withinNumDays
)
{
   withinNumDays = (withinNumDays == null) ? 7 : withinNumDays;

   if (this.isToday())
      return true;

   var date = this.valueOf();

   var now = new Date();
   var today = new Date((now.getMonth()+1) + "/" + now.getDate() + "/" + now.getFullYear());
   var withinMsecs = 1000 * 60 * 60 * 24 * withinNumDays;
   if ((today > date) && (today - date <= withinMsecs))
      return true;

   return false;
}

//-----------------------------------------------------------------------------

XDate.prototype.isBefore = function(
   otherXDate
)
{
   otherXDate = (otherXDate == null) ? null : XDate(otherXDate);

   if (!otherXDate)
      return false;

   var date = this.valueOf();

   return (date < otherXDate.toDate()) ? true : false;
}

//-----------------------------------------------------------------------------

XDate.prototype.isAfter = function(
   otherXDate
)
{
   otherXDate = (otherXDate == null) ? null : XDate(otherXDate);

   if (!otherXDate)
      return false;

   var date = this.valueOf();

   return (date > otherXDate.toDate()) ? true : false;
}

//-----------------------------------------------------------------------------

XDate.prototype.isSameDay = function(
   otherXDate
)
{
   otherXDate = (otherXDate == null) ? null : XDate(otherXDate);

   if (!otherXDate)
      return false;

   var date = this.valueOf();

   var otherDate = otherXDate.toDate();

   if (date.getFullYear() != otherDate.getFullYear())
      return false;

   if (date.getMonth() != otherDate.getMonth())
      return false;

   if (date.getDate() != otherDate.getDate())
      return false;

   return true;
}

//-----------------------------------------------------------------------------

XDate.prototype.ensureRecentYear = function()
{

   var date = this.valueOf();

   var year = date.getFullYear();
   date.setFullYear((year < 1950) ? year + 100 : year);

   this.setObjectValue(date);

   return this;
}

//-----------------------------------------------------------------------------

XDate.prototype.addDays = function(
   numDays,
   roundUp
)
{
   numDays = (numDays == null) ? 1 : numDays;

   var date = this.valueOf();

   date = new Date(1000 * 60 * 60 * 24 * numDays + date.valueOf());
   if (roundUp)
      date.setHours(23,59,59,0);

   //this.setObjectValue(date);

   return XDate(date);
}

//-----------------------------------------------------------------------------

XDate.prototype.subtractDays = function(
   numDays,
   roundDown
)
{
   numDays = (numDays == null) ? 1 : numDays;
   roundDown = (roundDown == null) ? false : roundDown;

   var date = this.valueOf();

   date = new Date(date.valueOf() - 1000 * 60 * 60 * 24 * numDays);
   if (roundDown)
      date.setHours(0,0,0,0);

   //this.setObjectValue(date);

   return XDate(date);
}

//=============================================================================




