]> git.ipfire.org Git - thirdparty/moment.git/commitdiff
parse flags
authorIsaac Cambron <isaac@isaaccambron.com>
Thu, 26 Sep 2013 11:11:48 +0000 (07:11 -0400)
committerIsaac Cambron <isaac@isaaccambron.com>
Thu, 26 Sep 2013 11:11:48 +0000 (07:11 -0400)
moment.js
test/lang/ka.js
test/moment/create.js
test/moment/is_valid.js

index 05011fadfe588ecb113e2cb9d6cef33450d28691..ead3bc5538d58213993702809dc76e5163816718 100644 (file)
--- a/moment.js
+++ b/moment.js
         round = Math.round,
         i,
 
+        YEAR = 0,
+        MONTH = 1,
+        DATE = 2,
+        HOUR = 3,
+        MINUTE = 4,
+        SECOND = 5,
+        MILLISECOND = 6,
+
         // internal storage for language config files
         languages = {},
 
 
     // Moment prototype object
     function Moment(config) {
+        checkOverflow(config);
         extend(this, config);
     }
 
         return value;
     }
 
+    function daysInMonth(year, month) {
+        return moment.utc([year, month + 1, 0]).date();
+    }
+
+    function isLeapYear(year) {
+        return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
+    }
+
+    function checkOverflow(m) {
+        if (m._a && m._pf.overflow == -2) {
+            m._pf.overflow =
+                m._a[MONTH] < 0 || m._a[MONTH] > 11 ? MONTH :
+                m._a[DATE] < 1 || m._a[DATE] >
+                  (m._pf.overflowMonthOk ? (isLeapYear(m._a[YEAR]) ? 364 : 365) : daysInMonth(m._a[YEAR], m._a[MONTH])) ? DATE :
+                m._a[HOUR] < 0 || m._a[HOUR] > 23 ? HOUR :
+                m._a[MINUTE] < 0 || m._a[MINUTE] > 59 ? MINUTE :
+                m._a[SECOND] < 0 || m._a[SECOND] > 59 ? SECOND :
+                m._a[MILLISECOND] < 0 || m._a[MILLISECOND] > 999 ? MILLISECOND :
+                -1;
+
+        }
+    }
+
+    function initializeParsingFlags(config) {
+        config._pf = {
+            empty : false,
+            unusedTokens : [],
+            trailingInput : '',
+            overflowMonthOk : false,
+            dstShifted : false,
+            overflow : -2,
+            charsLeftOver: 0
+        };
+    }
+
+    function isValid(m) {
+        if (m._isValid == null) {
+            m._isValid = !isNaN(m._d.getTime()) && m._pf.overflow < 0 && !m._pf.empty;
+
+            if (m._strict) {
+                m._isValid = m._isValid &&
+                    m._pf.charsLeftOver === 0 &&
+                    m._pf.trailingInput.length === 0;
+            }
+        }
+        return m._isValid;
+    }
+
     /************************************
         Languages
     ************************************/
         case 's':
             return parseTokenOneOrTwoDigits;
         default :
-            return new RegExp(regexpEscape(token.replace('\\', '')));
+            return null;
         }
     }
 
+    function getDefaultParserRegex(token) {
+        return new RegExp(regexpEscape(unescapeFormat(token.replace('\\', '')), "i"));
+    }
+
     function timezoneMinutesFromString(string) {
         var tzchunk = (parseTokenTimezone.exec(string) || [])[0],
             parts = (tzchunk + '').match(parseTimezoneChunker) || ['-', 0, 0],
                 config._isValid = false;
             }
             break;
-        // DAY OF MONTH
+        // DATE OF MONTH
         case 'D' : // fall through to DD
         case 'DD' :
             if (input != null) {
-                datePartArray[2] = toInt(input);
+                a = toInt(input);
+                datePartArray[DATE] = a;
+                if (a < 1) {
+                    config._pf.overflow = DATE;
+                }
             }
             break;
-        // DAY OF YEAR
+        // DATE OF YEAR
         case 'DDD' : // fall through to DDDD
         case 'DDDD' :
             if (input != null) {
-                datePartArray[1] = 0;
-                datePartArray[2] = toInt(input);
+                datePartArray[MONTH] = 0;
+                datePartArray[DATE] = toInt(input);
+                config._pf.overflowMonthOk = true;
             }
