text: ﻿/*
   * http://code.google.com/p/flexible-js-formatting/
   * 
   * Copyright (C) 2004 Baron Schwartz <baron at sequent dot org>
   *
   * Permission is hereby granted, free of charge, to any person obtaining a copy
   * of this software and associated documentation files (the "Software"), to deal
   * in the Software without restriction, including without limitation the rights
   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   * copies of the Software, and to permit persons to whom the Software is
   * furnished to do so, subject to the following conditions:
   *
   * The above copyright notice and this permission notice shall be included in
   * all copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
   * THE SOFTWARE.
   */
  
  Date.parseFunctions = {count:0};
  Date.parseRegexes = [];
  Date.formatFunctions = {count:0};
  
  Date.prototype.dateFormat = function(format, ignore_offset) {
      if (Date.formatFunctions[format] == null) {
          Date.createNewFormat(format);
      }
      var func = Date.formatFunctions[format];
      if (ignore_offset || ! this.offset) {
        return this[func]();
      } else {
        return (new Date(this.valueOf() - this.offset))[func]();
      }
  };
  
  Date.createNewFormat = function(format) {
      var funcName = "format" + Date.formatFunctions.count++;
      Date.formatFunctions[format] = funcName;
      var code = "Date.prototype." + funcName + " = function(){return ";
      var special = false;
      var ch = '';
      for (var i = 0; i < format.length; ++i) {
          ch = format.charAt(i);
  		// escape character start
          if (!special && ch == "\\") {
              special = true;
          }
  		// escaped string
          else if (!special && ch == '"') {
  			var end = format.indexOf('"', i+1);
  			if (end==-1)
  			{
  				end = format.length;
  			}
              code += "'" + String.escape(format.substring(i+1, end)) + "' + ";
  			i = end;
          }
  		// escaped character
          else if (special) {
              special = false;
              code += "'" + String.escape(ch) + "' + ";
          }
          else {
              code += Date.getFormatCode(ch);
          }
      }
      eval(code.substring(0, code.length - 3) + ";}");
  };
  
  Date.getFormatCode = function(character) {
      switch (character) {
      case "d":
          return "String.leftPad(this.getDate(), 2, '0') + ";
      case "D":
          return "Date.dayNames[this.getDay()].substring(0, 3) + ";
      case "j":
          return "this.getDate() + ";
      case "l":
          return "Date.dayNames[this.getDay()] + ";
      case "S":
          return "this.getSuffix() + ";
      case "w":
          return "this.getDay() + ";
      case "z":
          return "this.getDayOfYear() + ";
      case "W":
          return "this.getWeekOfYear() + ";
      case "F":
          return "Date.monthNames[this.getMonth()] + ";
      case "m":
          return "String.leftPad(this.getMonth() + 1, 2, '0') + ";
      case "M":
          return "Date.monthNames[this.getMonth()].substring(0, 3) + ";
      case "n":
          return "(this.getMonth() + 1) + ";
      case "t":
          return "this.getDaysInMonth() + ";
      case "L":
          return "(this.isLeapYear() ? 1 : 0) + ";
      case "Y":
          return "this.getFullYear() + ";
      case "y":
          return "('' + this.getFullYear()).substring(2, 4) + ";
      case "a":
          return "(this.getHours() < 12 ? 'am' : 'pm') + ";
      case "A":
          return "(this.getHours() < 12 ? 'AM' : 'PM') + ";
      case "g":
          return "((this.getHours() %12) ? this.getHours() % 12 : 12) + ";
      case "G":
          return "this.getHours() + ";
      case "h":
          return "String.leftPad((this.getHours() %12) ? this.getHours() % 12 : 12, 2, '0') + ";
      case "H":
          return "String.leftPad(this.getHours(), 2, '0') + ";
      case "i":
          return "String.leftPad(this.getMinutes(), 2, '0') + ";
      case "s":
          return "String.leftPad(this.getSeconds(), 2, '0') + ";
      case "X":
          return "String.leftPad(this.getMilliseconds(), 3, '0') + ";
      case "O":
          return "this.getGMTOffset() + ";
      case "T":
          return "this.getTimezone() + ";
      case "Z":
          return "(this.getTimezoneOffset() * -60) + ";
      case "q":	// quarter num, Q for name?
          return "this.getQuarter() + ";
      default:
          return "'" + String.escape(character) + "' + ";
      }
  };
  
  Date.parseDate = function(input, format) {
      if (Date.parseFunctions[format] == null) {
          Date.createParser(format);
      }
      var func = Date.parseFunctions[format];
      return Date[func](input);
  };
  
  Date.createParser = function(format) {
      var funcName = "parse" + Date.parseFunctions.count++;
      var regexNum = Date.parseRegexes.length;
      var currentGroup = 1;
      Date.parseFunctions[format] = funcName;
  
      var code = "Date." + funcName + " = function(input){\n"
          + "var y = -1, m = -1, d = -1, h = -1, i = -1, s = -1, ms = -1, z = 0;\n"
          + "var d = new Date();\n"
          + "y = d.getFullYear();\n"
          + "m = d.getMonth();\n"
          + "d = d.getDate();\n"
          + "var results = input.match(Date.parseRegexes[" + regexNum + "]);\n"
          + "if (results && results.length > 0) {" ;
      var regex = "";
  
      var special = false;
      var ch = '';
      for (var i = 0; i < format.length; ++i) {
          ch = format.charAt(i);
          if (!special && ch == "\\") {
              special = true;
          }
          else if (special) {
              special = false;
              regex += String.escape(ch);
          }
          else {
              obj = Date.formatCodeToRegex(ch, currentGroup);
              currentGroup += obj.g;
              regex += obj.s;
              if (obj.g && obj.c) {
                  code += obj.c;
              }
          }
      }
  
      code += "if (y > 0 && m >= 0 && d > 0 && h >= 0 && i >= 0 && s >= 0 && ms >= 0)\n"
          + "{return new Date(y, m, d, h, i, s, ms).applyOffset(z);}\n"
          + "if (y > 0 && m >= 0 && d > 0 && h >= 0 && i >= 0 && s >= 0)\n"
          + "{return new Date(y, m, d, h, i, s).applyOffset(z);}\n"
          + "else if (y > 0 && m >= 0 && d > 0 && h >= 0 && i >= 0)\n"
          + "{return new Date(y, m, d, h, i).applyOffset(z);}\n"
          + "else if (y > 0 && m >= 0 && d > 0 && h >= 0)\n"
          + "{return new Date(y, m, d, h).applyOffset(z);}\n"
          + "else if (y > 0 && m >= 0 && d > 0)\n"
          + "{return new Date(y, m, d).applyOffset(z);}\n"
          + "else if (y > 0 && m >= 0)\n"
          + "{return new Date(y, m).applyOffset(z);}\n"
          + "else if (y > 0)\n"
          + "{return new Date(y).applyOffset(z);}\n"
          + "}return null;}";
  
      Date.parseRegexes[regexNum] = new RegExp("^" + regex + "$");
      eval(code);
  };
  
  Date.formatCodeToRegex = function(character, currentGroup) {
      switch (character) {
      case "D":
          return {g:0,
          c:null,
          s:"(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)"};
      case "j":
      case "d":
          return {g:1,
              c:"d = parseInt(results[" + currentGroup + "], 10);\n",
              s:"(\\d{1,2})"};
      case "l":
          return {g:0,
              c:null,
              s:"(?:" + Date.dayNames.join("|") + ")"};
      case "S":
          return {g:0,
              c:null,
              s:"(?:st|nd|rd|th)"};
      case "w":
          return {g:0,
              c:null,
              s:"\\d"};
      case "z":
          return {g:0,
              c:null,
              s:"(?:\\d{1,3})"};
      case "W":
          return {g:0,
              c:null,
              s:"(?:\\d{2})"};
      case "F":
          return {g:1,
              c:"m = parseInt(Date.monthNumbers[results[" + currentGroup + "].substring(0, 3)], 10);\n",
              s:"(" + Date.monthNames.join("|") + ")"};
      case "M":
          return {g:1,
              c:"m = parseInt(Date.monthNumbers[results[" + currentGroup + "]], 10);\n",
              s:"(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)"};
      case "n":
      case "m":
          return {g:1,
              c:"m = parseInt(results[" + currentGroup + "], 10) - 1;\n",
              s:"(\\d{1,2})"};
      case "t":
          return {g:0,
              c:null,
              s:"\\d{1,2}"};
      case "L":
          return {g:0,
              c:null,
              s:"(?:1|0)"};
      case "Y":
          return {g:1,
              c:"y = parseInt(results[" + currentGroup + "], 10);\n",
              s:"(\\d{4})"};
      case "y":
          return {g:1,
              c:"var ty = parseInt(results[" + currentGroup + "], 10);\n"
                  + "y = ty > Date.y2kYear ? 1900 + ty : 2000 + ty;\n",
              s:"(\\d{1,2})"};
      case "a":
          return {g:1,
              c:"if (results[" + currentGroup + "] == 'am') {\n"
                  + "if (h == 12) { h = 0; }\n"
                  + "} else { if (h < 12) { h += 12; }}",
              s:"(am|pm)"};
      case "A":
          return {g:1,
              c:"if (results[" + currentGroup + "] == 'AM') {\n"
                  + "if (h == 12) { h = 0; }\n"
                  + "} else { if (h < 12) { h += 12; }}",
              s:"(AM|PM)"};
      case "g":
      case "G":
      case "h":
      case "H":
          return {g:1,
              c:"h = parseInt(results[" + currentGroup + "], 10);\n",
              s:"(\\d{1,2})"};
      case "i":
          return {g:1,
              c:"i = parseInt(results[" + currentGroup + "], 10);\n",
              s:"(\\d{2})"};
      case "s":
          return {g:1,
              c:"s = parseInt(results[" + currentGroup + "], 10);\n",
              s:"(\\d{2})"};
      case "X":
        return {g:1,
            c:"ms = parseInt(results[" + currentGroup + "], 10);\n",
            s:"(\\d{3})"};
      case "O":
      case "P":
          return {g:1,
              c:"z = Date.parseOffset(results[" + currentGroup + "], 10);\n",
              s:"(Z|[+-]\\d{2}:?\\d{2})"}; // "Z", "+05:00", "+0500" all acceptable.
      case "T":
          return {g:0,
              c:null,
              s:"[A-Z]{3}"};
      case "Z":
          return {g:1,
              c:"s = parseInt(results[" + currentGroup + "], 10);\n",
              s:"([+-]\\d{1,5})"};
      default:
          return {g:0,
              c:null,
              s:String.escape(character)};
      }
  };
  
  Date.parseOffset = function(str) {
    if (str == "Z") { return 0 ; } // UTC, no offset.
    var seconds ;
    seconds = parseInt(str[0] + str[1] + str[2]) * 3600 ; // e.g., "+05" or "-08"
    if (str[3] == ":") {            // "+HH:MM" is preferred iso8601 format ("O")
      seconds += parseInt(str[4] + str[5]) * 60;
    } else {                      // "+HHMM" is frequently used, though. ("P")
      seconds += parseInt(str[3] + str[4]) * 60;
    }
    return seconds ;
  };
  
  // convert the parsed date into UTC, but store the offset so we can optionally use it in dateFormat()
  Date.prototype.applyOffset = function(offset_seconds) {
    this.offset = offset_seconds * 1000 ;
    this.setTime(this.valueOf() + this.offset);
    return this ;
  };
  
  Date.prototype.getTimezone = function() {
      return this.toString().replace(
          /^.*? ([A-Z]{3}) [0-9]{4}.*$/, "$1").replace(
          /^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, "$1$2$3").replace(
          /^.*?[0-9]{4} \(([A-Z]{3})\)/, "$1");
  };
  
  Date.prototype.getGMTOffset = function() {
      return (this.getTimezoneOffset() > 0 ? "-" : "+")
          + String.leftPad(Math.floor(this.getTimezoneOffset() / 60), 2, "0")
          + String.leftPad(this.getTimezoneOffset() % 60, 2, "0");
  };
  
  Date.prototype.getDayOfYear = function() {
      var num = 0;
      Date.daysInMonth[1] = this.isLeapYear() ? 29 : 28;
      for (var i = 0; i < this.getMonth(); ++i) {
          num += Date.daysInMonth[i];
      }
      return num + this.getDate() - 1;
  };
  
  Date.prototype.getWeekOfYear = function() {
      // Skip to Thursday of this week
      var now = this.getDayOfYear() + (4 - this.getDay());
      // Find the first Thursday of the year
      var jan1 = new Date(this.getFullYear(), 0, 1);
      var then = (7 - jan1.getDay() + 4);
      document.write(then);
      return String.leftPad(((now - then) / 7) + 1, 2, "0");
  };
  
  Date.prototype.isLeapYear = function() {
      var year = this.getFullYear();
      return ((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)));
  };
  
  Date.prototype.getFirstDayOfMonth = function() {
      var day = (this.getDay() - (this.getDate() - 1)) % 7;
      return (day < 0) ? (day + 7) : day;
  };
  
  Date.prototype.getLastDayOfMonth = function() {
      var day = (this.getDay() + (Date.daysInMonth[this.getMonth()] - this.getDate())) % 7;
      return (day < 0) ? (day + 7) : day;
  };
  
  Date.prototype.getDaysInMonth = function() {
      Date.daysInMonth[1] = this.isLeapYear() ? 29 : 28;
      return Date.daysInMonth[this.getMonth()];
  };
  Date.prototype.getQuarter = function() {
      return Date.quarterFromMonthNum[this.getMonth()];
  };
  
  Date.prototype.getSuffix = function() {
      switch (this.getDate()) {
          case 1:
          case 21:
          case 31:
              return "st";
          case 2:
          case 22:
              return "nd";
          case 3:
          case 23:
              return "rd";
          default:
              return "th";
      }
  };
  
  String.escape = function(string) {
      return string.replace(/('|\\)/g, "\\$1");
  };
  
  String.leftPad = function (val, size, ch) {
      var result = new String(val);
      if (ch == null) {
          ch = " ";
      }
      while (result.length < size) {
          result = ch + result;
      }
      return result;
  };
  
  Date.quarterFromMonthNum = [1,1,1,2,2,2,3,3,3,4,4,4];
  Date.daysInMonth = [31,28,31,30,31,30,31,31,30,31,30,31];
  Date.monthNames =
     ["January",
      "February",
      "March",
      "April",
      "May",
      "June",
      "July",
      "August",
      "September",
      "October",
      "November",
      "December"];
  Date.dayNames =
     ["Sunday",
      "Monday",
      "Tuesday",
      "Wednesday",
      "Thursday",
      "Friday",
      "Saturday"];
  Date.y2kYear = 50;
  Date.monthNumbers = {
      Jan:0,
      Feb:1,
      Mar:2,
      Apr:3,
      May:4,
      Jun:5,
      Jul:6,
      Aug:7,
      Sep:8,
      Oct:9,
      Nov:10,
      Dec:11};
  Date.patterns = {
      ISO8601LongPattern: "Y\\-m\\-d\\TH\\:i\\:sO",
      ISO8601ShortPattern: "Y\\-m\\-d",
      ShortDatePattern: "n/j/Y",
      LongDatePattern: "l, F d, Y",
      FullDateTimePattern: "l, F d, Y g:i:s A",
      MonthDayPattern: "F d",
      ShortTimePattern: "g:i A",
      LongTimePattern: "g:i:s A",
      SortableDateTimePattern: "Y-m-d\\TH:i:s",
      UniversalSortableDateTimePattern: "Y-m-d H:i:sO",
      YearMonthPattern: "F, Y"};
  