UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

1,718 lines (1,462 loc) 50.8 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2006 STZ-IDA, Germany, http://www.stz-ida.de License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * Til Schneider (til132) * Fabian Jakobs (fjakobs) ************************************************************************ */ /** * A formatter and parser for dates, see * http://www.unicode.org/reports/tr35/#Date_Format_Patterns * * Here is a quick overview of the format pattern keys: * <table> * <tr><th>Key &nbsp;<th>Description * <tr><td><code> G </code><td> era, e.g. "AD" * <tr><td><code> y </code><td> year * <tr><td><code> Y </code><td> week year * <tr><td><code> u </code><td> extended year [Not supported yet] * <tr><td><code> Q </code><td> quarter * <tr><td><code> q </code><td> stand-alone quarter * <tr><td><code> M </code><td> month * <tr><td><code> L </code><td> stand-alone month * <tr><td><code> I </code><td> chinese leap month [Not supported yet] * <tr><td><code> w </code><td> week of year * <tr><td><code> W </code><td> week of month * <tr><td><code> d </code><td> day of month * <tr><td><code> D </code><td> day of year * <tr><td><code> F </code><td> day of week in month [Not supported yet] * <tr><td><code> g </code><td> modified Julian day [Not supported yet] * <tr><td><code> E </code><td> day of week * <tr><td><code> e </code><td> local day of week * <tr><td><code> c </code><td> stand-alone local day of week * <tr><td><code> a </code><td> period of day (am or pm) * <tr><td><code> h </code><td> 12-hour hour * <tr><td><code> H </code><td> 24-hour hour * <tr><td><code> K </code><td> hour [0-11] * <tr><td><code> k </code><td> hour [1-24] * <tr><td><code> j </code><td> special symbol [Not supported yet] * <tr><td><code> m </code><td> minute * <tr><td><code> s </code><td> second * <tr><td><code> S </code><td> fractional second * <tr><td><code> A </code><td> millisecond in day [Not supported yet] * <tr><td><code> z </code><td> time zone, specific non-location format * <tr><td><code> Z </code><td> time zone, rfc822/gmt format * <tr><td><code> v </code><td> time zone, generic non-location format [Not supported yet] * <tr><td><code> V </code><td> time zone, like z except metazone abbreviations [Not supported yet] * </table> * * (This list is preliminary, not all format keys might be implemented). Most * keys support repetitions that influence the meaning of the format. Parts of the * format string that should not be interpreted as format keys have to be * single-quoted. * * The same format patterns will be used for both parsing and output formatting. * * NOTE: Instances of this class must be disposed of after use * */ qx.Class.define("qx.util.format.DateFormat", { extend : qx.core.Object, implement : [ qx.util.format.IFormat ], /* ***************************************************************************** CONSTRUCTOR ***************************************************************************** */ /** * @param format {String|null} The format to use. If null, the locale's default * format is used. * @param locale {String?} optional locale to be used. In case this is not present, the {@link #locale} property of DateFormat * will be following the {@link qx.locale.Manager#locale} property of qx.locale.Manager */ construct : function(format, locale) { this.base(arguments); this.__initialLocale = this.__locale = locale; if (format != null) { this.__format = format.toString(); if(this.__format in qx.util.format.DateFormat.ISO_MASKS) { if(this.__format === 'isoUtcDateTime') { this.__UTC = true; } this.__format = qx.util.format.DateFormat.ISO_MASKS[this.__format]; } } else { this.__format = qx.locale.Date.getDateFormat("long", this.getLocale()) + " " + qx.locale.Date.getDateTimeFormat("HHmmss", "HH:mm:ss", this.getLocale()); } }, /* ***************************************************************************** STATICS ***************************************************************************** */ statics : { /** * Convenience factory that returns a <code>DateFomat</code> instance that * uses a short date-only format. Beware that the overall layout of the * date/time format string is that of the locale in effect when the factory * function is called. * * Implemented as a quasi-singleton, so beware of side effects. * * @return {DateFormat} a DateFormat instance. */ getDateInstance : function() { var DateFormat = qx.util.format.DateFormat; var format = qx.locale.Date.getDateFormat("short") + ""; // Memoizing the instance, so caller doesn't have to dispose it. if (DateFormat._dateInstance == null || DateFormat._dateInstance.__format != format) { DateFormat._dateInstance = new DateFormat(format); } return DateFormat._dateInstance; }, /** * Convenience factory that returns a <code>DateFomat</code> instance that * uses a long date/time format. Beware that the overall layout of the * date/time format string is that of the locale in effect when the factory * function is called. * * Implemented as a quasi-singleton, so beware of side effects. * * @return {DateFormat} a DateFormat instance. */ getDateTimeInstance : function() { var DateFormat = qx.util.format.DateFormat; var format = qx.locale.Date.getDateFormat("long") + " " + qx.locale.Date.getDateTimeFormat("HHmmss", "HH:mm:ss"); // Memoizing the instance, so caller doesn't have to dispose it. if (DateFormat._dateTimeInstance == null || DateFormat._dateTimeInstance.__format != format) { DateFormat._dateTimeInstance = new DateFormat(format); } return DateFormat._dateTimeInstance; }, /** * @type {Integer} The threshold until when a year should be assumed to belong to the * 21st century (e.g. 12 -> 2012). Years over this threshold but below 100 will be * assumed to belong to the 20th century (e.g. 88 -> 1988). Years over 100 will be * used unchanged (e.g. 1792 -> 1792). */ ASSUME_YEAR_2000_THRESHOLD : 30, /** @type {Map} Special masks of patterns that are used frequently*/ ISO_MASKS : { isoDate : "yyyy-MM-dd", isoTime : "HH:mm:ss", isoDateTime : "yyyy-MM-dd'T'HH:mm:ss", isoUtcDateTime : "yyyy-MM-dd'T'HH:mm:ss'Z'" }, /** @type {String} The am marker. */ AM_MARKER : "am", /** @type {String} The pm marker. */ PM_MARKER : "pm" }, /* ***************************************************************************** MEMBERS ***************************************************************************** */ members : { __locale : null, __initialLocale : null, __format : null, __parseFeed : null, __parseRules : null, __formatTree : null, __UTC : null, /** * Fills a number with leading zeros ("25" -> "0025"). * * @param number {Integer} the number to fill. * @param minSize {Integer} the minimum size the returned string should have. * @return {String} the filled number as string. */ __fillNumber : function(number, minSize) { var str = "" + (number < 0 ? ((-1) * number) : number); while (str.length < minSize) { str = "0" + str; } return number < 0 ? "-" + str : str; }, /** * Returns the day in year of a date. * * @param date {Date} the date. * @return {Integer} the day in year. */ __getDayInYear : function(date) { var helpDate = new Date(date.getTime()); var day = helpDate.getDate(); while (helpDate.getMonth() != 0) { // Set the date to the last day of the previous month helpDate.setDate(-1); day += helpDate.getDate() + 1; } return day; }, /** * Returns the thursday in the same week as the date. * * @param date {Date} the date to get the thursday of. * @return {Date} the thursday in the same week as the date. */ __thursdayOfSameWeek : function(date) { return new Date(date.getTime() + (3 - ((date.getDay() + 6) % 7)) * 86400000); }, /** * Returns the week in year of a date. * * @param date {Date} the date to get the week in year of. * @return {Integer} the week in year. */ __getWeekInYear : function(date) { // The following algorithm comes from http://www.salesianer.de/util/kalwoch.html // Get the thursday of the week the date belongs to var thursdayDate = this.__thursdayOfSameWeek(date); // Get the year the thursday (and therefore the week) belongs to var weekYear = thursdayDate.getFullYear(); // Get the thursday of the week january 4th belongs to // (which defines week 1 of a year) var thursdayWeek1 = this.__thursdayOfSameWeek(new Date(weekYear, 0, 4)); // Calculate the calendar week return Math.floor(1.5 + (thursdayDate.getTime() - thursdayWeek1.getTime()) / 86400000 / 7); }, /** * Returns the week in month of a date. * * @param date {Date} the date to get the week in year of. * @return {Integer} the week in month. */ __getWeekInMonth : function(date) { var thursdayDate = this.__thursdayOfSameWeek(date); var thursdayWeek1 = this.__thursdayOfSameWeek(new Date(date.getFullYear(), date.getMonth(), 4)); return Math.floor(1.5 + (thursdayDate.getTime() - thursdayWeek1.getTime()) / 86400000 / 7); }, /** * Returns the week year of a date. (that is the year of the week where this date happens to be) * For a week in the middle of the summer, the year is easily obtained, but for a week * when New Year's Eve takes place, the year of that week is ambiguous. * The thursday day of that week is used to determine the year. * * @param date {Date} the date to get the week in year of. * @return {Integer} the week year. */ __getWeekYear : function(date) { var thursdayDate = this.__thursdayOfSameWeek(date); return thursdayDate.getFullYear(); }, /** * Returns true if the year is a leap one. * * @param year {Integer} the year to check. * @return {Boolean} true if it is a leap year. */ __isLeapYear : function(year) { var februaryDate = new Date(year,2,1); februaryDate.setDate(-1); return februaryDate.getDate() + 1 === 29; }, /** * Returns a json object with month and day as keys. * * @param dayOfYear {Integer} the day of year. * @param year {Integer} the year to check. * @return {Object} a json object {month: M, day: D}. */ __getMonthAndDayFromDayOfYear : function(dayOfYear,year) { var month = 0; var day = 0; // if we don't know the year, we take a non-leap year' if(!year) { year = 1971; } var dayCounter = 0; for(var i=1; i <= 12; i++) { var tempDate = new Date(year,i,1); tempDate.setDate(-1); var days = tempDate.getDate() + 1; dayCounter += days; if(dayCounter < dayOfYear) { month++; day += days; } else { day = dayOfYear - (dayCounter-days); break; } } return {month: month,day: day}; }, /** * Returns the year of a date when we know the week year * * @param weekYear {Integer} the week year. * @param month {Integer} the month * @param dayOfMonth {Integer} the day in month * @return {Integer} the year. */ __getYearFromWeekYearAndMonth : function(weekYear, month, dayOfMonth) { var year; switch(month){ case 11 : year = weekYear - 1; if (weekYear != this.__getWeekYear(new Date(year,month,dayOfMonth))) { year = weekYear; } break; case 0 : year = weekYear + 1; if (weekYear != this.__getWeekYear(new Date(year,month,dayOfMonth))) { year = weekYear; } break; default : year = weekYear; } return year; }, /** * Sets the new value for locale property * @param value {String} The new value. * */ setLocale : function(value) { if (value !== null && typeof value != "string") { throw new Error("Cannot set locale to " + value + " - please provide a string"); } this.__locale = value === null ? this.__initialLocale : value; }, /** * Resets the Locale */ resetLocale : function() { this.setLocale(null); }, /** * Returns the locale */ getLocale : function() { var locale = this.__locale; if (locale === undefined) { locale = qx.locale.Manager.getInstance().getLocale(); } return locale; }, /** * Formats a date. * * @param date {Date} The date to format. * @return {String} the formatted date. */ format : function(date) { // check for null dates if (date == null) { return null; } if(isNaN(date.getTime())) { if (qx.core.Environment.get("qx.debug")) { qx.log.Logger.error("Provided date is invalid"); } return null; } if(this.__UTC) { date = new Date(date.getUTCFullYear(),date.getUTCMonth(),date.getUTCDate(),date.getUTCHours(),date.getUTCMinutes(),date.getUTCSeconds(),date.getUTCMilliseconds()); } var locale = this.getLocale(); var fullYear = date.getFullYear(); var month = date.getMonth(); var dayOfMonth = date.getDate(); var dayOfWeek = date.getDay(); var hours = date.getHours(); var minutes = date.getMinutes(); var seconds = date.getSeconds(); var ms = date.getMilliseconds(); var timezoneOffset = date.getTimezoneOffset(); var timezoneSign = timezoneOffset > 0 ? 1 : -1; var timezoneHours = Math.floor(Math.abs(timezoneOffset) / 60); var timezoneMinutes = Math.abs(timezoneOffset) % 60; // Create the output this.__initFormatTree(); var output = ""; for (var i=0; i<this.__formatTree.length; i++) { var currAtom = this.__formatTree[i]; if (currAtom.type == "literal") { output += currAtom.text; } else { // This is a wildcard var wildcardChar = currAtom.character; var wildcardSize = currAtom.size; // Get its replacement var replacement = "?"; switch(wildcardChar) { case 'y': // Year if (wildcardSize == 2) { replacement = this.__fillNumber(fullYear % 100, 2); } else { var year = Math.abs(fullYear); replacement = year + ""; if (wildcardSize > replacement.length) { for (var j = replacement.length; j < wildcardSize; j++) { replacement = "0" + replacement; } } if(fullYear < 0) { replacement = "-" + replacement; } } break; case 'Y': // Year replacement = this.__getWeekYear(date) + ""; var year = replacement.replace('-',''); if (wildcardSize > replacement.length) { for (var j = year.length; j < wildcardSize; j++) { year = "0" + year; } } replacement = replacement.indexOf("-") != -1 ? "-" + year : year; break; case 'G': // Era - there is no CLDR data for ERA yet if (wildcardSize >= 1 && wildcardSize <= 3) { replacement = fullYear > 0 ? 'AD' : 'BC'; } else if(wildcardSize == 4) { replacement = fullYear > 0 ? 'Anno Domini' : 'Before Christ'; } else if(wildcardSize == 5) { replacement = fullYear > 0 ? 'A' : 'B'; } break; case 'Q': // quarter if (wildcardSize == 1 || wildcardSize == 2) { replacement = this.__fillNumber(parseInt(month/4) + 1, wildcardSize); } if(wildcardSize == 3) { replacement = 'Q' + (parseInt(month/4) + 1); } break; case 'q': // quarter stand alone if (wildcardSize == 1 || wildcardSize == 2) { replacement = this.__fillNumber(parseInt(month/4) + 1, wildcardSize); } if(wildcardSize == 3) { replacement = 'Q' + (parseInt(month/4) + 1); } break; case 'D': // Day in year (e.g. 189) replacement = this.__fillNumber(this.__getDayInYear(date), wildcardSize); break; case 'd': // Day in month replacement = this.__fillNumber(dayOfMonth, wildcardSize); break; case 'w': // Week in year (e.g. 27) replacement = this.__fillNumber(this.__getWeekInYear(date), wildcardSize); break; case 'W': // Week in year (e.g. 27) replacement = this.__getWeekInMonth(date); break; case 'E': // Day in week if (wildcardSize >= 1 && wildcardSize <= 3) { replacement = qx.locale.Date.getDayName("abbreviated", dayOfWeek, locale, "format", true); } else if (wildcardSize == 4) { replacement = qx.locale.Date.getDayName("wide", dayOfWeek, locale, "format", true); } else if (wildcardSize == 5) { replacement = qx.locale.Date.getDayName("narrow", dayOfWeek, locale, "format", true); } break; case 'e': // Day in week var startOfWeek = qx.locale.Date.getWeekStart(locale); // the index is 1 based var localeDayOfWeek = 1 + ((dayOfWeek - startOfWeek >=0) ? (dayOfWeek - startOfWeek) : 7 + (dayOfWeek-startOfWeek)); if (wildcardSize >= 1 && wildcardSize <= 2) { replacement = this.__fillNumber(localeDayOfWeek, wildcardSize); } else if (wildcardSize == 3) { replacement = qx.locale.Date.getDayName("abbreviated", dayOfWeek, locale, "format", true); } else if (wildcardSize == 4) { replacement = qx.locale.Date.getDayName("wide", dayOfWeek, locale, "format", true); } else if (wildcardSize == 5) { replacement = qx.locale.Date.getDayName("narrow", dayOfWeek, locale, "format", true); } break; case 'c': // Stand-alone local day in week var startOfWeek = qx.locale.Date.getWeekStart(locale); // the index is 1 based var localeDayOfWeek = 1 + ((dayOfWeek - startOfWeek >=0) ? (dayOfWeek - startOfWeek) : 7 + (dayOfWeek-startOfWeek)); if (wildcardSize == 1) { replacement = ''+localeDayOfWeek; } else if (wildcardSize == 3) { replacement = qx.locale.Date.getDayName("abbreviated", dayOfWeek, locale, "stand-alone", true); } else if (wildcardSize == 4) { replacement = qx.locale.Date.getDayName("wide", dayOfWeek, locale, "stand-alone", true); } else if (wildcardSize == 5) { replacement = qx.locale.Date.getDayName("narrow", dayOfWeek, locale, "stand-alone", true); } break; case 'M': // Month if (wildcardSize == 1 || wildcardSize == 2) { replacement = this.__fillNumber(month + 1, wildcardSize); } else if (wildcardSize == 3) { replacement = qx.locale.Date.getMonthName("abbreviated", month, locale, "format", true); } else if (wildcardSize == 4) { replacement = qx.locale.Date.getMonthName("wide", month, locale, "format", true); } else if (wildcardSize == 5) { replacement = qx.locale.Date.getMonthName("narrow", month, locale, "format", true); } break; case 'L': // Stand-alone month if (wildcardSize == 1 || wildcardSize == 2) { replacement = this.__fillNumber(month + 1, wildcardSize); } else if (wildcardSize == 3) { replacement = qx.locale.Date.getMonthName("abbreviated", month, locale, "stand-alone", true); } else if (wildcardSize == 4) { replacement = qx.locale.Date.getMonthName("wide", month, locale, "stand-alone", true); } else if (wildcardSize == 5) { replacement = qx.locale.Date.getMonthName("narrow", month, locale, "stand-alone", true); } break; case 'a': // am/pm marker // NOTE: 0:00 is am, 12:00 is pm replacement = (hours < 12) ? qx.locale.Date.getAmMarker(locale) : qx.locale.Date.getPmMarker(locale); break; case 'H': // Hour in day (0-23) replacement = this.__fillNumber(hours, wildcardSize); break; case 'k': // Hour in day (1-24) replacement = this.__fillNumber((hours == 0) ? 24 : hours, wildcardSize); break; case 'K': // Hour in am/pm (0-11) replacement = this.__fillNumber(hours % 12, wildcardSize); break; case 'h': // Hour in am/pm (1-12) replacement = this.__fillNumber(((hours % 12) == 0) ? 12 : (hours % 12), wildcardSize); break; case 'm': // Minute in hour replacement = this.__fillNumber(minutes, wildcardSize); break; case 's': // Second in minute replacement = this.__fillNumber(seconds, wildcardSize); break; case 'S': // Fractional second replacement = this.__fillNumber(ms, 3); if (wildcardSize < replacement.length) { replacement = replacement.substr(0, wildcardSize); } else { while (wildcardSize > replacement.length) { // if needed, fill the remaining wildcard length with trailing zeros replacement += "0"; } } break; case 'z': // Time zone if (wildcardSize >= 1 && wildcardSize <= 4) { replacement = "GMT" + ((timezoneSign > 0) ? "-" : "+") + this.__fillNumber(Math.abs(timezoneHours), 2) + ":" + this.__fillNumber(timezoneMinutes, 2); } break; case 'Z': // RFC 822 time zone if (wildcardSize >= 1 && wildcardSize <= 3) { replacement = ((timezoneSign > 0) ? "-" : "+") + this.__fillNumber(Math.abs(timezoneHours), 2) + this.__fillNumber(timezoneMinutes, 2); } else { replacement = "GMT" + ((timezoneSign > 0) ? "-" : "+") + this.__fillNumber(Math.abs(timezoneHours), 2) + ":" + this.__fillNumber(timezoneMinutes, 2); } break; } output += replacement; } } return output; }, /** * Parses a date. * * @param dateStr {String} the date to parse. * @return {Date} the parsed date. * @throws {Error} If the format is not well formed or if the date string does not * match to the format. */ parse : function(dateStr) { this.__initParseFeed(); // Apply the regex var hit = this.__parseFeed.regex.exec(dateStr); if (hit == null) { throw new Error("Date string '" + dateStr + "' does not match the date format: " + this.__format); } // Apply the rules var dateValues = { era : 1, year : 1970, quarter : 1, month : 0, day : 1, dayOfYear : 1, hour : 0, ispm : false, weekDay : 4, weekYear : 1970, weekOfMonth : 1, weekOfYear : 1, min : 0, sec : 0, ms : 0 }; var currGroup = 1; var applyWeekYearAfterRule = false; var applyDayOfYearAfterRule = false; for (var i=0; i<this.__parseFeed.usedRules.length; i++) { var rule = this.__parseFeed.usedRules[i]; var value = hit[currGroup]; if (rule.field != null) { dateValues[rule.field] = parseInt(value, 10); } else { rule.manipulator(dateValues, value, rule.pattern); } if(rule.pattern == "Y+") { var yearRuleApplied = false; for(var k=0; k<this.__parseFeed.usedRules.length; k++) { if(this.__parseFeed.usedRules[k].pattern == 'y+'){ yearRuleApplied = true; break; } } if(!yearRuleApplied) { applyWeekYearAfterRule = true; } } if(rule.pattern.indexOf("D") != -1) { var dayRuleApplied = false; for(var k=0; k<this.__parseFeed.usedRules.length; k++) { if(this.__parseFeed.usedRules[k].pattern.indexOf("d") != -1){ dayRuleApplied = true; break; } } if(!dayRuleApplied) { applyDayOfYearAfterRule = true; } } currGroup += (rule.groups == null) ? 1 : rule.groups; } if(applyWeekYearAfterRule) { dateValues.year = this.__getYearFromWeekYearAndMonth(dateValues.weekYear,dateValues.month,dateValues.day); } if(applyDayOfYearAfterRule) { var dayAndMonth = this.__getMonthAndDayFromDayOfYear(dateValues.dayOfYear, dateValues.year); dateValues.month = dayAndMonth.month; dateValues.day = dayAndMonth.day; } if(dateValues.era < 0 && (dateValues.year * dateValues.era < 0)) { dateValues.year = dateValues.year * dateValues.era; } var date = new Date(dateValues.year, dateValues.month, dateValues.day, (dateValues.ispm) ? (dateValues.hour + 12) : dateValues.hour, dateValues.min, dateValues.sec, dateValues.ms); if(this.__UTC) { date = new Date(date.getUTCFullYear(),date.getUTCMonth(),date.getUTCDate(),date.getUTCHours(),date.getUTCMinutes(),date.getUTCSeconds(),date.getUTCMilliseconds()); } if (dateValues.month != date.getMonth() || dateValues.year != date.getFullYear()) { throw new Error("Error parsing date '" + dateStr + "': the value for day or month is too large"); } return date; }, /** * Helper method for {@link #format()} and {@link #parse()}. * Parses the date format. * */ __initFormatTree : function() { if (this.__formatTree != null) { return; } this.__formatTree = []; var currWildcardChar; var currWildcardSize = 0; var currLiteral = ""; var format = this.__format; var state = "default"; var i = 0; while (i < format.length) { var currChar = format.charAt(i); switch(state) { case "quoted_literal": // We are now inside a quoted literal // Check whether the current character is an escaped "'" character if (currChar == "'") { if (i + 1 >= format.length) { // this is the last character i++; break; } var lookAhead = format.charAt(i + 1); if (lookAhead == "'") { currLiteral += currChar; i++; } else { // quoted literal ends i++; state = "unkown"; } } else { currLiteral += currChar; i++; } break; case "wildcard": // Check whether the currChar belongs to that wildcard if (currChar == currWildcardChar) { // It does -> Raise the size currWildcardSize++; i++; } else { // It does not -> The current wildcard is done this.__formatTree.push( { type : "wildcard", character : currWildcardChar, size : currWildcardSize }); currWildcardChar = null; currWildcardSize = 0; state = "default"; } break; default: // We are not (any more) in a wildcard or quoted literal -> Check what's starting here if ((currChar >= 'a' && currChar <= 'z') || (currChar >= 'A' && currChar <= 'Z')) { // This is a letter -> All letters are wildcards // Start a new wildcard currWildcardChar = currChar; state = "wildcard"; } else if (currChar == "'") { if (i + 1 >= format.length) { // this is the last character currLiteral += currChar; i++; break; } var lookAhead = format.charAt(i + 1); if (lookAhead == "'") { currLiteral += currChar; i++; } i++; state = "quoted_literal"; } else { state = "default"; } if (state != "default") { // Add the literal if (currLiteral.length > 0) { this.__formatTree.push( { type : "literal", text : currLiteral }); currLiteral = ""; } } else { // This is an unquoted literal -> Add it to the current literal currLiteral += currChar; i++; } break; } } // Add the last wildcard or literal if (currWildcardChar != null) { this.__formatTree.push( { type : "wildcard", character : currWildcardChar, size : currWildcardSize }); } else if (currLiteral.length > 0) { this.__formatTree.push( { type : "literal", text : currLiteral }); } }, /** * Initializes the parse feed. * * The parse contains everything needed for parsing: The regular expression * (in compiled and uncompiled form) and the used rules. * * @throws {Error} If the date format is malformed. */ __initParseFeed : function() { if (this.__parseFeed != null) { // We already have the parse feed return; } var format = this.__format; // Initialize the rules this.__initParseRules(); this.__initFormatTree(); // Get the used rules and construct the regex pattern var usedRules = []; var pattern = "^"; for (var atomIdx=0; atomIdx<this.__formatTree.length; atomIdx++) { var currAtom = this.__formatTree[atomIdx]; if (currAtom.type == "literal") { pattern += qx.lang.String.escapeRegexpChars(currAtom.text); } else { // This is a wildcard var wildcardChar = currAtom.character; var wildcardSize = currAtom.size; // Get the rule for this wildcard var wildcardRule; for (var ruleIdx=0; ruleIdx<this.__parseRules.length; ruleIdx++) { var rule = this.__parseRules[ruleIdx]; if ( this.__isRuleForWildcard(rule,wildcardChar,wildcardSize)) { // We found the right rule for the wildcard wildcardRule = rule; break; } } // Check the rule if (wildcardRule == null) { // We have no rule for that wildcard -> Malformed date format var wildcardStr = ""; for (var i=0; i<wildcardSize; i++) { wildcardStr += wildcardChar; } throw new Error("Malformed date format: " + format + ". Wildcard " + wildcardStr + " is not supported"); } else { // Add the rule to the pattern usedRules.push(wildcardRule); pattern += wildcardRule.regex; } } } pattern += "$"; // Create the regex var regex; try { regex = new RegExp(pattern); } catch(exc) { throw new Error("Malformed date format: " + format); } // Create the this.__parseFeed this.__parseFeed = { regex : regex, "usedRules" : usedRules, pattern : pattern }; }, /** * Checks whether the rule matches the wildcard or not. * @param rule {Object} the rule we try to match with the wildcard * @param wildcardChar {String} the character in the wildcard * @param wildcardSize {Integer} the number of wildcardChar characters in the wildcard * @return {Boolean} if the rule matches or not */ __isRuleForWildcard : function(rule, wildcardChar, wildcardSize) { if(wildcardChar==='y' && rule.pattern==='y+') { rule.regex = rule.regexFunc(wildcardSize); return true; } else if(wildcardChar==='Y' && rule.pattern==='Y+') { rule.regex = rule.regexFunc(wildcardSize); return true; } else { return wildcardChar == rule.pattern.charAt(0) && wildcardSize == rule.pattern.length; } }, /** * Initializes the static parse rules. * */ __initParseRules : function() { var DateFormat = qx.util.format.DateFormat; var LString = qx.lang.String; if (this.__parseRules != null) { // The parse rules are already initialized return ; } var rules = this.__parseRules = []; var amMarker = qx.locale.Date.getAmMarker(this.getLocale()).toString() || DateFormat.AM_MARKER; var pmMarker = qx.locale.Date.getPmMarker(this.getLocale()).toString() || DateFormat.PM_MARKER; var locale = this.getLocale(); var yearManipulator = function(dateValues, value) { value = parseInt(value, 10); if(value >= 0) { if (value < DateFormat.ASSUME_YEAR_2000_THRESHOLD) { value += 2000; } else if (value < 100) { value += 1900; } } dateValues.year = value; }; var weekYearManipulator = function(dateValues, value) { value = parseInt(value, 10); if(value >= 0) { if (value < DateFormat.ASSUME_YEAR_2000_THRESHOLD) { value += 2000; } else if (value < 100) { value += 1900; } } dateValues.weekYear = value; }; var monthManipulator = function(dateValues, value) { dateValues.month = parseInt(value, 10) - 1; }; var localWeekDayManipulator = function(dateValues, value) { var startOfWeek = qx.locale.Date.getWeekStart(locale); var dayOfWeek = (parseInt(value,10) - 1 + startOfWeek) <= 6 ? parseInt(value,10) - 1 + startOfWeek : (parseInt(value,10) - 1 + startOfWeek) -7; dateValues.weekDay = dayOfWeek; }; var ampmManipulator = function(dateValues, value) { var pmMarker = qx.locale.Date.getPmMarker(locale).toString() || DateFormat.PM_MARKER; dateValues.ispm = (value == pmMarker); }; var noZeroHourManipulator = function(dateValues, value) { dateValues.hour = parseInt(value, 10) % 24; }; var noZeroAmPmHourManipulator = function(dateValues, value) { dateValues.hour = parseInt(value, 10) % 12; }; var ignoreManipulator = function(dateValues, value) { return; }; var narrowEraNames = ['A', 'B']; var narrowEraNameManipulator = function(dateValues, value) { dateValues.era = value == 'A' ? 1 : -1; }; var abbrevEraNames = ['AD', 'BC']; var abbrevEraNameManipulator = function(dateValues, value) { dateValues.era = value == 'AD' ? 1 : -1; }; var fullEraNames = ['Anno Domini', 'Before Christ']; var fullEraNameManipulator = function(dateValues, value) { dateValues.era = value == 'Anno Domini' ? 1 : -1; }; var abbrevQuarterNames = ['Q1','Q2','Q3','Q4']; var abbrevQuarterManipulator = function(dateValues, value) { dateValues.quarter = abbrevQuarterNames.indexOf(value); }; var fullQuarterNames = ['1st quarter','2nd quarter','3rd quarter','4th quarter']; var fullQuarterManipulator = function(dateValues, value) { dateValues.quarter = fullQuarterNames.indexOf(value); }; var cache = {}; var dateNamesManipulator = function(pattern){ var monthPatternLetters = ['L','M']; var dayPatternLetters = ['c', 'e', 'E']; var firstLetterInPattern = pattern.charAt(0); var isMonth = monthPatternLetters.indexOf(firstLetterInPattern)>=0; var getContext = function() { var letters = isMonth ? monthPatternLetters : dayPatternLetters; var context = firstLetterInPattern === letters[0] ? "stand-alone" : "format" ; var patternLength = pattern.length; var lengthName = 'abbreviated'; switch(patternLength) { case 4: lengthName = 'wide'; break; case 5: lengthName = 'narrow'; break; default: lengthName = 'abbreviated'; } return [context, lengthName]; }; if(!cache[pattern]) { cache[pattern] = {}; var context = getContext(); var func = isMonth ? qx.locale.Date.getMonthNames : qx.locale.Date.getDayNames; var names = func.call(qx.locale.Date, context[1], locale, context[0], true); for(var i=0, l=names.length; i<l; i++) { names[i] = LString.escapeRegexpChars(names[i].toString()); } cache[pattern].data = names; cache[pattern].func = function(dateValues, value) { value = LString.escapeRegexpChars(value); dateValues[isMonth ? 'month' : 'weekDay'] = names.indexOf(value); }; } return cache[pattern]; }; // Unsupported: F (Day of week in month) rules.push( { pattern : "y+", regexFunc : function(yNumber) { var regex = "(-*"; for(var i=0;i<yNumber;i++) { regex += "\\d"; if(i===yNumber-1 && i!==1) { regex += "+?"; } } regex += ")"; return regex; }, manipulator : yearManipulator }); rules.push( { pattern : "Y+", regexFunc : function(yNumber) { var regex = "(-*"; for(var i=0;i<yNumber;i++) { regex += "\\d"; if(i===yNumber-1) { regex += "+?"; } } regex += ")"; return regex; }, manipulator : weekYearManipulator }); rules.push( { pattern : "G", regex : "(" + abbrevEraNames.join("|") + ")", manipulator : abbrevEraNameManipulator }); rules.push( { pattern : "GG", regex : "(" + abbrevEraNames.join("|") + ")", manipulator : abbrevEraNameManipulator }); rules.push( { pattern : "GGG", regex : "(" + abbrevEraNames.join("|") + ")", manipulator : abbrevEraNameManipulator }); rules.push( { pattern : "GGGG", regex : "(" + fullEraNames.join("|") + ")", manipulator : fullEraNameManipulator }); rules.push( { pattern : "GGGGG", regex : "(" + narrowEraNames.join("|") + ")", manipulator : narrowEraNameManipulator }); rules.push( { pattern : "Q", regex : "(\\d\\d*?)", field : "quarter" }); rules.push( { pattern : "QQ", regex : "(\\d\\d?)", field : "quarter" }); rules.push( { pattern : "QQQ", regex : "(" + abbrevQuarterNames.join("|") + ")", manipulator : abbrevQuarterManipulator }); rules.push( { pattern : "QQQQ", regex : "(" + fullQuarterNames.join("|") + ")", manipulator : fullQuarterManipulator }); rules.push( { pattern : "q", regex : "(\\d\\d*?)", field : "quarter" }); rules.push( { pattern : "qq", regex : "(\\d\\d?)", field : "quarter" }); rules.push( { pattern : "qqq", regex : "(" + abbrevQuarterNames.join("|") + ")", manipulator : abbrevQuarterManipulator }); rules.push( { pattern : "qqqq", regex : "(" + fullQuarterNames.join("|") + ")", manipulator : fullQuarterManipulator }); rules.push( { pattern : "M", regex : "(\\d\\d*?)", manipulator : monthManipulator }); rules.push( { pattern : "MM", regex : "(\\d\\d?)", manipulator : monthManipulator }); rules.push( { pattern : "MMM", regex : "(" + dateNamesManipulator("MMM").data.join("|") + ")", manipulator : dateNamesManipulator("MMM").func }); rules.push( { pattern : "MMMM", regex : "(" + dateNamesManipulator("MMMM").data.join("|") + ")", manipulator : dateNamesManipulator("MMMM").func }); rules.push( { pattern : "MMMMM", regex : "(" + dateNamesManipulator("MMMMM").data.join("|") + ")", manipulator : dateNamesManipulator("MMMMM").func }); rules.push( { pattern : "L", regex : "(\\d\\d*?)", manipulator : monthManipulator }); rules.push( { pattern : "LL", regex : "(\\d\\d?)", manipulator : monthManipulator }); rules.push( { pattern : "LLL", regex : "(" + dateNamesManipulator("LLL").data.join("|") + ")", manipulator : dateNamesManipulator("LLL").func }); rules.push( { pattern : "LLLL", regex : "(" + dateNamesManipulator("LLLL").data.join("|") + ")", manipulator : dateNamesManipulator("LLLL").func }); rules.push( { pattern : "LLLLL", regex : "(" + dateNamesManipulator("LLLLL").data.join("|") + ")", manipulator : dateNamesManipulator("LLLLL").func }); rules.push( { pattern : "dd", regex : "(\\d\\d?)", field : "day" }); rules.push( { pattern : "d", regex : "(\\d\\d*?)", field : "day" }); rules.push( { pattern : "D", regex : "(\\d?)", field : "dayOfYear" }); rules.push( { pattern : "DD", regex : "(\\d\\d?)", field : "dayOfYear" }); rules.push( { pattern : "DDD", regex : "(\\d\\d\\d?)", field : "dayOfYear" }); rules.push( { pattern : "E", regex : "(" + dateNamesManipulator("E").data.join("|") + ")", manipulator : dateNamesManipulator("E").func }); rules.push( { pattern : "EE", regex : "(" + dateNamesManipulator("EE").data.join("|") + ")", manipulator : dateNamesManipulator("EE").func }); rules.push( { pattern : "EEE", regex : "(" + dateNamesManipulator("EEE").data.join("|") + ")", manipulator : dateNamesManipulator("EEE").func }); rules.push( { pattern : "EEEE", regex : "(" + dateNamesManipulator("EEEE").data.join("|") + ")", manipulator : dateNamesManipulator("EEEE").func }); rules.push( { pattern : "EEEEE", regex : "(" + dateNamesManipulator("EEEEE").data.join("|") + ")", manipulator : dateNamesManipulator("EEEEE").func }); rules.push( { pattern : "e", regex : "(\\d?)", manipulator : localWeekDayManipulator }); rules.push( { pattern : "ee", regex : "(\\d\\d?)", manipulator : localWeekDayManipulator }); rules.push( { pattern : "eee", regex : "(" + dateNamesManipulator("eee").data.join("|") + ")", manipulator : dateNamesManipulator("eee").func }); rules.push( { pattern : "eeee", regex : "(" + dateNamesManipulator("eeee").data.join("|") + ")", manipulator : dateNamesManipulator("eeee").func }); rules.push( { pattern : "eeeee", regex : "(" + dateNamesManipulator("eeeee").data.join("|") + ")", manipulator : dateNamesManipulator("eeeee").func }); rules.push( { pattern : "c", regex : "\\d?", manipulator : localWeekDayManipulator }); rules.push( { pattern : "ccc", regex : "(" + dateNamesManipulator("ccc").data.join("|") + ")", manipulator : dateNamesManipulator("ccc").func }); rules.push( { pattern : "cccc", regex : "(" + dateNamesManipulator("cccc").data.join("|") + ")", manipulator : dateNamesManipulator("cccc").func }); rules.push( { pattern : "ccccc", regex : "(" + dateNamesManipulator("ccccc").data.join("|") + ")", manipulator : dateNamesManipulator("ccccc").func }); rules.push( { pattern : "a", regex : "(" + amMarker + "|" + pmMarker + ")", manipulator : ampmManipulator }); rules.push( { pattern : "W", regex : "(\\d?)", field : "weekOfMonth" }); rules.push( { pattern : "w", regex : "(\\d\\d?)", field : "weekOfYear" }); rules.push( { pattern : "ww", regex : "(\\d\\d)", field : "weekOfYear" }); rules.push( { pattern : "HH", regex : "(\\d\\d?)", field : "hour" }); rules.push( { pattern : "H", regex : "(\\d\\d?)", field : "hour" }); rules.push( { pattern : "kk", regex : "(\\d\\d?)", manipulator : noZeroHourManipulator }); rules.push( { pattern : "k", regex : "(\\d\\d?)", manipulator : noZeroHourManipulator }); rules.push( { pattern : "KK", regex : "(\\d\\d?)", field : "hour" }); rules.push( { pattern : "K", regex : "(\\d\\d?)", field : "hour" }); rules.push( { pattern : "hh", regex : "(\\d\\d?)", manipulator : noZeroAmPmHourManipulator }); rules.push( { pattern : "h", regex : "(\\d\\d?)", manipulator : noZeroAmPmHourManipulator }); rules.push( { pattern : "mm", regex : "(\\d\\d?)", field : "min" }); rules.push( { pattern : "m", regex : "(\\d\\d?)", field : "min" }); rules.push( { pattern : "ss", regex : "(\\d\\d?)", field : "sec" }); rules.push(