+
             break;
         // YEAR
         case 'YY' :
         case 'a' : // fall through to A
         case 'A' :
             config._isPm = getLangDefinition(config._l).isPM(input);
-            return;
+            break;
         // 24 HOUR
         case 'H' : // fall through to hh
         case 'HH' : // fall through to hh
             config._tzm = timezoneMinutesFromString(input);
             break;
         }
-
-        // if the input is null, the date is not valid
-        if (input == null) {
-            config._isValid = false;
-        }
     }
 
     // convert an array to a date.
 
     // date from string and format string
     function makeDateFromStringAndFormat(config) {
-        if (config._strict) {
-            makeDateFromStringAndStrictFormat(config);
-            return;
-        }
+        config._pf.empty = true;
+        config._a = [];
+
         // This array is used to make a Date, either with `new Date` or `Date.UTC`
         var lang = getLangDefinition(config._l),
             string = '' + config._i,
-            i, parsedInput, tokens,
+            i, parsedInput, tokens, regex, defaulted, token,
             stringLength = string.length,
             totalParsedInputLength = 0;
 
         tokens = expandFormat(config._f, lang).match(formattingTokens);
 
-        config._a = [];
+        if (!tokens) {
+            dateFromArray(config);
+            checkOverflow(config);
+            return;
+        }
+
         for (i = 0; i < tokens.length; i++) {
-            parsedInput = (getParseRegexForToken(tokens[i], config).exec(string) || [])[0];
+            token = tokens[i];
+            defaulted = false;
+            regex = getParseRegexForToken(token, config);
+            if (regex === null) {
+                regex = getDefaultParserRegex(token);
+                defaulted = true;
+            }
+            if (config._strict) {
+                regex = new RegExp("^" + regex.source, regex.ignoreCase ? "i" : "");
+            }
+            parsedInput = (regex.exec(string) || [])[0];
             if (parsedInput) {
                 string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
                 totalParsedInputLength += parsedInput.length;
+                if (!defaulted) {
+                    config._pf.empty = false;
+                }
             }
+            else {
+                if (!defaulted) {
+                    config._pf.unusedTokens.push(token);
+                }
+            }
+
             // don't parse if its not a known token
-            if (formatTokenFunctions[tokens[i]]) {
-                addTimeToArrayFromToken(tokens[i], parsedInput, config);
+            if (formatTokenFunctions[token]) {
+                addTimeToArrayFromToken(token, parsedInput, config);
             }
         }
+
         // add remaining unparsed input length to the string
-        config._il = stringLength - totalParsedInputLength;
+        config._pf.charsLeftOver = stringLength - totalParsedInputLength;
+        config._pf.trailingInput = string;
 
         // handle am pm
         if (config._isPm && config._a[3] < 12) {
         if (config._isPm === false && config._a[3] === 12) {
             config._a[3] = 0;
         }
-        // return
-        dateFromArray(config);
-    }
-
-    function makeDateFromStringAndStrictFormat(config) {
-        var regexp = '', non_token_start = 0,
-            tokens = config._f.match(formattingTokens),
-            match, i, tokenIndex;
-
-        // We're not interested in the result. Just the tokens and their
-        // starting positions.
-        config._f.replace(formattingTokens, function (token) {
-            var offset = arguments[arguments.length - 2],
-                tokenRegexp;
-
-            if (formatTokenFunctions[token]) {
-                tokenRegexp = getParseRegexForToken(token).toString();
-                // Do not remember groups
-                tokenRegexp = tokenRegexp.replace(/\(/g, '(?:');
-
-                // regexp-escape strings in-between tokens
-                if (offset > non_token_start) {
-                    regexp += regexpEscape(unescapeFormat(config._f.substring(non_token_start, offset)));
-                }
-                non_token_start = offset + token.length;
-
-                // add token regexp
-                regexp += '(' + tokenRegexp.substring(1, tokenRegexp.lastIndexOf('/')) + ')';
-            }
-
-            return token;
-        });
-
-        // Handle stuff after last formatting token.
-        if (non_token_start !== config._f.length) {
-            regexp += regexpEscape(unescapeFormat(config._f.substring(non_token_start)));
-        }
-        regexp = new RegExp('^' + regexp + '$');
-        match = config._i.match(regexp);
-        if (match === null) {
-            config._a = [];
-            config._d = new Date(0);
-            config._isValid = false;
-            return;
-        }
-
-        config._a = [];
-        for (i = tokenIndex = 0; i < tokens.length; ++i) {
-            if (formatTokenFunctions[tokens[i]]) {
-                addTimeToArrayFromToken(tokens[i], match[tokenIndex + 1], config);
-                ++tokenIndex;
-            }
-        }
 
+        // return
         dateFromArray(config);
+        checkOverflow(config);
     }
 
     function unescapeFormat(s) {
     // date from string and array of format strings
     function makeDateFromStringAndArray(config) {
         var tempConfig,
-            tempMoment,
             bestMoment,
 
-            scoreToBeat = 99,
+            scoreToBeat = 1000,
             i,
             currentScore;
 
         for (i = 0; i < config._f.length; i++) {
+            currentScore = 0;
             tempConfig = extend({}, config);
+            initializeParsingFlags(tempConfig);
             tempConfig._f = config._f[i];
             makeDateFromStringAndFormat(tempConfig);
-            tempMoment = new Moment(tempConfig);
 
-            currentScore = compareArrays(tempConfig._a, tempMoment.toArray());
-            // if there is any input that was not parsed
-            // add a penalty for that format
-            currentScore += tempMoment._il || 0;
+            if (!isValid(tempConfig)) {
+                currentScore += 100;
+            }
+
+            // if there is any input that was not parsed add a penalty for that format
+            currentScore += tempConfig._pf.charsLeftOver;
+
+            //or tokens
+            currentScore += tempConfig._pf.unusedTokens.length * 10;
+
+            tempConfig._pf.score = currentScore;
 
             if (currentScore < scoreToBeat) {
                 scoreToBeat = currentScore;
-                bestMoment = tempMoment;
+                bestMoment = tempConfig;
             }
         }
 
-        extend(config, bestMoment);
+        extend(config, bestMoment || tempConfig);
     }
 
     // date from iso format
         var input = config._i,
             format = config._f;
 
+        if (typeof config._pf === 'undefined') {
+            initializeParsingFlags(config);
+        }
+
         if (input === null ||
             (typeof input === 'string' &&
              input.replace(/^\s+|\s+$/g, '') === '')) {
             _i : input,
             _f : format,
             _strict : strict
-        });
-        if (m != null) {
-            m = m.utc();
-        }
+        }).utc();
 
         return m;
     };
         },
 
         isValid : function () {
-            if (this._isValid == null) {
-                if (this._a) {
-                    this._isValid = !compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray());
-                } else {
-                    this._isValid = !isNaN(this._d.getTime());
-                }
+            return isValid(this);
+        },
+
+        isDSTShifted : function () {
+
+            if (this._a) {
+                //this is best-effort. What if it's a valid overflow AND DST-shifted?
+                return !this._pf.overflowMonthOk && !compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray());
             }
