/*!
* Copyright (c) 2009 Francesco Mele jsbeans@francescomele.com
*
* This Software is licenced under the LGPL Licence (GNU Lesser General
* Public License).
* In addition to the LGPL Licence the Software is subject to the
* following conditions:
*
* i every modification must be public and comunicated to the Author
* ii every "jsbean" added to this library must be self consistent
* except for the dependence from jsbeans-x.x.x.js
* iii 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.
*/
/**
* Pure javascript implementaion of <code>java.text.SimpleDateFormat</code>.
* <pre>
* Letter | Date or Time Component | Presentation | Examples
* G | Era designator | Text | AD
* y | Year | Year | 1996; 96
* M | Month in year | Month | July; Jul; 07
* w | Week in year | Number | 27
* W | Week in month | Number | 2
* D | Day in year | Number | 189
* d | Day in month | Number | 10
* F | Day of week in month | Number | 2
* E | Day in week | Text | Tuesday; Tue
* a | Am/pm marker | Text | PM
* H | Hour in day (0-23) | Number | 0
* k | Hour in day (1-24) | Number | 24
* K | Hour in am/pm (0-11) | Number | 0
* h | Hour in am/pm (1-12) | Number | 12
* m | Minute in hour | Number | 30
* s | Second in minute | Number | 55
* S | Millisecond | Number | 978
* z | Time zone | General time zone | Pacific Standard Time; PST; GMT-08:00
* Z | Time zone | RFC 822 time zone | -0800
* </pre>
* @namespace jsbeans
* @class SimpleDateFormat
* @param [pattern] {String} default "MM/dd/yyyy hh:mm"
* @constructor
* */
jsbeans.SimpleDateFormat = function() {
this.pattern = arguments[0] || "MM/dd/yyyy hh:mm";
//default Locale
this.locale = "en";
};
/**
* Array of default locales: english ('en') and italian ('it')
* @config jsbeans.SimpleDateFormat.LOCALES
* @static
* */
jsbeans.SimpleDateFormat.LOCALES = ["en", "it"];
/**
* Returns the available locales as set by <code class="prop">jsbeans.SimpleDateFormat.LOCALES</code> and, eventually, other than defaults.
* @method getAvailableLocales
* @static
* */
jsbeans.SimpleDateFormat.getAvailableLocales = function() {
return jsbeans.SimpleDateFormat.LOCALES;
};
/**
* A new Locale must be in the format
* <pre>
* {
* name: 'xx'// two chars, e.g. 'fr'
* months: ['first month', 'second month', ...]// month's names
* days: ['first day starting from sunday', 'day after sunday', ...]//day's names starting from sunday
* }
* </pre>
* @method addLocale
* @param locale {JSON}
* */
jsbeans.SimpleDateFormat.addLocale = function(locale) {
// a new Locale must be well formed ...
if (!locale || !locale.name || !locale.months || !locale.days) {
return;
}
// ... and months and days must be arrays of right length
if (locale.months.constructor.toLowerCase() != "array" || locale.days.constructor.toLowerCase() != "array" || locale.months.length != 12 || locale.days.length != 7) {
return;
}
// now we can go on
jsbeans.SimpleDateFormat.LOCALES.push(locale.name);
jsbeans.SimpleDateFormat.MONTH_NAMES[locale.name] = locale.months;
jsbeans.SimpleDateFormat.DAY_NAMES[locale.name] = locale.days;
};
/**
* Loads a script tag: <code><script type="text/javascript" src="{@param}scriptPrefixSlashEnded/SimpleDateFormatLocale_{@param}localeName.js"> </script></code>.<br/>
* As a choice of design we do not throws exceptions so no feedback will be return to user in case of error
* @method loadLocale
* @param localeName {String}
* @param [scriptPrefixSlashEnded] {String} optional url prefix. Default ""
* @static
* */
jsbeans.SimpleDateFormat.loadLocale = function(localeName /*, scriptPrefixSlashEnded*/) {
var head = document.getElementsByTagName("head")[0] || document.documentElement;
var script = document.createElement("script");
script.type = "text/javascript";
script.src = (arguments[1] || "") + "SimpleDateFormatLocale_" + localeName + ".js";
head.appendChild(script);
};
/**
* Array of month names for default locales.
* @config jsbeans.SimpleDateFormat.MONTH_NAMES
* @static
* */
jsbeans.SimpleDateFormat.MONTH_NAMES = {
en: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
,it: ["Gennaio", "Febbraio", "Marzo", "Aprile", "Maggio", "giugno", "Luglio", "Agosto", "Settembre", "Ottobre", "Novembre", "Dicembre"]
};
/**
* Array of day names for default locales.
* @config jsbeans.SimpleDateFormat.DAY_NAMES
* @static
* */
jsbeans.SimpleDateFormat.DAY_NAMES = {
en: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
,it: ["Domenica", "Luned\u00ec", "Marted\u00ec", "Mercoled\u00ec", "Gioved\u00ec", "Venerd\u00ec", "Sabato"]
};
/**
* Applies the given {@param} <code class="param">pattern</code> string to <code class="this">this</code> date format.
* @method applyPattern
* @param pattern {String}
* */
jsbeans.SimpleDateFormat.prototype.applyPattern = function(pattern) {
this.pattern = pattern;
};
/**
* Sets {@param} <code class="param">locale</code> to <code class="this">this</code> date format.<br/>
* The {@param} <code class="param">locale</code> must be loaded first, otherwise "en" will be used.
* @method setLocale
* @param locale {String}
* */
jsbeans.SimpleDateFormat.prototype.setLocale = function(locale) {
if (typeof locale == "undefined" || locale == null) {
locale = "en";
}
locale = locale.toLowerCase();;
var locales = jsbeans.SimpleDateFormat.getAvailableLocales();
for (var i = 0, l; l = locales[i]; i++) {
if (l == locale) {
this.locale = locale;
return;
}
}
this.locale = "en";
};
/**
* Returns the current locale
* @method getLocale
* @return {String}
* */
jsbeans.SimpleDateFormat.prototype.getLocale = function() {
return this.locale;
};
/**
* Returns the localized month's name from its index.
* @method getMonthName
* @param monthZeroBased {Integer} the index of month
* @return {String} the localized month's name
* */
jsbeans.SimpleDateFormat.prototype.getMonthName = function(monthZeroBased) {
return jsbeans.SimpleDateFormat.MONTH_NAMES[this.getLocale()][monthZeroBased];
};
/**
* Returns the localized day's name from its index.
* @method getDayName
* @param dayZeroBased {Integer} the index of day
* @return {String} the localized day's name
* */
jsbeans.SimpleDateFormat.prototype.getDayName = function(dayZeroBased) {
return jsbeans.SimpleDateFormat.DAY_NAMES[this.getLocale()][dayZeroBased];
};
/**
* Returns a formatted string starting from {@param} <code class="param">date</code> based on current pattern.
* @method format
* @param date {Date} the date to format
* @return {String} the formatted date
* */
jsbeans.SimpleDateFormat.prototype.format = function(date) {
var res = "";
var pad = function(str, len) {
while (str.length < len) {
str = "0" + str;
}
return str;
};
var formatText = function(data, letterOccurs, minLen) {
return (letterOccurs >= 4) ? data : data.substr(0, Math.max(minLen, letterOccurs));
};
var formatNumber = function(data, letterOccurs) {
return pad("" + data, letterOccurs);
};
var matches;
var searchString = this.pattern;
var matchedString, quoted, patternLetters, otherLetters, otherChars;
while ((matches = jsbeans.SimpleDateFormat._Constants.regex.exec(searchString))) {
matchedString = matches[0];
quoted = matches[1];
patternLetters = matches[2];
otherLetters = matches[3];
otherChars = matches[4];
if (quoted) {
if (quoted == "''") {
res += "'";
}
else {
res += quoted.substring(1, quoted.length - 1);
}
}
else if (otherLetters) {
// do nothing
}
else if (otherChars) {
res += otherChars;
}
else if (patternLetters) {
var patternLetter = patternLetters.charAt(0);
var letterOccurs = patternLetters.length;
var rawData = "";
switch (patternLetter) {
case "a":
rawData = (date.getHours() >= 12) ? "PM" : "AM";
break;
case "d":
rawData = date.getDate();
break;
case "D":
rawData = jsbeans.SimpleDateFormat._getDayInYear(date);
break;
case "E":
rawData = this.getDayName(date.getDay());
break;
case "F":
rawData = 1 + Math.floor((date.getDate() - 1) / 7);
break;
case "G":
rawData = "AD";
break;
case "h":
rawData = (date.getHours() % 12) || 12;
break;
case "H":
rawData = date.getHours();
break;
case "k":
rawData = date.getHours() || 24;
break;
case "K":
rawData = date.getHours() % 12;
break;
case "m":
rawData = date.getMinutes();
break;
case "M":
rawData = date.getMonth();
break;
case "s":
rawData = date.getSeconds();
break;
case "S":
rawData = date.getMilliseconds();
break;
case "w":
rawData = jsbeans.SimpleDateFormat._getWeekInYear(this._getMinimalDaysInFirstWeek(), date);
break;
case "W":
rawData = jsbeans.SimpleDateFormat._getWeekInMonth(this._getMinimalDaysInFirstWeek(), date);
break;
case "y":
rawData = date.getFullYear();
break;
case "Z":
rawData = date.getTimezoneOffset();
break;
}
switch (jsbeans.SimpleDateFormat._Constants.types[patternLetter]) {
case jsbeans.SimpleDateFormat._Constants.TEXT2:
res += formatText(rawData, letterOccurs, 2);
break;
case jsbeans.SimpleDateFormat._Constants.TEXT3:
res += formatText(rawData, letterOccurs, 3);
break;
case jsbeans.SimpleDateFormat._Constants.NUMBER:
res += formatNumber(rawData, letterOccurs);
break;
case jsbeans.SimpleDateFormat._Constants.YEAR:
if (letterOccurs <= 3) {
res += ("" + rawData).substr(2, 2);
}
else {
res += formatNumber(rawData, letterOccurs);
}
break;
case jsbeans.SimpleDateFormat._Constants.MONTH:
if (letterOccurs >= 3) {
res += formatText(this.getMonthName(rawData), letterOccurs, letterOccurs);
}
else {
res += formatNumber(rawData + 1, letterOccurs);
}
break;
case jsbeans.SimpleDateFormat._Constants.TIMEZONE:
var prefix = rawData > 0 ? "-" : "+";
var absData = Math.abs(rawData);
var hours = "" + Math.floor(absData / 60);
hours = pad(hours, 2);
var minutes = "" + (absData % 60);
minutes = pad(minutes, 2);
res += prefix + hours + minutes;
break;
}
}
searchString = searchString.substr(matches.index + matches[0].length);
}
return res;
};
/**
* @method _setMinimalDaysInFirstWeek
* @param days {Integer}
* @private
* */
jsbeans.SimpleDateFormat.prototype._setMinimalDaysInFirstWeek = function(days) {
this.minimalDaysInFirstWeek = days;
};
jsbeans.SimpleDateFormat.prototype._getMinimalDaysInFirstWeek = function(days) {
return typeof(this.minimalDaysInFirstWeek) == "undefined" ? 1 : this.minimalDaysInFirstWeek;
};
/**
* @config constants
* @type JSON
* @private
* @static
* @final
* */
jsbeans.SimpleDateFormat._Constants = {
regex: /('[^']*')|(G+|y+|M+|w+|W+|D+|d+|F+|E+|a+|H+|k+|K+|h+|m+|s+|S+|Z+)|([a-zA-Z]+)|([^a-zA-Z']+)/,
TEXT2: 0,
TEXT3: 1,
NUMBER: 2,
YEAR: 3,
MONTH: 4,
TIMEZONE: 5,
types: {
G: 0/*this.TEXT2*/,
y: 3/*this.YEAR*/,
M: 4/*this.MONTH*/,
w: 2/*this.NUMBER*/,
W: 2/*this.NUMBER*/,
D: 2/*this.NUMBER*/,
d: 2/*this.NUMBER*/,
F: 2/*this.NUMBER*/,
E: 1/*this.TEXT3*/,
a: 0/*this.TEXT2*/,
H: 2/*this.NUMBER*/,
k: 2/*this.NUMBER*/,
K: 2/*this.NUMBER*/,
h: 2/*this.NUMBER*/,
m: 2/*this.NUMBER*/,
s: 2/*this.NUMBER*/,
S: 2/*this.NUMBER*/,
Z: 5/*this.TIMEZONE*/
},
ONE_DAY: 86400000/*24 * 60 * 60 * 1000*/,
ONE_WEEK: 604800000 /*7 * this.ONE_DAY*/
};
/**
* Utility method
* @method _floorToMidnight
* @private
* @static
* */
jsbeans.SimpleDateFormat._floorToMidnight = function(year, month, day) {
var res = new Date(year, month, day, 0, 0, 0);
res.setMilliseconds(0);
return res;
};
/**
* Utility method
* @method _before
* @private
* @static
* */
jsbeans.SimpleDateFormat._before = function(d1, d2) {
return d1.getTime() < d2.getTime();
};
/**
* Utility method
* @method _getUTCTime
* @private
* @static
* */
jsbeans.SimpleDateFormat._getUTCTime = function(d) {
return Date.UTC(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds());
};
/**
* Utility method
* @method _getTimeDifference
* @private
* @static
* */
jsbeans.SimpleDateFormat._getTimeDifference = function(d1, d2) {
return jsbeans.SimpleDateFormat._getUTCTime(d1) - jsbeans.SimpleDateFormat._getUTCTime(d2);
};
/**
* Utility method
* @method _getPreviousSunday
* @private
* @static
* */
jsbeans.SimpleDateFormat._getPreviousSunday = function(d) {
var midday = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 12, 0, 0);
var prevSunday = new Date(midday.getTime() - d.getDay() * jsbeans.SimpleDateFormat._Constants.ONE_DAY);
return jsbeans.SimpleDateFormat._floorToMidnight(prevSunday.getFullYear(), prevSunday.getMonth(), prevSunday.getDate());
};
/**
* Utility method
* @method _getWeekInYear
* @private
* @static
* */
jsbeans.SimpleDateFormat._getWeekInYear = function(minimalDaysInFirstWeek, d) {
if (typeof(this.minimalDaysInFirstWeek) == "undefined") {
minimalDaysInFirstWeek = 1;
}
var prevSunday = jsbeans.SimpleDateFormat._getPreviousSunday(d);
var startOfYear = jsbeans.SimpleDateFormat._floorToMidnight(d.getFullYear(), 0, 1);
var sundays = jsbeans.SimpleDateFormat._before(prevSunday, startOfYear) ? 0 : 1 + Math.floor(jsbeans.SimpleDateFormat._getTimeDifference(prevSunday, startOfYear) / jsbeans.SimpleDateFormat._Constants.ONE_WEEK);
var daysInFirstWeek = 7 - startOfYear.getDay();
var res = sundays;
if (daysInFirstWeek < minimalDaysInFirstWeek) {
res--;
}
return res;
};
/**
* Utility method
* @method _getWeekInMonth
* @private
* @static
* */
jsbeans.SimpleDateFormat._getWeekInMonth = function(minimalDaysInFirstWeek, d) {
if (typeof(this.minimalDaysInFirstWeek) == "undefined") {
minimalDaysInFirstWeek = 1;
}
var prevSunday = jsbeans.SimpleDateFormat._getPreviousSunday(d);
var startOfMonth = jsbeans.SimpleDateFormat._floorToMidnight(d.getFullYear(), d.getMonth(), 1);
var sundays = jsbeans.SimpleDateFormat._before(prevSunday, startOfMonth) ? 0 : 1 + Math.floor((jsbeans.SimpleDateFormat._getTimeDifference(prevSunday, startOfMonth)) / jsbeans.SimpleDateFormat._Constants.ONE_WEEK);
var daysInFirstWeek = 7 - startOfMonth.getDay();
var res = sundays;
if (daysInFirstWeek >= minimalDaysInFirstWeek) {
res++;
}
return res;
};
/**
* Utility method
* @method _getDayInYear
* @private
* @static
* */
jsbeans.SimpleDateFormat._getDayInYear = function(d) {
var startOfYear = jsbeans.SimpleDateFormat._floorToMidnight(d.getFullYear(), 0, 1);
return 1 + Math.floor(jsbeans.SimpleDateFormat._getTimeDifference(d, startOfYear) / jsbeans.SimpleDateFormat._Constants.ONE_DAY);
};