You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@shindig.apache.org by vs...@apache.org on 2009/03/31 00:45:53 UTC

svn commit: r760210 [36/37] - in /incubator/shindig/trunk/features: ./ src/main/javascript/features/ src/main/javascript/features/i18n/ src/main/javascript/features/i18n/data/

Added: incubator/shindig/trunk/features/src/main/javascript/features/i18n/datetimeparse.js
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/features/src/main/javascript/features/i18n/datetimeparse.js?rev=760210&view=auto
==============================================================================
--- incubator/shindig/trunk/features/src/main/javascript/features/i18n/datetimeparse.js (added)
+++ incubator/shindig/trunk/features/src/main/javascript/features/i18n/datetimeparse.js Mon Mar 30 22:45:30 2009
@@ -0,0 +1,999 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+/**
+ * @fileoverview Date/Time parsing library with locale support
+ */
+
+var gadgets = gadgets || {};
+
+gadgets.i18n = gadgets.i18n || {};
+
+/**
+ * DateTimeParse is for parsing date in a locale-sensitive manner. It allows
+ * user to use any customized patterns to parse date-time string under certain
+ * locale. Things varies across locales like month name, weekname, field
+ * order, etc.
+ *
+ * This module is the counter-part of DateTimeFormat. They use the same
+ * date/time pattern specification, which is borrowed from ICU/JDK.
+ *
+ * This implementation could parse partial date/time.
+ *
+ * Time Format Syntax: To specify the time format use a time pattern string.
+ * In this pattern, following letters are reserved as pattern letters, which
+ * are defined as the following:
+ *
+ * <pre>
+ * Symbol   Meaning                 Presentation        Example
+ * ------   -------                 ------------        -------
+ * G        era designator          (Text)              AD
+ * y#       year                    (Number)            1996
+ * M        month in year           (Text & Number)     July & 07
+ * d        day in month            (Number)            10
+ * h        hour in am/pm (1~12)    (Number)            12
+ * H        hour in day (0~23)      (Number)            0
+ * m        minute in hour          (Number)            30
+ * s        second in minute        (Number)            55
+ * S        fractional second       (Number)            978
+ * E        day of week             (Text)              Tuesday
+ * D        day in year             (Number)            189
+ * a        am/pm marker            (Text)              PM
+ * k        hour in day (1~24)      (Number)            24
+ * K        hour in am/pm (0~11)    (Number)            0
+ * z        time zone               (Text)              Pacific Standard Time
+ * Z        time zone (RFC 822)     (Number)            -0800
+ * v        time zone (generic)     (Text)              Pacific Time
+ * '        escape for text         (Delimiter)         'Date='
+ * ''       single quote            (Literal)           'o''clock'
+ * </pre>
+ *
+ * The count of pattern letters determine the format. <p>
+ * (Text): 4 or more pattern letters--use full form,
+ *         less than 4--use short or abbreviated form if one exists.
+ *         In parsing, we will always try long format, then short. <p>
+ * (Number): the minimum number of digits. <p>
+ * (Text & Number): 3 or over, use text, otherwise use number. <p>
+ * Any characters that not in the pattern will be treated as quoted text. For
+ * instance, characters like ':', '.', ' ', '#' and '@' will appear in the
+ * resulting time text even they are not embraced within single quotes. In our
+ * current pattern usage, we didn't use up all letters. But those unused
+ * letters are strongly discouraged to be used as quoted text without quote.
+ * That's because we may use other letter for pattern in future. <p>
+ *
+ * Examples Using the US Locale:
+ *
+ * Format Pattern                         Result
+ * --------------                         -------
+ * "yyyy.MM.dd G 'at' HH:mm:ss vvvv" ->>  1996.07.10 AD at 15:08:56 Pacific Time
+ * "EEE, MMM d, ''yy"                ->>  Wed, July 10, '96
+ * "h:mm a"                          ->>  12:08 PM
+ * "hh 'o''clock' a, zzzz"           ->>  12 o'clock PM, Pacific Daylight Time
+ * "K:mm a, vvv"                     ->>  0:00 PM, PT
+ * "yyyyy.MMMMM.dd GGG hh:mm aaa"    ->>  01996.July.10 AD 12:08 PM
+ *
+ * <p> When parsing a date string using the abbreviated year pattern ("yy"),
+ * DateTimeParse must interpret the abbreviated year relative to some
+ * century. It does this by adjusting dates to be within 80 years before and 20
+ * years after the time the parse function is called. For example, using a
+ * pattern of "MM/dd/yy" and a DateTimeParse instance created on Jan 1, 1997,
+ * the string "01/11/12" would be interpreted as Jan 11, 2012 while the string
+ * "05/04/64" would be interpreted as May 4, 1964. During parsing, only
+ * strings consisting of exactly two digits, as defined by {@link
+ * java.lang.Character#isDigit(char)}, will be parsed into the default
+ * century. Any other numeric string, such as a one digit string, a three or
+ * more digit string will be interpreted as its face value.
+ *
+ * <p> If the year pattern does not have exactly two 'y' characters, the year is
+ * interpreted literally, regardless of the number of digits. So using the
+ * pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D.
+ *
+ * <p> When numeric fields abut one another directly, with no intervening
+ * delimiter characters, they constitute a run of abutting numeric fields. Such
+ * runs are parsed specially. For example, the format "HHmmss" parses the input
+ * text "123456" to 12:34:56, parses the input text "12345" to 1:23:45, and
+ * fails to parse "1234". In other words, the leftmost field of the run is
+ * flexible, while the others keep a fixed width. If the parse fails anywhere in
+ * the run, then the leftmost field is shortened by one character, and the
+ * entire run is parsed again. This is repeated until either the parse succeeds
+ * or the leftmost field is one character in length. If the parse still fails at
+ * that point, the parse of the run fails.
+ *
+ * <p> Now timezone parsing only support GMT:hhmm, GMT:+hhmm, GMT:-hhmm
+ *
+ */
+
+/**
+ * Construct a DateTimeParse object based on current locale by using
+ * the symbol table passed in.
+ * @constructor
+ */
+gadgets.i18n.DateTimeParse = function(symbol) {
+    this.symbols_ = symbol;
+};
+
+/**
+ * Year.
+ * @type {number}
+ */
+gadgets.i18n.DateTimeParse.prototype.year = 0;
+
+
+/**
+ * Month.
+ * @type {number}
+ */
+gadgets.i18n.DateTimeParse.prototype.month = 0;
+
+
+/**
+ * Day of month.
+ * @type {number}
+ */
+gadgets.i18n.DateTimeParse.prototype.dayOfMonth = 0;
+
+
+/**
+ * Hours.
+ * @type {number}
+ */
+gadgets.i18n.DateTimeParse.prototype.hours = 0;
+
+
+/**
+ * Minutes.
+ * @type {number}
+ */
+gadgets.i18n.DateTimeParse.prototype.minutes = 0;
+
+
+/**
+ * Seconds.
+ * @type {number}
+ */
+gadgets.i18n.DateTimeParse.prototype.seconds = 0;
+
+
+/**
+ * Milliseconds.
+ * @type {number}
+ */
+gadgets.i18n.DateTimeParse.prototype.milliseconds = 0;
+
+
+/**
+ * Number of years prior to now that the century used to
+ * disambiguate two digit years will begin
+ *
+ * @type {number}
+ */
+gadgets.i18n.DateTimeParse.ambiguousYearCenturyStart = 80;
+
+/**
+ * Apply a pattern to this Parser. The pattern string will be parsed and saved
+ * in "compiled" form.
+ * Note: this method is somewhat similar to the pattern parsing methold in
+ *       datetimeformat. If you see something wrong here, you might want
+ *       to check the other.
+ * @param {string} pattern It describes the format of date string that need to
+ *     be parsed.
+ */
+gadgets.i18n.DateTimeParse.prototype.applyPattern = function(pattern) {
+    this.patternParts_ = [];
+    var inQuote = false;
+    var buf = '';
+
+    for (var i = 0; i < pattern.length; i++) {
+        var ch = pattern.charAt(i);
+
+    // handle space, add literal part (if exist), and add space part
+        if (ch == ' ') {
+            if (buf.length > 0) {
+                this.patternParts_.push({text: buf, count: 0, abutStart: false});
+                buf = '';
+            }
+            this.patternParts_.push({text: ' ', count: 0, abutStart: false});
+            while (i + 1 < pattern.length && pattern.charAt(i + 1) == ' ') {
+                i++;
+            }
+        } else if (inQuote) {
+            // inside quote, except '', just copy or exit
+            if (ch == '\'') {
+                if (i + 1 < pattern.length && pattern.charAt(i + 1) == '\'') {
+                    // quote appeared twice continuously, interpret as one quote.
+                    buf += ch;
+                    ++i;
+                } else {
+                    // exit quote
+                    inQuote = false;
+                }
+            } else {
+                // literal
+                buf += ch;
+            }
+        } else if (gadgets.i18n.DateTimeParse.PATTERN_CHARS_.indexOf(ch) >= 0) {
+            // outside quote, it is a pattern char
+            if (buf.length > 0) {
+                this.patternParts_.push({text: buf, count: 0, abutStart: false});
+                buf = '';
+            }
+            var count = this.getNextCharCount_(pattern, i);
+            this.patternParts_.push({text: ch, count: count, abutStart: false});
+            i += count - 1;
+        } else if (ch == '\'') {
+            // Two consecutive quotes is a quote literal, inside or outside of quotes.
+            if (i + 1 < pattern.length && pattern.charAt(i + 1) == '\'') {
+                buf += "'";
+                i++;
+            } else {
+                inQuote = true;
+            }
+        } else {
+            buf += ch;
+        }
+    }
+
+    if (buf.length > 0) {
+        this.patternParts_.push({text: buf, count: 0, abutStart: false});
+    }
+
+    this.markAbutStart_();
+};
+
+
+/**
+ * Apply a predefined pattern to this Parser.
+ * @param {number} formatType A constant used to identified the predefined
+ *     pattern string stored in locale repository.
+ * @return {void} nothing
+ */
+gadgets.i18n.DateTimeParse.prototype.applyStandardPattern = function(formatType)
+{
+    var pattern;
+  // formatType constants are defined in a way so that following resolution is
+    // possible.
+    if (formatType < 4) {
+        pattern = this.symbols_.DATEFORMATS[formatType];
+    } else if (formatType < 8) {
+        pattern = this.symbols_.TIMEFORMATS[formatType - 4];
+    } else if (formatType < 12) {
+        pattern = this.symbols_.DATEFORMATS[formatType - 8];
+        pattern += ' ';
+        pattern += this.symbols_.TIMEFORMATS[formatType - 8];
+    } else {
+        return this.applyStandardPattern(gadgets.i18n.MEDIUM_DATETIME_FORMAT);
+    }
+    return this.applyPattern(pattern);
+};
+
+
+/**
+ * Parse the given string and fill info into date object. This version does
+ * not validate the input.
+ * @param {string} text The string being parsed.
+ * @param {number} start The position from where parse should begin.
+ * @param {Date} date The Date object to hold the parsed date.
+ * @return {number} How many characters parser advanced.
+ */
+gadgets.i18n.DateTimeParse.prototype.parse = function(text, start, date) {
+    return this.internalParse_(text, start, date, false);
+};
+
+
+/**
+ * Parse the given string and fill info into date object.
+ * @param {string} text The string being parsed.
+ * @param {number} start The position from where parse should begin.
+ * @param {Date} date The Date object to hold the parsed date.
+ * @param {boolean} validation If true, input string need to be a valid
+ *     date/time string.
+ * @return {number} How many characters parser advanced.
+ * @private
+ */
+gadgets.i18n.DateTimeParse.prototype.internalParse_ =
+function(text, start, date, validation) {
+    var cal = new gadgets.i18n.DateTimeParse.MyDate_();
+    var parsePos = [start];
+
+  // For parsing abutting numeric fields. 'abutPat' is the
+    // offset into 'pattern' of the first of 2 or more abutting
+    // numeric fields. 'abutStart' is the offset into 'text'
+    // where parsing the fields begins. 'abutPass' starts off as 0
+    // and increments each time we try to parse the fields.
+    var abutPat = -1; // If >=0, we are in a run of abutting numeric fields
+    var abutStart = 0;
+    var abutPass = 0;
+
+    for (var i = 0; i < this.patternParts_.length; ++i) {
+        if (this.patternParts_[i].count > 0) {
+            if (abutPat < 0 && this.patternParts_[i].abutStart) {
+                abutPat = i;
+                abutStart = start;
+                abutPass = 0;
+            }
+
+      // Handle fields within a run of abutting numeric fields. Take
+            // the pattern "HHmmss" as an example. We will try to parse
+            // 2/2/2 characters of the input text, then if that fails,
+            // 1/2/2. We only adjust the width of the leftmost field; the
+            // others remain fixed. This allows "123456" => 12:34:56, but
+            // "12345" => 1:23:45. Likewise, for the pattern "yyyyMMdd" we
+            // try 4/2/2, 3/2/2, 2/2/2, and finally 1/2/2.
+            if (abutPat >= 0) {
+                // If we are at the start of a run of abutting fields, then
+                // shorten this field in each pass. If we can't shorten
+                // this field any more, then the parse of this set of
+                // abutting numeric fields has failed.
+                var count = this.patternParts_[i].count;
+                if (i == abutPat) {
+                    count -= abutPass;
+                    abutPass++;
+                    if (count == 0) {
+                        // tried all possible width, fail now
+                        return 0;
+                    }
+                }
+
+                if (!this.subParse_(text, parsePos, this.patternParts_[i], count,
+                        cal)) {
+                    // If the parse fails anywhere in the run, back up to the
+                    // start of the run and retry.
+                    i = abutPat - 1;
+                    parsePos[0] = abutStart;
+                    continue;
+                }
+            }
+
+                // Handle non-numeric fields and non-abutting numeric fields.
+            else {
+                abutPat = -1;
+                if (!this.subParse_(text, parsePos, this.patternParts_[i], 0, cal)) {
+                    return 0;
+                }
+            }
+        } else {
+            // Handle literal pattern characters. These are any
+            // quoted characters and non-alphabetic unquoted
+            // characters.
+            abutPat = -1;
+      // A run of white space in the pattern matches a run
+            // of white space in the input text.
+            if (this.patternParts_[i].text.charAt(0) == ' ') {
+                // Advance over run in input text
+                var s = parsePos[0];
+                this.skipSpace_(text, parsePos);
+
+        // Must see at least one white space char in input
+                if (parsePos[0] > s) {
+                    continue;
+                }
+            } else if (text.indexOf(this.patternParts_[i].text, parsePos[0]) ==
+                       parsePos[0]) {
+                parsePos[0] += this.patternParts_[i].text.length;
+                continue;
+            }
+      // We fall through to this point if the match fails
+            return 0;
+        }
+    }
+
+  // return progress
+    return cal.calcDate_(date, validation) ? parsePos[0] - start : 0;
+};
+
+/**
+ * Calculate character repeat count in pattern.
+ *
+ * @param {string} pattern It describes the format of date string that need to
+ *     be parsed.
+ * @param {number} start the position of pattern character.
+ *
+ * @return {number} Repeat count.
+ * @private
+ */
+gadgets.i18n.DateTimeParse.prototype.getNextCharCount_ =
+function(pattern, start) {
+    var ch = pattern.charAt(start);
+    var next = start + 1;
+    while (next < pattern.length && pattern.charAt(next) == ch) {
+        ++next;
+    }
+    return next - start;
+};
+
+/**
+ * All acceptable pattern characters.
+ * @private
+ */
+gadgets.i18n.DateTimeParse.PATTERN_CHARS_ = 'GyMdkHmsSEDahKzZv';
+
+/**
+ * Pattern characters that specify numerical field.
+ * @private
+ */
+gadgets.i18n.DateTimeParse.NUMERIC_FORMAT_CHARS_ = 'MydhHmsSDkK';
+
+/**
+ * Check if the pattern part is a numeric field.
+ *
+ * @param {Object} part pattern part to be examined.
+ *
+ * @return {boolean} true if the pattern part is numberic field.
+ * @private
+ */
+gadgets.i18n.DateTimeParse.prototype.isNumericField_ = function(part) {
+    if (part.count <= 0) {
+        return false;
+    }
+    var i = gadgets.i18n.DateTimeParse.NUMERIC_FORMAT_CHARS_.indexOf(
+            part.text.charAt(0));
+    return i > 0 || i == 0 && part.count < 3;
+};
+
+
+/**
+ * Identify the start of an abutting numeric fields' run. Taking pattern
+ * "HHmmss" as an example. It will try to parse 2/2/2 characters of the input
+ * text, then if that fails, 1/2/2. We only adjust the width of the leftmost
+ * field; the others remain fixed. This allows "123456" => 12:34:56, but
+ * "12345" => 1:23:45. Likewise, for the pattern "yyyyMMdd" we try 4/2/2,
+ * 3/2/2, 2/2/2, and finally 1/2/2. The first field of connected numeric
+ * fields will be marked as abutStart, its width can be reduced to accomodate
+ * others.
+ *
+ * @private
+ */
+gadgets.i18n.DateTimeParse.prototype.markAbutStart_ = function() {
+    // abut parts are continuous numeric parts. abutStart is the switch
+    // point from non-abut to abut
+    var abut = false;
+
+    for (var i = 0; i < this.patternParts_.length; i++) {
+        if (this.isNumericField_(this.patternParts_[i])) {
+            // if next part is not following abut sequence, and isNumericField_
+            if (!abut && i + 1 < this.patternParts_.length &&
+                this.isNumericField_(this.patternParts_[i + 1])) {
+                abut = true;
+                this.patternParts_[i].abutStart = true;
+            }
+        } else {
+            abut = false;
+        }
+    }
+};
+
+
+/**
+ * Skip space in the string.
+ *
+ * @param {string} text input string.
+ * @param {Array} pos where skip start, and return back where the skip stops.
+ * @private
+ */
+gadgets.i18n.DateTimeParse.prototype.skipSpace_ = function(text, pos) {
+    var m = text.substring(pos[0]).match(/^\s+/);
+    if (m) {
+        pos[0] += m[0].length;
+    }
+};
+
+/**
+ * Protected method that converts one field of the input string into a
+ * numeric field value.
+ *
+ * @param {string} text the time text to be parsed.
+ * @param {Array} pos Parse position.
+ * @param {Object} part the pattern part for this field.
+ * @param {number} digitCount when > 0, numeric parsing must obey the count.
+ * @param {Object} cal MyDate_ object that will hold parsed value.
+ *
+ * @return {boolean} True if it parses successfully.
+ * @private
+ */
+gadgets.i18n.DateTimeParse.prototype.subParse_ =
+function(text, pos, part, digitCount, cal) {
+    this.skipSpace_(text, pos);
+
+    var start = pos[0];
+    var ch = part.text.charAt(0);
+
+  // parse integer value if it is a numeric field
+    var value = -1;
+    if (this.isNumericField_(part)) {
+        if (digitCount > 0) {
+            if ((start + digitCount) > text.length) {
+                return false;
+            }
+            value = this.parseInt_(
+                    text.substring(0, start + digitCount), pos);
+        } else {
+            value = this.parseInt_(text, pos);
+        }
+    }
+
+    switch (ch) {
+        case 'G': // ERA
+            cal.era = this.matchString_(text, pos, this.symbols_.ERAS);
+            return true;
+        case 'M': // MONTH
+            return this.subParseMonth_(text, pos, cal, value);
+        case 'E':
+            return this.subParseDayOfWeek_(text, pos, cal);
+        case 'a': // AM_PM
+            cal.ampm = this.matchString_(text, pos, this.symbols_.AMPMS);
+            return true;
+        case 'y': // YEAR
+            return this.subParseYear_(text, pos, start, value, part, cal);
+        case 'd': // DATE
+            cal.day = value;
+            return true;
+        case 'S': // FRACTIONAL_SECOND
+            return this.subParseFractionalSeconds_(value, pos, start, cal);
+        case 'h': // HOUR (1..12)
+            if (value == 12) {
+                value = 0;
+            }
+        case 'K': // HOUR (0..11)
+        case 'H': // HOUR_OF_DAY (0..23)
+        case 'k': // HOUR_OF_DAY (1..24)
+            cal.hours = value;
+            return true;
+        case 'm': // MINUTE
+            cal.minutes = value;
+            return true;
+        case 's': // SECOND
+            cal.seconds = value;
+            return true;
+
+        case 'z': // ZONE_OFFSET
+        case 'Z': // TIMEZONE_RFC
+        case 'v': // TIMEZONE_GENERIC
+            return this.subparseTimeZoneInGMT_(text, pos, cal);
+        default:
+            return false;
+    }
+};
+
+/**
+ * Parse year field. Year field is special because
+ * 1) two digit year need to be resolved.
+ * 2) we allow year to take a sign.
+ * 3) year field participate in abut processing.
+ *
+ * @param {string} text the time text to be parsed.
+ * @param {Array} pos Parse position.
+ * @param {number} start where this field start.
+ * @param {number} value integer value of year.
+ * @param {Object} part the pattern part for this field.
+ * @param {Object} cal MyDate_ object that will hold parsed value.
+ *
+ * @return {boolean} True if successful.
+ * @private
+ */
+gadgets.i18n.DateTimeParse.prototype.subParseYear_ =
+function(text, pos, start, value, part, cal) {
+    var ch;
+    if (value < 0) {
+        //possible sign
+        ch = text.charAt(pos[0]);
+        if (ch != '+' && ch != '-') {
+            return false;
+        }
+        pos[0]++;
+        value = this.parseInt_(text, pos);
+        if (value < 0) {
+            return false;
+        }
+        if (ch == '-') {
+            value = -value;
+        }
+    }
+
+  // only if 2 digit was actually parsed, and pattern say it has 2 digit.
+    if (ch == null && (pos[0] - start) == 2 && part.count == 2) {
+        cal.setTwoDigitYear_(value);
+    } else {
+        cal.year = value;
+    }
+    return true;
+};
+
+/**
+ * Parse Month field.
+ *
+ * @param {string} text the time text to be parsed.
+ * @param {Array} pos Parse position.
+ * @param {Object} cal MyDate_ object that will hold parsed value.
+ * @param {number} value numeric value if this field is expressed using
+ *      numeric pattern, or -1 if not.
+ *
+ * @return {boolean} True if parsing successful.
+ * @private
+ */
+gadgets.i18n.DateTimeParse.prototype.subParseMonth_ =
+function(text, pos, cal, value) {
+    // when month is symbols, i.e., MMM or MMMM, value will be -1
+    if (value < 0) {
+        // Want to be able to parse both short and long forms.
+        // Try count == 4 first:
+        value = this.matchString_(text, pos, this.symbols_.MONTHS);
+        if (value < 0) { // count == 4 failed, now try count == 3
+            value = this.matchString_(text, pos, this.symbols_.SHORTMONTHS);
+        }
+        if (value < 0) {
+            return false;
+        }
+        cal.month = value;
+        return true;
+    } else {
+        cal.month = value - 1;
+        return true;
+    }
+};
+
+/**
+ * Parse Day of week field.
+ * @param {string} text the time text to be parsed.
+ * @param {Array} pos Parse position.
+ * @param {Object} cal MyDate_ object that holds parsed value.
+ *
+ * @return {boolean} True if successful.
+ * @private
+ */
+gadgets.i18n.DateTimeParse.prototype.subParseDayOfWeek_ =
+function(text, pos, cal) {
+    // Handle both short and long forms.
+    // Try count == 4 (DDDD) first:
+    var value = this.matchString_(text, pos, this.symbols_.WEEKDAYS);
+    if (value < 0) {
+        value = this.matchString_(text, pos, this.symbols_.SHORTWEEKDAYS);
+    }
+    if (value < 0) {
+        return false;
+    }
+    cal.dayOfWeek = value;
+    return true;
+};
+
+/**
+ * Parse fractional seconds field.
+ *
+ * @param {number} value parsed numberic value.
+ * @param {Array} pos current parse position.
+ * @param {number} start where this field start.
+ * @param {Object} cal MyDate_ object that holds parsed value.
+ *
+ * @return {boolean} True if successful.
+ * @private
+ */
+gadgets.i18n.DateTimeParse.prototype.subParseFractionalSeconds_ =
+function(value, pos, start, cal) {
+    // Fractional seconds left-justify
+    var len = pos[0] - start;
+    cal.milliseconds = len < 3 ? value * Math.pow(10, 3 - len) :
+                       Math.round(value / Math.pow(10, len - 3));
+    return true;
+};
+
+/**
+ * Parse GMT type timezone.
+ *
+ * @param {string} text the time text to be parsed.
+ * @param {Array} pos Parse position.
+ * @param {Object} cal MyDate_ object that holds parsed value.
+ *
+ * @return {boolean} True if successful.
+ * @private
+ */
+gadgets.i18n.DateTimeParse.prototype.subparseTimeZoneInGMT_ =
+function(text, pos, cal) {
+    // First try to parse generic forms such as GMT-07:00. Do this first
+    // in case localized DateFormatZoneData contains the string "GMT"
+    // for a zone; in that case, we don't want to match the first three
+    // characters of GMT+/-HH:MM etc.
+
+    // For time zones that have no known names, look for strings
+    // of the form:
+    //    GMT[+-]hours:minutes or
+    //    GMT[+-]hhmm or
+    //    GMT.
+    if (text.indexOf('GMT', pos[0]) == pos[0]) {
+        pos[0] += 3;  // 3 is the length of GMT
+        return this.parseTimeZoneOffset_(text, pos, cal);
+    }
+
+  // TODO(shanjian): check for named time zones by looking through the locale
+    // data from the DateFormatZoneData strings. should parse both short and long
+    // forms.
+    // subParseZoneString(text, start, cal);
+
+    // As a last resort, look for numeric timezones of the form
+    // [+-]hhmm as specified by RFC 822.  This code is actually
+    // a little more permissive than RFC 822.  It will try to do
+    // its best with numbers that aren't strictly 4 digits long.
+    return this.parseTimeZoneOffset_(text, pos, cal);
+};
+
+/**
+ * Parse time zone offset.
+ *
+ * @param {string} text the time text to be parsed.
+ * @param {Array} pos Parse position.
+ * @param {Object} cal MyDate_ object that holds parsed value.
+ *
+ * @return {boolean} True if successful.
+ * @private
+ */
+gadgets.i18n.DateTimeParse.prototype.parseTimeZoneOffset_ =
+function(text, pos, cal) {
+    if (pos[0] >= text.length) {
+        cal.tzOffset = 0;
+        return true;
+    }
+
+    var sign = 1;
+    switch (text.charAt(pos[0])) {
+        case '-': sign = -1;  // fall through
+        case '+': pos[0]++;
+    }
+
+  // Look for hours:minutes or hhmm.
+    var st = pos[0];
+    var value = this.parseInt_(text, pos);
+    if (value == 0 && pos[0] == st) {
+        return false;
+    }
+
+    var offset;
+    if (pos[0] < text.length && text.charAt(pos[0]) == ':') {
+        // This is the hours:minutes case
+        offset = value * 60;
+        pos[0]++;
+        st = pos[0];
+        value = this.parseInt_(text, pos);
+        if (value == 0 && pos[0] == st) {
+            return false;
+        }
+        offset += value;
+    } else {
+        // This is the hhmm case.
+        offset = value;
+    // Assume "-23".."+23" refers to hours.
+        if (offset < 24 && (pos[0] - st) <= 2)
+            offset *= 60;
+        else
+            // todo: this looks questionable, should have more error checking
+            offset = offset % 100 + offset / 100 * 60;
+    }
+
+    offset *= sign;
+    cal.tzOffset = -offset;
+    return true;
+};
+
+/**
+ * Parse a integer string and return integer value.
+ *
+ * @param {string} text string being parsed.
+ * @param {Array} pos parse position.
+ *
+ * @return {number} Converted integer value.
+ * @private
+ */
+gadgets.i18n.DateTimeParse.prototype.parseInt_ = function(text, pos) {
+    var m = text.substring(pos[0]).match(/^\d+/);
+    if (!m) {
+        return -1;
+    }
+    pos[0] += m[0].length;
+    return parseInt(m[0], 10);
+};
+
+/**
+ * Attempt to match the text at a given position against an array of strings.
+ * Since multiple strings in the array may match (for example, if the array
+ * contains "a", "ab", and "abc", all will match the input string "abcd") the
+ * longest match is returned.
+ *
+ * @param {string} text The string to match to.
+ * @param {Array} pos parsing position.
+ * @param {Array} data The string array that is used to found match from.
+ *
+ * @return {number} the new start position if matching succeeded; a negative
+ *     number indicating matching failure.
+ * @private
+ */
+gadgets.i18n.DateTimeParse.prototype.matchString_ = function(text, pos, data) {
+    // There may be multiple strings in the data[] array which begin with
+    // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
+    // We keep track of the longest match, and return that. Note that this
+    // unfortunately requires us to test all array elements.
+    var bestMatchLength = 0;
+    var bestMatch = -1;
+    var lower_text = text.substring(pos[0]).toLowerCase();
+    for (var i = 0; i < data.length; ++i) {
+        var len = data[i].length;
+    // Always compare if we have no match yet; otherwise only compare
+        // against potentially better matches (longer strings).
+        if (len > bestMatchLength &&
+            lower_text.indexOf(data[i].toLowerCase()) == 0) {
+            bestMatch = i;
+            bestMatchLength = len;
+        }
+    }
+    if (bestMatch >= 0) {
+        pos[0] += bestMatchLength;
+    }
+    return bestMatch;
+};
+
+
+/**
+ * This class hold the intermediate parsing result. After all fields are
+ * consumed, final result will be resolved from this class.
+ * @constructor
+ * @private
+ */
+gadgets.i18n.DateTimeParse.MyDate_ = function() {
+};
+
+/**
+ * 2 digit year special handling. Assuming for example that the
+ * defaultCenturyStart is 6/18/1903. This means that two-digit years will be
+ * forced into the range 6/18/1903 to 6/17/2003. As a result, years 00, 01, and
+ * 02 correspond to 2000, 2001, and 2002. Years 04, 05, etc. correspond
+ * to 1904, 1905, etc. If the year is 03, then it is 2003 if the
+ * other fields specify a date before 6/18, or 1903 if they specify a
+ * date afterwards. As a result, 03 is an ambiguous year. All other
+ * two-digit years are unambiguous.
+ *
+ * @param {number} year 2 digit year value before adjustment.
+ * @return {number} disambiguated year
+ * @private
+ */
+gadgets.i18n.DateTimeParse.MyDate_.prototype.setTwoDigitYear_ = function(year)
+{
+    var now = new Date();
+    var defaultCenturyStartYear =
+            now.getFullYear() - gadgets.i18n.DateTimeParse.ambiguousYearCenturyStart;
+    var ambiguousTwoDigitYear = defaultCenturyStartYear % 100;
+    this.ambiguousYear = (year == ambiguousTwoDigitYear);
+    year += Math.floor(defaultCenturyStartYear / 100) * 100 +
+            (year < ambiguousTwoDigitYear ? 100 : 0);
+    return this.year = year;
+};
+
+/**
+ * Based on the fields set, fill a Date object. For those fields that not
+ * set, use the passed in date object's value.
+ *
+ * @param {Date} date Date object to be filled.
+ * @param {boolean} validation If true, input string will be checked to make
+ *     sure it is valid.
+ *
+ * @return {boolean} false if fields specify a invalid date.
+ * @private
+ */
+gadgets.i18n.DateTimeParse.MyDate_.prototype.calcDate_ =
+function(date, validation) {
+    // year 0 is 1 BC, and so on.
+    if (this.era != undefined && this.year != undefined &&
+        this.era == 0 && this.year > 0) {
+        this.year = -(this.year - 1);
+    }
+
+    if (this.year != undefined) {
+        date.setFullYear(this.year);
+    }
+
+  // The setMonth and setDate logic is a little tricky. We need to make sure
+    // day of month is smaller enough so that it won't cause a month switch when
+    // setting month. For example, if data in date is Nov 30, when month is set
+    // to Feb, because there is no Feb 30, JS adjust it to Mar 2. So Feb 12 will
+    // become  Mar 12.
+    var org_date = date.getDate();
+    date.setDate(1); // every month has a 1st day, this can actually be anything
+    // less than 29.
+
+    if (this.month != undefined) {
+        date.setMonth(this.month);
+    }
+
+    if (this.day != undefined) {
+        date.setDate(this.day);
+    } else {
+        date.setDate(org_date);
+    }
+
+    if (this.hours == undefined) {
+        this.hours = date.getHours();
+    }
+
+  // adjust ampm
+    if (this.ampm != undefined && this.ampm > 0) {
+        if (this.hours < 12) {
+            this.hours += 12;
+        }
+    }
+    date.setHours(this.hours);
+
+    if (this.minutes != undefined) {
+        date.setMinutes(this.minutes);
+    }
+
+    if (this.seconds != undefined) {
+        date.setSeconds(this.seconds);
+    }
+
+    if (this.milliseconds != undefined) {
+        date.setMilliseconds(this.milliseconds);
+    }
+
+  // If validation is needed, verify that the uncalculated date fields
+    // match the calculated date fields.  We do this before we set the
+    // timezone offset, which will skew all of the dates.
+    //
+    // Don't need to check the day of week as it is guaranteed to be
+    // correct or return false below.
+    if (validation &&
+        (this.year != undefined && this.year != date.getFullYear() ||
+         this.month != undefined && this.month != date.getMonth() ||
+         this.dayOfMonth != undefined && this.dayOfMonth != date.getDate() ||
+         this.hours >= 24 || this.minutes >= 60 || this.seconds >= 60 ||
+         this.milliseconds >= 1000)) {
+        return false;
+    }
+
+  // adjust time zone
+    if (this.tzOffset != undefined) {
+        var offset = date.getTimezoneOffset();
+        date.setTime(date.getTime() + (this.tzOffset - offset) * 60 * 1000);
+    }
+
+  // resolve ambiguous year if needed
+    if (this.ambiguousYear) { // the two-digit year == the default start year
+        var defaultCenturyStart = new Date();
+        defaultCenturyStart.setFullYear(
+                defaultCenturyStart.getFullYear() -
+                gadgets.i18n.DateTimeParse.ambiguousYearCenturyStart);
+        if (date.getTime() < defaultCenturyStart.getTime()) {
+            date.setFullYear(defaultCenturyStart.getFullYear() + 100);
+        }
+    }
+
+  // dayOfWeek, validation only
+    if (this.dayOfWeek != undefined) {
+        if (this.day == undefined) {
+            // adjust to the nearest day of the week
+            var adjustment = (7 + this.dayOfWeek - date.getDay()) % 7;
+            if (adjustment > 3) {
+                adjustment -= 7;
+            }
+            var orgMonth = date.getMonth();
+            date.setDate(date.getDate() + adjustment);
+
+      // don't let it switch month
+            if (date.getMonth() != orgMonth) {
+                date.setDate(date.getDate() + (adjustment > 0 ? -7 : 7));
+            }
+        } else if (this.dayOfWeek != date.getDay()) {
+            return false;
+        }
+    }
+    return true;
+};