-            return !!this._isValid;
+
+            return false;
+        },
+
+        parsingFlags : function () {
+            return extend({}, this._pf);
         },
 
         invalidAt: function () {
-            var i, arr1 = this._a, arr2 = (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray();
-            for (i = 6; i >= 0 && arr1[i] === arr2[i]; --i) {
-                // empty loop body
-            }
-            return i;
+            return this._pf.overflow;
         },
 
         utc : function () {
         },
 
         isLeapYear : function () {
-            var year = this.year();
-            return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
+            return isLeapYear(this.year());
         },
 
         isDST : function () {
         },
 
         daysInMonth : function () {
-            return moment.utc([this.year(), this.month() + 1, 0]).date();
+            return daysInMonth(this.year(), this.month());
         },
 
         dayOfYear : function (input) {
index 0250eea6d1950417a154e8b75d9b6d2c60ecc09a..3e34781cf520e68ba3246584aa0e17a16323dd5f 100644 (file)
@@ -113,11 +113,11 @@ exports["lang:ka"] = {
         test.equal(moment([2011, 0, 29]).format('DDDo'), '29-ე', '29-ე');
         test.equal(moment([2011, 0, 30]).format('DDDo'), '30-ე', '30-ე');
 
-        test.equal(moment([2011, 0, 0]).add('days', 40).format('DDDo'),  'მე-40',  'მე-40');
-        test.equal(moment([2011, 0, 0]).add('days', 50).format('DDDo'),  '50-ე',   '50-ე');
-        test.equal(moment([2011, 0, 0]).add('days', 60).format('DDDo'),  'მე-60',  'მე-60');
-        test.equal(moment([2011, 0, 0]).add('days', 100).format('DDDo'), 'მე-100', 'მე-100');
-        test.equal(moment([2011, 0, 0]).add('days', 101).format('DDDo'), '101-ე',  '101-ე');
+        test.equal(moment("2011 40", "YYYY DDD").format('DDDo'),  'მე-40',  'მე-40');
+        test.equal(moment("2011 50", "YYYY DDD").format('DDDo'),  '50-ე',   '50-ე');
+        test.equal(moment("2011 60", "YYYY DDD").format('DDDo'),  'მე-60',  'მე-60');
+        test.equal(moment("2011 100", "YYYY DDD").format('DDDo'), 'მე-100', 'მე-100');
+        test.equal(moment("2011 101", "YYYY DDD").format('DDDo'), '101-ე',  '101-ე');
         test.done();
     },
 
index 186eec79c23f4f685a8e998ae8fe18a4a325470f..34b5ce5f096edf77f2e8bf3e4660ba92dfc9ea69 100644 (file)
@@ -125,9 +125,9 @@ exports.create = {
         test.equal(moment('05/1/2012 12:25:00 am', 'MM/DD/YYYY h:m:s a').format('MM/DD/YYYY'), '05/01/2012', 'should not break if am/pm is left off from the parsing tokens');
         test.equal(moment('05/1/2012 12:25:00 pm', 'MM/DD/YYYY h:m:s a').format('MM/DD/YYYY'), '05/01/2012', 'should not break if am/pm is left off from the parsing tokens');
 
-        test.ok(moment('05/1/2012 12:25:00', 'MM/DD/YYYY h:m:s a').isValid())
-        test.ok(moment('05/1/2012 12:25:00 am', 'MM/DD/YYYY h:m:s a').isValid())
-        test.ok(moment('05/1/2012 12:25:00 pm', 'MM/DD/YYYY h:m:s a').isValid())
+        test.ok(moment('05/1/2012 12:25:00', 'MM/DD/YYYY h:m:s a').isValid());
+        test.ok(moment('05/1/2012 12:25:00 am', 'MM/DD/YYYY h:m:s a').isValid());
+        test.ok(moment('05/1/2012 12:25:00 pm', 'MM/DD/YYYY h:m:s a').isValid());
 
         test.done();
     },
@@ -140,10 +140,10 @@ exports.create = {
         test.equal(moment(' ', 'DD').format('YYYY-MM-DD HH:mm:ss'), 'Invalid date');
         test.equal(moment(' ', ['MM', "DD"]).format('YYYY-MM-DD HH:mm:ss'), 'Invalid date');
 
-        test.ok(!moment('', 'MM').isValid())
-        test.ok(!moment(' ', 'MM').isValid())
-        test.ok(!moment(' ', 'DD').isValid())
-        test.ok(!moment(' ', ['MM', "DD"]).isValid())
+        test.ok(!moment('', 'MM').isValid());
+        test.ok(!moment(' ', 'MM').isValid());
+        test.ok(!moment(' ', 'DD').isValid());
+        test.ok(!moment(' ', ['MM', "DD"]).isValid());
 
         test.done();
     },
@@ -231,6 +231,7 @@ exports.create = {
                 ['LLLL',                'Thursday, September 2 1999 12:30 AM'],
                 ['llll',                'Thu, Sep 2 1999 12:30 AM']
             ],
+            m,
             i;
 
         test.expect(2 * a.length);
@@ -318,8 +319,8 @@ exports.create = {
 
         test.equal(moment('13-11-1999', ['MM/DD/YYYY', 'DD/MM/YYYY']).format('MM DD YYYY'), '11 13 1999', 'second must be month');
         test.equal(moment('11-13-1999', ['MM/DD/YYYY', 'DD/MM/YYYY']).format('MM DD YYYY'), '11 13 1999', 'first must be month');
-        test.equal(moment('13-14-1999', ['MM/DD/YYYY', 'DD/MM/YYYY']).format('MM DD YYYY'), '01 14 2000', 'either can be a month, month first format');
-        test.equal(moment('13-14-1999', ['DD/MM/YYYY', 'MM/DD/YYYY']).format('MM DD YYYY'), '02 13 2000', 'either can be a month, day first format');
+        test.equal(moment('01-02-2000', ['MM/DD/YYYY', 'DD/MM/YYYY']).format('MM DD YYYY'), '01 02 2000', 'either can be a month, month first format');
+        test.equal(moment('02-01-2000', ['DD/MM/YYYY', 'MM/DD/YYYY']).format('MM DD YYYY'), '01 02 2000', 'either can be a month, day first format');
 
         test.equal(moment('11-02-10', ['MM/DD/YY', 'YY MM DD', 'DD-MM-YY']).format('MM DD YYYY'), '02 11 2010', 'all unparsed substrings have influence on format penalty');
         test.equal(moment('11-02-10', ['MM.DD.YY', 'DD-MM-YY']).format('MM DD YYYY'), '02 11 2010', 'escape RegExp special characters on comparing');
@@ -463,13 +464,13 @@ exports.create = {
     "null" : function (test) {
         test.expect(6);
 
-        test.Ok(!moment('').isValid());
-        test.Ok(!moment(null).isValid());
-        test.Ok(!moment('', 'YYYY-MM-DD').isValid());
+        test.ok(!moment('').isValid());
+        test.ok(!moment(null).isValid());
+        test.ok(!moment('', 'YYYY-MM-DD').isValid());
 
-        test.Ok(!moment.utc('').isValid(), "Calling moment.utc('')");
-        test.Ok(!moment.utc(null).isValid(), "Calling moment.utc(null)");
-        test.Ok(!moment.utc('', 'YYYY-MM-DD').isValid(), "Calling moment.utc('', 'YYYY-MM-DD')");
+        test.ok(!moment.utc('').isValid(), "Calling moment.utc('')");
+        test.ok(!moment.utc(null).isValid(), "Calling moment.utc(null)");
+        test.ok(!moment.utc('', 'YYYY-MM-DD').isValid(), "Calling moment.utc('', 'YYYY-MM-DD')");
         test.done();
     },
 
index fc47860dbf25e54c35e1ee139793b1540c947706..ead45fd6b29f7f358c33944b518e032ddac5d0fa 100644 (file)
@@ -2,16 +2,21 @@ var moment = require("../../moment");
 
 exports.is_valid = {
     "array bad month" : function (test) {
-        test.expect(2);
+        var underflow = moment([2010, -1]),
+            overflow = moment([2100, 12]);
+
+        test.expect(4);
+        test.equal(underflow.isValid(), false, 'month -1 invalid');
+        test.equal(underflow.parsingFlags().overflow, 1, 'month -1 overflow');
 
-        test.equal(moment([2010, -1]).isValid(), false, 'month -1');
-        test.equal(moment([2100, 12]).isValid(), false, 'month 12');
+        test.equal(overflow.isValid(), false, 'month 12 invalid');
+        test.equal(overflow.parsingFlags().overflow, 1, 'month 12 invalid');
 
         test.done();
     },
 
     "array good month" : function (test) {
-        test.expect(24);
+        test.expect(12 * 2);
 
         for (var i = 0; i < 12; i++) {
             test.equal(moment([2010, i]).isValid(), true, 'month ' + i);
@@ -22,13 +27,21 @@ exports.is_valid = {
     },
 
     "array bad date" : function (test) {
-        test.expect(4);
+        var tests = [
+            moment([2010, 0, 0]),
+            moment([2100, 0, 32]),
+            moment.utc([2010, 0, 0]),
+            moment.utc([2100, 0, 32])
+        ],
+        i, m;
 
-        test.equal(moment([2010, 0, 0]).isValid(), false, 'date 0');
-        test.equal(moment([2100, 0, 32]).isValid(), false, 'date 32');
+        test.expect(tests.length * 2);
 
-        test.equal(moment.utc([2010, 0, 0]).isValid(), false, 'utc date 0');
-        test.equal(moment.utc([2100, 0, 32]).isValid(), false, 'utc date 32');
+        for (i in tests) {
+            m = tests[i];
+            test.equal(m.isValid(), false);
+            test.equal(m.parsingFlags().overflow, 2);
+        }
 
         test.done();
     },
@@ -77,7 +90,7 @@ exports.is_valid = {
         test.expect(2);
 
         test.equal(moment('fail', "MM-DD-YYYY").isValid(), false, 'string "fail" with format "MM-DD-YYYY"');
-        test.equal(moment("xx-xx-2001", 'DD-MM-YYY').isValid(), false, 'string "xx-xx-2001" with format "MM-DD-YYYY"');
+        test.equal(moment("xx-xx-2001", 'DD-MM-YYY').isValid(), true, 'string "xx-xx-2001" with format "MM-DD-YYYY"');
         test.done();
     },