Date.MILLISECOND = 1;
Date.ONESECOND   = 1000;
Date.ONEMINUTE   = 60 * Date.ONESECOND;
Date.ONEHOUR     = 60 * Date.ONEMINUTE;
Date.ONEDAY      = 24 * Date.ONEHOUR;
Date.ONEWEEK     =  7 * Date.ONEDAY;
Date.ONEMONTH    = 30 * Date.ONEDAY;
Date.ISOFORMAT   = "yyyy-MM-dd'T'HH:mm:ss'Z'";


/**
 * format a Date to a string
 * @param String Format pattern
 * @param Object Java Locale Object (optional)
 * @param Object Java TimeZone Object (optional)
 * @return String formatted Date
 */
Date.prototype.format = function (format, locale, timezone) {
   if (!format)
      return this.toString();
   var sdf = locale ? new java.text.SimpleDateFormat(format, locale)
                    : new java.text.SimpleDateFormat(format);
   if (timezone && timezone != sdf.getTimeZone())
      sdf.setTimeZone(timezone);
   return sdf.format(this);
}


/** 
 * set the date/time to UTC by subtracting
 * the timezone offset
 */ 
Date.prototype.toUTC = function() {
   this.setMinutes(this.getMinutes() + this.getTimezoneOffset());
}


/** 
 * set the date/time to local time by adding
 * the timezone offset
 */ 
Date.prototype.toLocalTime = function() {
   this.setMinutes(this.getMinutes() - this.getTimezoneOffset());
}


/**
 * returns the difference between this and another
 * date object in milliseconds
 */
Date.prototype.diff = function(dateObj) {
   return this.getTime() - dateObj.getTime();
}


/**
 * Returns an object containing yyyy, MM, dd, mm, ss, ms
 * The resulting object contains:<pre>
 *    .year
 *    .month
 *    .day
 *    .minute
 *    .second
 *    .millisecond</pre>
 * 
 * @return Object
 */
Date.prototype.splitValues = function () {
   var result = {
      year:        parseInt(this.format("yyyy")),
      month:       parseInt(this.format("M")),
      day:         parseInt(this.format("d")),
      minute:      parseInt(this.format("m")),
      second:      parseInt(this.format("s")),
      millisecond: parseInt(this.format("sss"))
   }
   return result;
}


/**
 * return the timespan to current date/time or a different Date object
 * @param Object parameter object containing optional properties:
 *               .now = String to use if difference is < 1 minute
 *               .day|days = String to use for single|multiple day(s)
 *               .hour|hours = String to use for single|multiple hour(s)
 *               .minute|minutes = String to use for single|multiple minute(s)
 *               .date = Date object to use for calculating the timespan
 * @return Object containing properties:
 *                .isFuture = (Boolean)
 *                .span = (String) timespan
 * @see Date.prototype.getAge, Date.prototype.getExpiry
 */
Date.prototype.getTimespan = function(param) {
   if (!param)
      param = {date: new Date()};
   else if (!param.date)
      param.date = new Date();

   var result = {isFuture: this > param.date};
   var date = this.splitValues();
   var now = param.date.splitValues();
   var age = {};

   // calculate years
   age.years = now["year"] - date["year"];
   if ((date["month"] >= now["month"]) || (date["month"] == now["month"] && date["day"] >= now["day"])) {
      age.years --;
   }
   // calculate months
   age.months = ( (date["month"] < now["month"]) ? (now["month"] - date["month"]) : (12 - date["month"] + now["month"]));
   if (now["day"] < date["day"]) {
      age.months --;
   }
   age.absMonths = age.years * 12 + age.months;
   // calculate days
   // calculating days, hours, minutes, seconds and milliseconds
   var diff = Math.abs(param.date.diff(this));
   age.days = Math.floor((diff % Date.ONEMONTH) / Date.ONEDAY);
   age.absDays = Math.floor(diff / Date.ONEDAY);
   age.hours = Math.floor((diff % Date.ONEDAY) / Date.ONEHOUR);
   age.absHours = Math.floor(diff / Date.ONEHOUR);
   age.minutes = Math.floor((diff % Date.ONEHOUR) / Date.ONEMINUTE);
   age.absMinutes = Math.floor(diff / Date.ONEMINUTE);
   age.seconds = Math.floor((diff % Date.ONEMINUTE) / Date.ONESECOND);
   age.absSeconds = Math.floor(diff / Date.ONESECOND);
   age.milliseconds = Math.floor((diff % Date.ONESECOND));
   age.absMilliseconds = diff;

   result.age = age;

   res.push();
   if (diff < Date.ONEMINUTE)
      res.write(param.now || "now");
   else {
      var arr = [{one: "day", many: "days"},
                 {one: "hour", many: "hours"},
                 {one: "minute", many: "minutes"}];
      for (var i in arr) {
         var value = age[arr[i].many];
         if (value != 0) {
            var prop = (value == 1 ? arr[i].one : arr[i].many);
            res.write(value);
            res.write(" ");
            res.write(param[prop] || prop);
            if (i < arr.length -1)
               res.write(param.delimiter || ", ");
         }
      }
   }
   result.span = res.pop();
   return result;
}

/**
 * return the past timespan between this Date object and
 * the current Date or a different Date object
 * @see Date.prototype.getTimespan
 */
Date.prototype.getAge = function(param) {
   var age = this.getTimespan(param);
   if (!age.isFuture)
      return age.span;
   return null;
}

/**
 * return the future timespan between this Date object and
 * the current Date or a different Date object
 * @see Date.prototype.getTimespan
 */
Date.prototype.getExpiry = function(param) {
   var age = this.getTimespan(param);
   if (age.isFuture)
      return age.span;
   return null;
}


/**
 * Returns if the Date is a leap year
 *
 * @return Boolean
 */
Date.prototype.isLeapYear = function (a) {
   var y = this.getFullYear();
   return (((a%4 == 0) && (a%100 != 0)) || (a%400 == 0));
}


/**
 * Returns an Array (starting with 1 for jannuary) containing the days in month
 *
 * @return String
 */
Date.prototype.getStarSign = function() {
   var date = this.splitValues();
   var horroscopes = [ 20, 19, 21, 20, 21, 22, 23, 23, 23, 23, 22, 22 ];
   var starsigns = [
      "Capricorn",
      "Aquarius",
      "Pisces",
      "Aries",
      "Taurus",
      "Gemini",
      "Cancer",
      "Leo",
      "Virgo",
      "Libra",
      "Scorpio",
      "Sagittarius"
   ];

   if (date["day"] < horroscopes[date["month"] - 1])
      return starsigns[date["month"] - 1];
   else if (date["month"] == 12)
      return starsigns[0];
   else
      return starsigns[date["month"]];
}