Propchange: incubator/shindig/trunk/features/src/main/javascript/features/i18n/datetimeparse.js
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/shindig/trunk/features/src/main/javascript/features/i18n/datetimeparse.js
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Added: incubator/shindig/trunk/features/src/main/javascript/features/i18n/datetimeparsetest.js
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/features/src/main/javascript/features/i18n/datetimeparsetest.js?rev=760210&view=auto
==============================================================================
--- incubator/shindig/trunk/features/src/main/javascript/features/i18n/datetimeparsetest.js (added)
+++ incubator/shindig/trunk/features/src/main/javascript/features/i18n/datetimeparsetest.js Mon Mar 30 22:45:30 2009
@@ -0,0 +1,617 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+
+/**
+ * @fileoverview Unit Tests - gadgets.i18n.DateTimeFormat.
+ */
+
+function DateTimeParseTest(name) {
+    TestCase.call(this, name);
+}
+
+DateTimeParseTest.inherits(TestCase);
+
+var DateTimeConstants_en = {
+    ERAS:["BC", "AD"],
+    ERANAMES:["Before Christ", "Anno Domini"],
+    NARROWMONTHS:["J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"],
+    MONTHS:["January", "February", "March", "April", "May", "June", "July",
+        "August", "September", "October", "November", "December"],
+    SHORTMONTHS:["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep",
+        "Oct", "Nov", "Dec"],
+    WEEKDAYS:["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday",
+        "Saturday"],
+    SHORTWEEKDAYS:["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
+    NARROWWEEKDAYS:["S", "M", "T", "W", "T", "F", "S"],
+    SHORTQUARTERS:["Q1", "Q2", "Q3", "Q4"],
+    QUARTERS:["1st quarter", "2nd quarter", "3rd quarter", "4th quarter"],
+    AMPMS:["AM", "PM"],
+    DATEFORMATS:["EEEE, MMMM d, yyyy", "MMMM d, yyyy", "MMM d, yyyy", "M/d/yy"],
+    TIMEFORMATS:["h:mm:ss a v", "h:mm:ss a z", "h:mm:ss a", "h:mm a"],
+    ZONESTRINGS:null
+};
+
+DateTimeConstants_en.STANDALONENARROWMONTHS =
+DateTimeConstants_en.NARROWMONTHS;
+DateTimeConstants_en.STANDALONEMONTHS = DateTimeConstants_en.MONTHS;
+DateTimeConstants_en.STANDALONESHORTMONTHS = DateTimeConstants_en.SHORTMONTHS;
+DateTimeConstants_en.STANDALONEWEEKDAYS = DateTimeConstants_en.WEEKDAYS;
+DateTimeConstants_en.STANDALONESHORTWEEKDAYS =
+DateTimeConstants_en.SHORTWEEKDAYS;
+DateTimeConstants_en.STANDALONENARROWWEEKDAYS =
+DateTimeConstants_en.NARROWWEEKDAYS;
+
+DateTimeParseTest.prototype.setUp = function() {
+    gadgets.i18n.dtParser_
+            = new gadgets.i18n.DateTimeParse(DateTimeConstants_en);
+};
+
+DateTimeParseTest.prototype.testNegativeYear = function() {
+    var date = new Date();
+
+    this.assertTrue(gadgets.i18n.parseDateTime("MM/dd, yyyy", "11/22, 1999",
+            0, date) > 0);
+    this.assertEquals(1999, date.getFullYear());
+    this.assertEquals(11 - 1, date.getMonth());
+    this.assertEquals(22, date.getDate());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("MM/dd, yyyy", "11/22, -1999",
+            0, date) > 0);
+    this.assertEquals(-1999, date.getFullYear());
+    this.assertEquals(11 - 1, date.getMonth());
+    this.assertEquals(22, date.getDate());
+};
+
+DateTimeParseTest.prototype.testEra = function() {
+    var date = new Date();
+
+    this.assertTrue(gadgets.i18n.parseDateTime("MM/dd, yyyyG", "11/22, 1999BC",
+            0, date) > 0);
+    this.assertEquals(-1998, date.getFullYear());
+    this.assertEquals(11 - 1, date.getMonth());
+    this.assertEquals(22, date.getDate());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("MM/dd, yyyyG", "11/22, 1BC",
+            0, date) > 0);
+    this.assertEquals(0, date.getFullYear());
+    this.assertEquals(11 - 1, date.getMonth());
+    this.assertEquals(22, date.getDate());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("MM/dd, yyyyG", "11/22, 1999AD",
+            0, date) > 0);
+    this.assertEquals(1999, date.getFullYear());
+    this.assertEquals(11 - 1, date.getMonth());
+    this.assertEquals(22, date.getDate());
+};
+
+DateTimeParseTest.prototype.testFractionalSeconds = function() {
+    var date = new Date();
+
+    this.assertTrue(gadgets.i18n.parseDateTime("hh:mm:ss.SSS", "11:12:13.956",
+            0, date) > 0);
+    this.assertEquals(11, date.getHours());
+    this.assertEquals(12, date.getMinutes());
+    this.assertEquals(13, date.getSeconds());
+    this.assertEquals(956, date.getTime() % 1000);
+
+    this.assertTrue(gadgets.i18n.parseDateTime("hh:mm:ss.SSS", "11:12:13.95",
+            0, date) > 0);
+    this.assertEquals(11, date.getHours());
+    this.assertEquals(12, date.getMinutes());
+    this.assertEquals(13, date.getSeconds());
+    this.assertEquals(950, date.getTime() % 1000);
+
+    this.assertTrue(gadgets.i18n.parseDateTime("hh:mm:ss.SSS", "11:12:13.9",
+            0, date) > 0);
+    this.assertEquals(11, date.getHours());
+    this.assertEquals(12, date.getMinutes());
+    this.assertEquals(13, date.getSeconds());
+    this.assertEquals(900, date.getTime() % 1000);
+};
+
+DateTimeParseTest.prototype.testAmbiguousYear = function() {
+    var date = new Date();
+
+  // assume this year is 2006, year 27 to 99 will be interpret as 1927 to 1999
+    // year 00 to 25 will be 2000 to 2025. Year 26 can be either 1926 or 2026
+    // depend on the exact time.
+    var org_date = new Date();
+    org_date.setFullYear(org_date.getFullYear() + 20);
+
+  // following 2 lines only works in 2006. Keep them here as they explained
+    // our intention better.
+    //assertTrue(DateTimeParse.parse("01/01/26", 0, "MM/dd/yy", date) > 0);
+    //assertTrue(date.getYear() == 2026 - 1900);
+
+    // rewrite so that it works in any year.
+    org_date.setMonth(0);
+    org_date.setDate(1);
+    org_date.setHours(0);
+    org_date.setMinutes(0);
+    org_date.setSeconds(0);
+    org_date.setMilliseconds(1);
+    var str = '01/01/' + (org_date.getFullYear() % 100);
+    this.assertTrue(gadgets.i18n.parseDateTime("MM/dd/yy", str, 0, date) > 0);
+    this.assertEquals(org_date.getFullYear(), date.getFullYear());
+
+  // following 2 lines only works in 2006. Keep them here as they explained
+    // our intention better.
+    //assertTrue(DateTimeParse.parse("MM/dd/yy", "12/30/26", 0, date) > 0);
+    //assertTrue(date.getYear() == 1926 - 1900);
+
+    // rewrite so that it works in any year.
+    org_date.setMonth(11);
+    org_date.setDate(31);
+    org_date.setHours(23);
+    org_date.setMinutes(59);
+    org_date.setSeconds(59);
+    org_date.setMilliseconds(999);
+    str = '12/31/' + (org_date.getFullYear() % 100);
+    this.assertTrue(gadgets.i18n.parseDateTime("MM/dd/yy", str, 0, date) > 0);
+    this.assertEquals(org_date.getFullYear(), date.getFullYear() + 100);
+
+    this.assertTrue(
+            gadgets.i18n.parseDateTime("yy,MM,dd", "2097,07,21", 0, date) > 0);
+    this.assertEquals(2097, date.getFullYear());
+
+  // Test the ability to move the disambiguation century
+    gadgets.i18n.DateTimeParse.ambiguousYearCenturyStart = 60;
+
+    org_date.setMonth(0);
+    org_date.setDate(1);
+    org_date.setHours(0);
+    org_date.setMinutes(0);
+    org_date.setSeconds(0);
+    org_date.setMilliseconds(1);
+    str = '01/01/' + (org_date.getFullYear() % 100);
+    this.assertTrue(gadgets.i18n.parseDateTime("MM/dd/yy", str, 0, date) > 0);
+
+    this.assertEquals(org_date.getFullYear(), date.getFullYear());
+
+  // Increment org_date 20 more years
+    org_date.setFullYear(date.getFullYear() + 20);
+    str = '01/01/' + (org_date.getFullYear() % 100);
+    this.assertTrue(gadgets.i18n.parseDateTime("MM/dd/yy", str, 0, date) > 0);
+    this.assertEquals(org_date.getFullYear(), date.getFullYear());
+
+    org_date.setFullYear(date.getFullYear() + 21);
+    str = '01/01/' + (org_date.getFullYear() % 100);
+    this.assertTrue(gadgets.i18n.parseDateTime("MM/dd/yy", str, 0, date) > 0);
+    this.assertEquals(org_date.getFullYear(), date.getFullYear() + 100);
+
+  // Reset parameter for other test cases
+    gadgets.i18n.DateTimeParse.ambiguousYearCenturyStart = 80;
+};
+
+DateTimeParseTest.prototype.testLeapYear = function() {
+    var date = new Date();
+
+    this.assertTrue(gadgets.i18n.parseDateTime("MMdd, yyyy", "0229, 2001",
+            0, date) > 0);
+    this.assertEquals(3 - 1, date.getMonth());
+    this.assertEquals(1, date.getDate());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("MMdd, yyyy", "0229, 2000",
+            0, date) > 0);
+    this.assertEquals(2 - 1, date.getMonth());
+    this.assertEquals(29, date.getDate());
+};
+
+DateTimeParseTest.prototype.testAbutField = function() {
+    var date = new Date();
+
+    this.assertTrue(gadgets.i18n.parseDateTime("hhmm", "1122", 0, date) > 0);
+    this.assertEquals(11, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("hhmm", "122", 0, date) > 0);
+    this.assertEquals(1, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(
+            gadgets.i18n.parseDateTime("hhmmss", "112233", 0, date) > 0);
+    this.assertEquals(11, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+    this.assertEquals(33, date.getSeconds());
+
+    this.assertTrue(
+            gadgets.i18n.parseDateTime("hhmmss", "12233", 0, date) > 0);
+    this.assertEquals(1, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+    this.assertEquals(33, date.getSeconds());
+
+    this.assertTrue(
+            gadgets.i18n.parseDateTime("yyyyMMdd", "19991202", 0, date) > 0);
+    this.assertEquals(1999, date.getFullYear());
+    this.assertEquals(12 - 1, date.getMonth());
+    this.assertEquals(02, date.getDate());
+
+    this.assertTrue(
+            gadgets.i18n.parseDateTime("yyyyMMdd", "9991202", 0, date) > 0);
+    this.assertTrue(date.getFullYear() == 999);
+    this.assertEquals(12 - 1, date.getMonth());
+    this.assertEquals(02, date.getDate());
+
+    this.assertTrue(
+            gadgets.i18n.parseDateTime("yyyyMMdd", "991202", 0, date) > 0);
+    this.assertEquals(99, date.getFullYear());
+    this.assertEquals(12 - 1, date.getMonth());
+    this.assertEquals(02, date.getDate());
+
+    this.assertTrue(
+            gadgets.i18n.parseDateTime("yyyyMMdd", "91202", 0, date) > 0);
+    this.assertEquals(9, date.getFullYear());
+    this.assertEquals(12 - 1, date.getMonth());
+    this.assertEquals(02, date.getDate());
+};
+
+DateTimeParseTest.prototype.testYearParsing = function() {
+    var date = new Date();
+
+    this.assertTrue(
+            gadgets.i18n.parseDateTime("yyMMdd", "991202", 0, date) > 0);
+    this.assertEquals(1999, date.getFullYear());
+    this.assertEquals(12 - 1, date.getMonth());
+    this.assertEquals(02, date.getDate());
+
+    this.assertTrue(
+            gadgets.i18n.parseDateTime("yyyyMMdd", "20051202", 0, date) > 0);
+    this.assertEquals(2005, date.getFullYear());
+    this.assertEquals(12 - 1, date.getMonth());
+    this.assertEquals(02, date.getDate());
+};
+
+DateTimeParseTest.prototype.testHourParsing_hh = function() {
+    var date = new Date();
+
+    this.assertTrue(gadgets.i18n.parseDateTime("hhmm", "0022", 0, date) > 0);
+    this.assertEquals(00, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("hhmm", "1122", 0, date) > 0);
+    this.assertEquals(11, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("hhmm", "1222", 0, date) > 0);
+    this.assertEquals(00, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("hhmm", "2322", 0, date) > 0);
+    this.assertEquals(23, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("hhmm", "2422", 0, date) > 0);
+    this.assertEquals(00, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("hhmma", "0022am", 0, date) > 0);
+    this.assertEquals(00, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("hhmma", "1122am", 0, date) > 0);
+    this.assertEquals(11, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("hhmma", "1222am", 0, date) > 0);
+    this.assertEquals(00, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("hhmma", "2322am", 0, date) > 0);
+    this.assertEquals(23, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("hhmma", "2422am", 0, date) > 0);
+    this.assertEquals(00, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("hhmma", "0022pm", 0, date) > 0);
+    this.assertEquals(12, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("hhmma", "1122pm", 0, date) > 0);
+    this.assertEquals(23, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("hhmma", "1222pm", 0, date) > 0);
+    this.assertEquals(12, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("hhmma", "2322pm", 0, date) > 0);
+    this.assertEquals(23, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("hhmma", "2422pm", 0, date) > 0);
+    this.assertEquals(00, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+};
+
+DateTimeParseTest.prototype.testHourParsing_KK = function() {
+    var date = new Date();
+
+    this.assertTrue(gadgets.i18n.parseDateTime("KKmm", "0022", 0, date) > 0);
+    this.assertEquals(00, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("KKmm", "1122", 0, date) > 0);
+    this.assertEquals(11, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("KKmm", "1222", 0, date) > 0);
+    this.assertEquals(12, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("KKmm", "2322", 0, date) > 0);
+    this.assertEquals(23, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("KKmm", "2422", 0, date) > 0);
+    this.assertEquals(00, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+
+    this.assertTrue(gadgets.i18n.parseDateTime("KKmma", "0022am", 0, date) > 0);
+    this.assertEquals(00, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("KKmma", "1122am", 0, date) > 0);
+    this.assertEquals(11, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("KKmma", "1222am", 0, date) > 0);
+    this.assertEquals(12, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("KKmma", "2322am", 0, date) > 0);
+    this.assertEquals(23, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("KKmma", "2422am", 0, date) > 0);
+    this.assertEquals(00, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("KKmma", "0022pm", 0, date) > 0);
+    this.assertEquals(12, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("KKmma", "1122pm", 0, date) > 0);
+    this.assertEquals(23, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("KKmma", "1222pm", 0, date) > 0);
+    this.assertEquals(12, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("KKmma", "2322pm", 0, date) > 0);
+    this.assertEquals(23, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("KKmma", "2422pm", 0, date) > 0);
+    this.assertEquals(00, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+};
+
+DateTimeParseTest.prototype.testHourParsing_kk = function() {
+    var date = new Date();
+
+    this.assertTrue(gadgets.i18n.parseDateTime("kkmm", "0022", 0, date) > 0);
+    this.assertEquals(00, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("kkmm", "1122", 0, date) > 0);
+    this.assertEquals(11, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("kkmm", "1222", 0, date) > 0);
+    this.assertEquals(12, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("kkmm", "2322", 0, date) > 0);
+    this.assertEquals(23, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("kkmm", "2422", 0, date) > 0);
+    this.assertEquals(00, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("kkmma", "0022am", 0, date) > 0);
+    this.assertEquals(00, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("kkmma", "1122am", 0, date) > 0);
+    this.assertEquals(11, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("kkmma", "1222am", 0, date) > 0);
+    this.assertEquals(12, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("kkmma", "2322am", 0, date) > 0);
+    this.assertEquals(23, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("kkmma", "2422am", 0, date) > 0);
+    this.assertEquals(00, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("kkmma", "0022pm", 0, date) > 0);
+    this.assertEquals(12, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("kkmma", "1122pm", 0, date) > 0);
+    this.assertEquals(23, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("kkmma", "1222pm", 0, date) > 0);
+    this.assertEquals(12, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("kkmma", "2322pm", 0, date) > 0);
+    this.assertEquals(23, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("kkmma", "2422pm", 0, date) > 0);
+    this.assertEquals(00, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+};
+
+DateTimeParseTest.prototype.testHourParsing_HH = function() {
+    var date = new Date();
+
+    this.assertTrue(gadgets.i18n.parseDateTime("HHmm", "0022", 0, date) > 0);
+    this.assertEquals(00, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("HHmm", "1122", 0, date) > 0);
+    this.assertEquals(11, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("HHmm", "1222", 0, date) > 0);
+    this.assertEquals(12, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("HHmm", "2322", 0, date) > 0);
+    this.assertEquals(23, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("HHmm", "2422", 0, date) > 0);
+    this.assertEquals(00, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("HHmma", "0022am", 0, date) > 0);
+    this.assertEquals(00, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("HHmma", "1122am", 0, date) > 0);
+    this.assertEquals(11, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("HHmma", "1222am", 0, date) > 0);
+    this.assertEquals(12, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("HHmma", "2322am", 0, date) > 0);
+    this.assertEquals(23, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("HHmma", "2422am", 0, date) > 0);
+    this.assertEquals(00, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("HHmma", "0022pm", 0, date) > 0);
+    this.assertEquals(12, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("HHmma", "1122pm", 0, date) > 0);
+    this.assertEquals(23, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("HHmma", "1222pm", 0, date) > 0);
+    this.assertEquals(12, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("HHmma", "2322pm", 0, date) > 0);
+    this.assertEquals(23, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+
+    this.assertTrue(gadgets.i18n.parseDateTime("HHmma", "2422pm", 0, date) > 0);
+    this.assertEquals(00, date.getHours());
+    this.assertEquals(22, date.getMinutes());
+};
+
+DateTimeParseTest.prototype.testEnglishDate = function() {
+    var date = new Date();
+
+    this.assertTrue(gadgets.i18n.parseDateTime("yyyy MMM dd hh:mm",
+            "2006 Jul 10 15:44", 0, date) > 0);
+    this.assertEquals(2006, date.getFullYear());
+    this.assertEquals(7 - 1, date.getMonth());
+    this.assertEquals(10, date.getDate());
+    this.assertEquals(15, date.getHours());
+    this.assertEquals(44, date.getMinutes());
+};
+
+DateTimeParseTest.prototype.testTimeZone = function() {
+    var date = new Date();
+
+    this.assertTrue(gadgets.i18n.parseDateTime("MM/dd/yyyy, hh:mm:ss zzz",
+            "07/21/2003, 11:22:33 GMT-0700", 0,
+            date) > 0);
+    var hour_GmtMinus07 = date.getHours();
+
+    this.assertTrue(gadgets.i18n.parseDateTime("MM/dd/yyyy, hh:mm:ss zzz",
+            "07/21/2003, 11:22:33 GMT-0600", 0,
+            date) > 0);
+    var hour_GmtMinus06 = date.getHours();
+    this.assertEquals(1, (hour_GmtMinus07 + 24 - hour_GmtMinus06) % 24);
+
+    this.assertTrue(gadgets.i18n.parseDateTime("MM/dd/yyyy, hh:mm:ss zzz",
+            "07/21/2003, 11:22:33 GMT-0800", 0,
+            date) > 0);
+    var hour_GmtMinus08 = date.getHours();
+    this.assertEquals(1, (hour_GmtMinus08 + 24 - hour_GmtMinus07) % 24);
+
+    this.assertTrue(gadgets.i18n.parseDateTime("MM/dd/yyyy, HH:mm:ss zzz",
+            "07/21/2003, 23:22:33 GMT-0800", 0,
+            date) > 0);
+    this.assertEquals((date.getHours() + 24 - hour_GmtMinus07) % 24, 13);
+
+    this.assertTrue(gadgets.i18n.parseDateTime("MM/dd/yyyy, HH:mm:ss zzz",
+            "07/21/2003, 11:22:33 GMT+0800", 0,
+            date) > 0);
+    var hour_Gmt08 = date.getHours();
+    this.assertEquals(16, (hour_GmtMinus08 + 24 - hour_Gmt08) % 24);
+
+    this.assertTrue(gadgets.i18n.parseDateTime("MM/dd/yyyy, HH:mm:ss zzz",
+            "07/21/2003, 11:22:33 GMT0800", 0,
+            date) > 0);
+    this.assertEquals(hour_Gmt08, date.getHours());
+};
+
+DateTimeParseTest.prototype.testWeekDay = function() {
+    var date = new Date();
+
+    this.assertTrue(gadgets.i18n.parseDateTime("EEEE, MM/dd/yyyy",
+            "Wednesday, 08/16/2006", 0, date) > 0);
+    this.assertEquals(2006, date.getFullYear());
+    this.assertEquals(8 - 1, date.getMonth());
+    this.assertEquals(16, date.getDate());
+    this.assertTrue(gadgets.i18n.parseDateTime("EEEE, MM/dd/yyyy",
+            "Tuesday, 08/16/2006", 0, date) == 0);
+    this.assertTrue(gadgets.i18n.parseDateTime("EEEE, MM/dd/yyyy",
+            "Thursday, 08/16/2006", 0, date) == 0);
+    this.assertTrue(gadgets.i18n.parseDateTime("EEEE, MM/dd/yyyy",
+            "Wed, 08/16/2006", 0, date) > 0);
+    this.assertTrue(gadgets.i18n.parseDateTime("EEEE, MM/dd/yyyy",
+            "Wasdfed, 08/16/2006", 0, date) == 0);
+
+    date.setDate(25);
+    this.assertTrue(gadgets.i18n.parseDateTime("EEEE, MM/yyyy",
+            "Wed, 09/2006", 0, date) > 0);
+    this.assertEquals(27, date.getDate());
+
+    date.setDate(30);
+    this.assertTrue(gadgets.i18n.parseDateTime("EEEE, MM/yyyy",
+            "Wed, 09/2006", 0, date) > 0);
+    this.assertEquals(27, date.getDate());
+    date.setDate(30);
+    this.assertTrue(gadgets.i18n.parseDateTime("EEEE, MM/yyyy",
+            "Mon, 09/2006", 0, date) > 0);
+    this.assertEquals(25, date.getDate());
+
+};

Propchange: incubator/shindig/trunk/features/src/main/javascript/features/i18n/datetimeparsetest.js
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/shindig/trunk/features/src/main/javascript/features/i18n/datetimeparsetest.js
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Added: incubator/shindig/trunk/features/src/main/javascript/features/i18n/feature.xml
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/features/src/main/javascript/features/i18n/feature.xml?rev=760210&view=auto
==============================================================================
--- incubator/shindig/trunk/features/src/main/javascript/features/i18n/feature.xml (added)
+++ incubator/shindig/trunk/features/src/main/javascript/features/i18n/feature.xml Mon Mar 30 22:45:30 2009
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations under the License.
+-->
+<feature>
+  <name>opensocial-i18n</name>
+  <dependency>core</dependency>
+  <dependency>core.io</dependency>
+  <gadget>
+    <script src="formatting.js"/>
+    <script src="datetimeformat.js"/>
+    <script src="datetimeparse.js"/>
+    <script src="numberformat.js"/>
+    <script src="currencycodemap.js"/>
+  </gadget>
+</feature>

Propchange: incubator/shindig/trunk/features/src/main/javascript/features/i18n/feature.xml
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/shindig/trunk/features/src/main/javascript/features/i18n/feature.xml
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: incubator/shindig/trunk/features/src/main/javascript/features/i18n/feature.xml
------------------------------------------------------------------------------
    svn:mime-type = text/xml

Added: incubator/shindig/trunk/features/src/main/javascript/features/i18n/formatting.js
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/features/src/main/javascript/features/i18n/formatting.js?rev=760210&view=auto
==============================================================================
--- incubator/shindig/trunk/features/src/main/javascript/features/i18n/formatting.js (added)
+++ incubator/shindig/trunk/features/src/main/javascript/features/i18n/formatting.js Mon Mar 30 22:45:30 2009
@@ -0,0 +1,284 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+
+/**
+ * @fileoverview Functions for dealing with locale-specific formatting
+ *
+ * Note: Gadgets locale is set at render time. Gadget containers should emit
+ * the data files required by the i18n feature by emitting
+ * DateTimeConstants__<2 letter language code>_<2 letter country code>.js
+ * and NumberFormatConstants__<2 letter language code>_<2 letter countrycode>.js.
+ * Data files are located at features/i18n/data. Note the _<2 letter country code>
+ * part is optional. The i18n package above will then load the corresponding
+ * formatter/parser for that locale if any of the functions in the package are
+ * invoked.
+ */
+
+var gadgets = gadgets || {};
+
+gadgets.i18n = gadgets.i18n || {};
+
+gadgets.i18n.dtFormatter_ = null;
+gadgets.i18n.dtParser_ = null;
+gadgets.i18n.numFormatter_ = null;
+
+/**
+ * Format the given date object into a string representation using pattern
+ * specified.
+ * @param {string/number} pattern String to specify patterns or Number used to reference predefined
+ *        pattern that a date should be formatted into.
+ * @param {Date} date Date object being formatted.
+ *
+ * @return {string} string representation of date/time.
+ */
+gadgets.i18n.formatDateTime = function(pattern, date) {
+    if (!gadgets.i18n.dtFormatter_) {
+        gadgets.i18n.dtFormatter_ = new gadgets.i18n.DateTimeFormat(gadgets.i18n.DateTimeConstants);
+        typeof pattern == 'string'
+                ? gadgets.i18n.dtFormatter_.applyPattern(pattern)
+                : gadgets.i18n.dtFormatter_.applyStandardPattern(pattern);
+        gadgets.i18n.dtFormatter_.patternInUse_ = pattern;
+    } else if (gadgets.i18n.dtFormatter_.patternInUse_ != pattern) {
+        typeof pattern == 'string'
+                ? gadgets.i18n.dtFormatter_.applyPattern(pattern)
+                : gadgets.i18n.dtFormatter_.applyStandardPattern(pattern);
+        gadgets.i18n.dtFormatter_.patternInUse_ = pattern;
+    }
+    return gadgets.i18n.dtFormatter_.format(date);
+};
+
+
+/**
+ * Parse a string using the format as specified in pattern string, and
+ * return date in the passed "date" parameter.
+ *
+ * @param {string/number} pattern String to specify patterns or Number used to
+ *        reference predefined
+ *        pattern that a date should be parsed from.
+ * @param {string} text The string that need to be parsed.
+ * @param {number} start The character position in "text" where parse begins.
+ * @param {Date} date The date object that will hold parsed value.
+ *
+ * @return {number} The number of characters advanced or 0 if failed.
+ */
+gadgets.i18n.parseDateTime = function(pattern, text, start, date) {
+    if (!gadgets.i18n.dtParser_) {
+        gadgets.i18n.dtParser_ = new gadgets.i18n.DateTimeParse(gadgets.i18n.DateTimeConstants);
+        typeof pattern == 'string'
+                ? gadgets.i18n.dtParser_.applyPattern(pattern)
+                : gadgets.i18n.dtParser_.applyStandardPattern(pattern);
+        gadgets.i18n.dtParser_.patternInUse_ = pattern;
+    } else if (gadgets.i18n.dtParser_.patternInUse_ != pattern) {
+        typeof pattern == 'string'
+                ? gadgets.i18n.dtParser_.applyPattern(pattern)
+                : gadgets.i18n.dtParser_.applyStandardPattern(pattern);
+        gadgets.i18n.dtParser_.patternInUse_ = pattern;
+    }
+    return gadgets.i18n.dtParser_.parse(text, start, date);
+};
+
+
+/**
+ * Format the number using given pattern.
+ * @param {string/number} pattern String to specify patterns or Number used to
+ *        reference predefined
+ *        pattern that a number should be formatted into.
+ * @param {number} value The number being formatted.
+ * @param {string} opt_currencyCode optional international currency code, it
+ *     determines the currency code/symbol should be used in format/parse. If
+ *     not given, the currency code for current locale will be used.
+ * @return {string} The formatted string.
+ */
+gadgets.i18n.formatNumber = function(pattern, value, opt_currencyCode) {
+    if (!gadgets.i18n.numFormatter_) {
+        gadgets.i18n.numFormatter_ = new gadgets.i18n.NumberFormat(gadgets.i18n.NumberFormatConstants);
+        typeof pattern == 'string'
+                ? gadgets.i18n.numFormatter_.applyPattern(
+                  pattern, opt_currencyCode)
+                : gadgets.i18n.numFormatter_.applyStandardPattern(
+                  pattern, opt_currencyCode);
+        gadgets.i18n.numFormatter_.patternInUse_ = pattern;
+    } else if (gadgets.i18n.numFormatter_.patternInUse_ != pattern) {
+        typeof pattern == 'string'
+                ? gadgets.i18n.numFormatter_.applyPattern(
+                  pattern, opt_currencyCode)
+                : gadgets.i18n.numFormatter_.applyStandardPattern(
+                  pattern, opt_currencyCode);
+        gadgets.i18n.numFormatter_.patternInUse_ = pattern;
+    }
+    return gadgets.i18n.numFormatter_.format(value);
+};
+
+
+/**
+ * Parse the given text using specified pattern to get a number.
+ * @param {string/number} pattern String to specify patterns or Number used
+ *        to reference predefined
+ *        pattern that a number should be parsed from.
+ * @param {string} text input text being parsed.
+ * @param {Array} opt_pos optional one element array that holds position
+ *     information. It tells from where parse should begin. Upon return, it
+ *     holds parse stop position.
+ * @param {string} opt_currencyCode optional international currency code, it
+ *     determines the currency code/symbol should be used in format/parse. If
+ *     not given, the currency code for current locale will be used.
+ * @return {number} Parsed number, 0 if in error.
+ */
+gadgets.i18n.parseNumber = function(pattern, text, opt_pos, opt_currencyCode) {
+    if (!gadgets.i18n.numFormatter_) {
+        gadgets.i18n.numFormatter_ = new gadgets.i18n.NumberFormat();
+        typeof pattern == 'string'
+                ? gadgets.i18n.numFormatter_.applyPattern(pattern,
+                                                          opt_currencyCode)
+                : gadgets.i18n.numFormatter_.applyStandardPattern(
+                  pattern, opt_currencyCode);
+        gadgets.i18n.numFormatter_.patternInUse_ = pattern;
+        gadgets.i18n.numFormatter_.currencyCodeInUse_ = opt_currencyCode;
+    } else if (gadgets.i18n.numFormatter_.patternInUse_ != pattern ||
+               gadgets.i18n.numFormatter_.currencyCodeInUse_
+                       != opt_currencyCode) {
+        typeof pattern == 'string'
+                ? gadgets.i18n.numFormatter_.applyPattern(pattern,
+                                                          opt_currencyCode)
+                : gadgets.i18n.numFormatter_.applyStandardPattern(
+                  pattern, opt_currencyCode);
+        gadgets.i18n.numFormatter_.patternInUse_ = pattern;
+        gadgets.i18n.numFormatter_.currencyCodeInUse_ = opt_currencyCode;
+    }
+    return gadgets.i18n.numFormatter_.parse(text, opt_pos);
+};
+
+// Couple of constants to represent predefined Date/Time format type.
+
+/**
+ * Format for full representations of dates.
+ * @type {number}
+ */
+gadgets.i18n.FULL_DATE_FORMAT = 0;
+
+
+/**
+ * Format for long representations of dates.
+ * @type {number}
+ */
+gadgets.i18n.LONG_DATE_FORMAT = 1;
+
+
+/**
+ * Format for medium representations of dates.
+ * @type {number}
+ */
+gadgets.i18n.MEDIUM_DATE_FORMAT = 2;
+
+
+/**
+ * Format for short representations of dates.
+ * @type {number}
+ */
+gadgets.i18n.SHORT_DATE_FORMAT = 3;
+
+
+/**
+ * Format for full representations of times.
+ * @type {number}
+ */
+gadgets.i18n.FULL_TIME_FORMAT = 4;
+
+
+/**
+ * Format for long representations of times.
+ * @type {number}
+ */
+gadgets.i18n.LONG_TIME_FORMAT = 5;
+
+
+/**
+ * Format for medium representations of times.
+ * @type {number}
+ */
+gadgets.i18n.MEDIUM_TIME_FORMAT = 6;
+
+
+/**
+ * Format for short representations of times.
+ * @type {number}
+ */
+gadgets.i18n.SHORT_TIME_FORMAT = 7;
+
+
+/**
+ * Format for short representations of datetimes.
+ * @type {number}
+ */
+gadgets.i18n.FULL_DATETIME_FORMAT = 8;
+
+
+/**
+ * Format for short representations of datetimes.
+ * @type {number}
+ */
+gadgets.i18n.LONG_DATETIME_FORMAT = 9;
+
+
+/**
+ * Format for medium representations of datetimes.
+ * @type {number}
+ */
+gadgets.i18n.MEDIUM_DATETIME_FORMAT = 10;
+
+
+/**
+ * Format for short representations of datetimes.
+ * @type {number}
+ */
+gadgets.i18n.SHORT_DATETIME_FORMAT = 11;
+
+
+/**
+ * Predefined number format pattern type. The actual pattern is defined
+ * separately for each locale.
+ */
+
+
+/**
+ * Pattern for decimal numbers.
+ * @type {number}
+ */
+gadgets.i18n.DECIMAL_PATTERN = 1;
+
+
+/**
+ * Pattern for scientific numbers.
+ * @type {number}
+ */
+gadgets.i18n.SCIENTIFIC_PATTERN = 2;
+
+
+/**
+ * Pattern for percentages.
+ * @type {number}
+ */
+gadgets.i18n.PERCENT_PATTERN = 3;
+
+
+/**
+ * Pattern for currency.
+ * @type {number}
+ */
+gadgets.i18n.CURRENCY_PATTERN = 4;

Propchange: incubator/shindig/trunk/features/src/main/javascript/features/i18n/formatting.js
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/shindig/trunk/features/src/main/javascript/features/i18n/formatting.js
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision