123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167 |
- // Copyright 2006 The Closure Library Authors. All Rights Reserved.
- //
- // Licensed 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.
- */
- /**
- * Namespace for locale date/time parsing functions
- */
- goog.provide('goog.i18n.DateTimeParse');
- goog.require('goog.asserts');
- goog.require('goog.date');
- goog.require('goog.i18n.DateTimeFormat');
- goog.require('goog.i18n.DateTimeSymbols');
- /**
- * 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 based on current locale.
- * @param {string|number} pattern pattern specification or pattern type.
- * @param {!Object=} opt_dateTimeSymbols Optional symbols to use for this
- * instance rather than the global symbols.
- * @constructor
- * @final
- */
- goog.i18n.DateTimeParse = function(pattern, opt_dateTimeSymbols) {
- goog.asserts.assert(
- goog.isDef(opt_dateTimeSymbols) || goog.isDef(goog.i18n.DateTimeSymbols),
- 'goog.i18n.DateTimeSymbols or explicit symbols must be defined');
- this.patternParts_ = [];
- /**
- * Data structure with all the locale info needed for date formatting.
- * (day/month names, most common patterns, rules for week-end, etc.)
- * @const @private {!goog.i18n.DateTimeSymbolsType}
- */
- this.dateTimeSymbols_ = /** @type {!goog.i18n.DateTimeSymbolsType} */ (
- opt_dateTimeSymbols || goog.i18n.DateTimeSymbols);
- if (typeof pattern == 'number') {
- this.applyStandardPattern_(pattern);
- } else {
- this.applyPattern_(pattern);
- }
- };
- /**
- * Number of years prior to now that the century used to
- * disambiguate two digit years will begin
- *
- * @type {number}
- */
- goog.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 method 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.
- * @private
- */
- goog.i18n.DateTimeParse.prototype.applyPattern_ = function(pattern) {
- 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 < pattern.length - 1 && 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 += '\'';
- i++;
- } else {
- // exit quote
- inQuote = false;
- }
- } else {
- // literal
- buf += ch;
- }
- } else if (goog.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.
- * @private
- */
- goog.i18n.DateTimeParse.prototype.applyStandardPattern_ = function(formatType) {
- var pattern;
- // formatType constants are in consecutive numbers. So it can be used to
- // index array in following way.
- // if type is out of range, default to medium date/time format.
- if (formatType > goog.i18n.DateTimeFormat.Format.SHORT_DATETIME) {
- formatType = goog.i18n.DateTimeFormat.Format.MEDIUM_DATETIME;
- }
- if (formatType < 4) {
- pattern = this.dateTimeSymbols_.DATEFORMATS[formatType];
- } else if (formatType < 8) {
- pattern = this.dateTimeSymbols_.TIMEFORMATS[formatType - 4];
- } else {
- pattern = this.dateTimeSymbols_.DATETIMEFORMATS[formatType - 8];
- pattern = pattern.replace(
- '{1}', this.dateTimeSymbols_.DATEFORMATS[formatType - 8]);
- pattern = pattern.replace(
- '{0}', this.dateTimeSymbols_.TIMEFORMATS[formatType - 8]);
- }
- 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 {goog.date.DateLike} date The Date object to hold the parsed date.
- * @param {number=} opt_start The position from where parse should begin.
- * @return {number} How many characters parser advanced.
- */
- goog.i18n.DateTimeParse.prototype.parse = function(text, date, opt_start) {
- var start = opt_start || 0;
- return this.internalParse_(text, date, start, false /*validation*/);
- };
- /**
- * Parse the given string and fill info into date object. This version will
- * validate the input and make sure it is a valid date/time.
- * @param {string} text The string being parsed.
- * @param {goog.date.DateLike} date The Date object to hold the parsed date.
- * @param {number=} opt_start The position from where parse should begin.
- * @return {number} How many characters parser advanced.
- */
- goog.i18n.DateTimeParse.prototype.strictParse = function(
- text, date, opt_start) {
- var start = opt_start || 0;
- return this.internalParse_(text, date, start, true /*validation*/);
- };
- /**
- * Parse the given string and fill info into date object.
- * @param {string} text The string being parsed.
- * @param {goog.date.DateLike} date The Date object to hold the parsed date.
- * @param {number} start The position from where parse should begin.
- * @param {boolean} validation If true, input string need to be a valid
- * date/time string.
- * @return {number} How many characters parser advanced.
- * @private
- */
- goog.i18n.DateTimeParse.prototype.internalParse_ = function(
- text, date, start, validation) {
- var cal = new goog.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
- */
- goog.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
- */
- goog.i18n.DateTimeParse.PATTERN_CHARS_ = 'GyMdkHmsSEDahKzZvQL';
- /**
- * Pattern characters that specify numerical field.
- * @private
- */
- goog.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 numeric field.
- * @private
- */
- goog.i18n.DateTimeParse.prototype.isNumericField_ = function(part) {
- if (part.count <= 0) {
- return false;
- }
- var i = goog.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 accommodate
- * others.
- *
- * @private
- */
- goog.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<number>} pos where skip start, and return back where the skip
- * stops.
- * @private
- */
- goog.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<number>} pos Parse position.
- * @param {Object} part the pattern part for this field.
- * @param {number} digitCount when > 0, numeric parsing must obey the count.
- * @param {goog.i18n.DateTimeParse.MyDate_} cal object that holds parsed value.
- *
- * @return {boolean} True if it parses successfully.
- * @private
- */
- goog.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
- value = this.matchString_(text, pos, this.dateTimeSymbols_.ERAS);
- if (value >= 0) {
- cal.era = value;
- }
- return true;
- case 'M': // MONTH
- case 'L': // STANDALONEMONTH
- return this.subParseMonth_(text, pos, cal, value);
- case 'E':
- return this.subParseDayOfWeek_(text, pos, cal);
- case 'a': // AM_PM
- value = this.matchString_(text, pos, this.dateTimeSymbols_.AMPMS);
- if (value >= 0) {
- cal.ampm = value;
- }
- return true;
- case 'y': // YEAR
- return this.subParseYear_(text, pos, start, value, part, cal);
- case 'Q': // QUARTER
- return this.subParseQuarter_(text, pos, cal, value);
- case 'd': // DATE
- if (value >= 0) {
- 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)
- if (value >= 0) {
- cal.hours = value;
- }
- return true;
- case 'm': // MINUTE
- if (value >= 0) {
- cal.minutes = value;
- }
- return true;
- case 's': // SECOND
- if (value >= 0) {
- 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<number>} 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 {goog.i18n.DateTimeParse.MyDate_} cal object to hold parsed value.
- *
- * @return {boolean} True if successful.
- * @private
- */
- goog.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 && 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<number>} pos Parse position.
- * @param {goog.i18n.DateTimeParse.MyDate_} cal object to 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
- */
- goog.i18n.DateTimeParse.prototype.subParseMonth_ = function(
- text, pos, cal, value) {
- // when month is symbols, i.e., MMM, MMMM, LLL or LLLL, value will be -1
- if (value < 0) {
- // Want to be able to parse both short and long forms.
- // Try count == 4 first
- var months = this.dateTimeSymbols_.MONTHS
- .concat(this.dateTimeSymbols_.STANDALONEMONTHS)
- .concat(this.dateTimeSymbols_.SHORTMONTHS)
- .concat(this.dateTimeSymbols_.STANDALONESHORTMONTHS);
- value = this.matchString_(text, pos, months);
- if (value < 0) {
- return false;
- }
- // The months variable is multiple of 12, so we have to get the actual
- // month index by modulo 12.
- cal.month = (value % 12);
- return true;
- } else {
- cal.month = value - 1;
- return true;
- }
- };
- /**
- * Parse Quarter field.
- *
- * @param {string} text the time text to be parsed.
- * @param {Array<number>} pos Parse position.
- * @param {goog.i18n.DateTimeParse.MyDate_} cal object to 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
- */
- goog.i18n.DateTimeParse.prototype.subParseQuarter_ = function(
- text, pos, cal, value) {
- // value should be -1, since this is a non-numeric field.
- if (value < 0) {
- // Want to be able to parse both short and long forms.
- // Try count == 4 first:
- value = this.matchString_(text, pos, this.dateTimeSymbols_.QUARTERS);
- if (value < 0) { // count == 4 failed, now try count == 3
- value = this.matchString_(text, pos, this.dateTimeSymbols_.SHORTQUARTERS);
- }
- if (value < 0) {
- return false;
- }
- cal.month = value * 3; // First month of quarter.
- cal.day = 1;
- return true;
- }
- return false;
- };
- /**
- * Parse Day of week field.
- * @param {string} text the time text to be parsed.
- * @param {Array<number>} pos Parse position.
- * @param {goog.i18n.DateTimeParse.MyDate_} cal object to hold parsed value.
- *
- * @return {boolean} True if successful.
- * @private
- */
- goog.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.dateTimeSymbols_.WEEKDAYS);
- if (value < 0) {
- value = this.matchString_(text, pos, this.dateTimeSymbols_.SHORTWEEKDAYS);
- }
- if (value < 0) {
- return false;
- }
- cal.dayOfWeek = value;
- return true;
- };
- /**
- * Parse fractional seconds field.
- *
- * @param {number} value parsed numeric value.
- * @param {Array<number>} pos current parse position.
- * @param {number} start where this field start.
- * @param {goog.i18n.DateTimeParse.MyDate_} cal object to hold parsed value.
- *
- * @return {boolean} True if successful.
- * @private
- */
- goog.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<number>} pos Parse position.
- * @param {goog.i18n.DateTimeParse.MyDate_} cal object to hold parsed value.
- *
- * @return {boolean} True if successful.
- * @private
- */
- goog.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(user): 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<number>} pos Parse position.
- * @param {goog.i18n.DateTimeParse.MyDate_} cal object to hold parsed value.
- *
- * @return {boolean} True if successful.
- * @private
- */
- goog.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) {
- return false;
- }
- var offset;
- if (pos[0] < text.length && text.charAt(pos[0]) == ':') {
- // This is the hours:minutes case
- offset = value * 60;
- pos[0]++;
- value = this.parseInt_(text, pos);
- if (value < 0) {
- 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 an integer string and return integer value.
- *
- * @param {string} text string being parsed.
- * @param {Array<number>} pos parse position.
- *
- * @return {number} Converted integer value or -1 if the integer cannot be
- * parsed.
- * @private
- */
- goog.i18n.DateTimeParse.prototype.parseInt_ = function(text, pos) {
- // Delocalizes the string containing native digits specified by the locale,
- // replaces the native digits with ASCII digits. Leaves other characters.
- // This is the reverse operation of localizeNumbers_ in datetimeformat.js.
- if (this.dateTimeSymbols_.ZERODIGIT) {
- var parts = [];
- for (var i = pos[0]; i < text.length; i++) {
- var c = text.charCodeAt(i) - this.dateTimeSymbols_.ZERODIGIT;
- parts.push(
- (0 <= c && c <= 9) ? String.fromCharCode(c + 0x30) : text.charAt(i));
- }
- text = parts.join('');
- } else {
- text = text.substring(pos[0]);
- }
- var m = text.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<number>} pos parsing position.
- * @param {Array<string>} data The string array of matching patterns.
- *
- * @return {number} the new start position if matching succeeded; a negative
- * number indicating matching failure.
- * @private
- */
- goog.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
- */
- goog.i18n.DateTimeParse.MyDate_ = function() {};
- /**
- * The date's era.
- * @type {?number}
- */
- goog.i18n.DateTimeParse.MyDate_.prototype.era;
- /**
- * The date's year.
- * @type {?number}
- */
- goog.i18n.DateTimeParse.MyDate_.prototype.year;
- /**
- * The date's month.
- * @type {?number}
- */
- goog.i18n.DateTimeParse.MyDate_.prototype.month;
- /**
- * The date's day of month.
- * @type {?number}
- */
- goog.i18n.DateTimeParse.MyDate_.prototype.day;
- /**
- * The date's hour.
- * @type {?number}
- */
- goog.i18n.DateTimeParse.MyDate_.prototype.hours;
- /**
- * The date's before/afternoon denominator.
- * @type {?number}
- */
- goog.i18n.DateTimeParse.MyDate_.prototype.ampm;
- /**
- * The date's minutes.
- * @type {?number}
- */
- goog.i18n.DateTimeParse.MyDate_.prototype.minutes;
- /**
- * The date's seconds.
- * @type {?number}
- */
- goog.i18n.DateTimeParse.MyDate_.prototype.seconds;
- /**
- * The date's milliseconds.
- * @type {?number}
- */
- goog.i18n.DateTimeParse.MyDate_.prototype.milliseconds;
- /**
- * The date's timezone offset.
- * @type {?number}
- */
- goog.i18n.DateTimeParse.MyDate_.prototype.tzOffset;
- /**
- * The date's day of week. Sunday is 0, Saturday is 6.
- * @type {?number}
- */
- goog.i18n.DateTimeParse.MyDate_.prototype.dayOfWeek;
- /**
- * 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
- */
- goog.i18n.DateTimeParse.MyDate_.prototype.setTwoDigitYear_ = function(year) {
- var now = new Date();
- var defaultCenturyStartYear =
- now.getFullYear() - goog.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 {goog.date.DateLike} 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
- */
- goog.i18n.DateTimeParse.MyDate_.prototype.calcDate_ = function(
- date, validation) {
- // Throw exception if date if null.
- if (date == null) {
- throw Error('Parameter \'date\' should not be null.');
- }
- // 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 orgDate = date.getDate();
- // Every month has a 1st day, this can actually be anything less than 29.
- date.setDate(1);
- if (this.month != undefined) {
- date.setMonth(this.month);
- }
- if (this.day != undefined) {
- date.setDate(this.day);
- } else {
- var maxDate =
- goog.date.getNumberOfDaysInMonth(date.getFullYear(), date.getMonth());
- date.setDate(orgDate > maxDate ? maxDate : orgDate);
- }
- if (goog.isFunction(date.setHours)) {
- if (this.hours == undefined) {
- this.hours = date.getHours();
- }
- // adjust ampm
- if (this.ampm != undefined && this.ampm > 0 && this.hours < 12) {
- this.hours += 12;
- }
- date.setHours(this.hours);
- }
- if (goog.isFunction(date.setMinutes) && this.minutes != undefined) {
- date.setMinutes(this.minutes);
- }
- if (goog.isFunction(date.setSeconds) && this.seconds != undefined) {
- date.setSeconds(this.seconds);
- }
- if (goog.isFunction(date.setMilliseconds) && 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.day != undefined && this.day != 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() -
- goog.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;
- };
|