]> git.ipfire.org Git - thirdparty/moment.git/commitdiff
revamping week and weekday parsing
authorIsaac Cambron <isaac@isaaccambron.com>
Fri, 4 Oct 2013 07:32:58 +0000 (03:32 -0400)
committerIsaac Cambron <isaac@isaaccambron.com>
Fri, 4 Oct 2013 07:32:58 +0000 (03:32 -0400)
1  2 
moment.js
test/moment/create.js
test/moment/is_valid.js
test/moment/parsing_flags.js

diff --cc moment.js
index ab18e71343938b6819ce5a3136c052d374962ed3,745f42da7ca5516ea79118ccc7fd18f98c5881e7..263e22914a86019a6c95993d9c734fd5de7c4408
+++ b/moment.js
          return value;
      }
  
-     function applyWeekData(m) {
-         var apply = function () {
-             var i, unit, norm, val, found = false;
-             for (i in arguments) {
-                 unit = arguments[i];
-                 norm = normalizeUnits(unit);
-                 if (m._w[unit]) {
-                     found = true;
-                     val = m._w[unit];
-                     val = i === '0' && val.length < 3 ? (parseInt(val, 10) > 68 ? '19' + val : '20' + val) : val;
-                     m.set(norm, val);
-                 }
-                 else if (found) {
-                     m.set(norm, unit === 'e' ? 0 : 1);
-                 }
+     function daysInMonth(year, month) {
 -        return moment.utc([year, month + 1, 0]).date();
++        return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
++    }
++
++    function daysInYear(year) {
++        return isLeapYear(year) ? 366 : 365;
+     }
+     function isLeapYear(year) {
+         return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
+     }
+     function checkOverflow(m) {
+         var overflow;
+         if (m._a && m._pf.overflow === -2) {
+             overflow =
+                 m._a[MONTH] < 0 || m._a[MONTH] > 11 ? MONTH :
+                 m._a[DATE] < 1 || m._a[DATE] > 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;
+             if (m._pf._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) {
+                 overflow = DATE;
              }
-         };
  
-         apply('gg', 'w', 'e');
-         apply('GG', 'W', 'E');
+             m._pf.overflow = overflow;
+         }
+     }
+     function initializeParsingFlags(config) {
+         config._pf = {
+             empty : false,
+             unusedTokens : [],
+             unusedInput : [],
+             overflow : -2,
+             charsLeftOver : 0,
+             nullInput : false,
+             invalidMonth : null,
+             userInvalidated : false,
+         };
+     }
  
-         if (m._w['d']) {
-             m.day(m._w['d']);
+     function isValid(m) {
+         if (m._isValid == null) {
+             m._isValid = !isNaN(m._d.getTime()) &&
+                 m._pf.overflow < 0 &&
+                 !m._pf.empty &&
+                 !m._pf.invalidMonth &&
+                 !m._pf.nullInput &&
+                 !m._pf.userInvalidated;
+             if (m._strict) {
+                 m._isValid = m._isValid &&
+                     m._pf.charsLeftOver === 0 &&
+                     m._pf.unusedTokens.length === 0;
+             }
          }
+         return m._isValid;
+     }
+     function normalizeLanguage(key) {
+         return key ? key.toLowerCase().replace('_', '-') : key;
      }
  
      /************************************
          week : function (mom) {
              return weekOfYear(mom, this._week.dow, this._week.doy).week;
          },
++
          _week : {
              dow : 0, // Sunday is the first day of the week.
              doy : 6  // The week that contains Jan 1st is the first week of the year.
          case 'h':
          case 'm':
          case 's':
 +        case 'w':
 +        case 'ww':
 +        case 'W':
 +        case 'WW':
 +        case 'e':
 +        case 'ee':
 +        case 'E':
 +        case 'EE':
              return parseTokenOneOrTwoDigits;
          default :
-             return new RegExp(regexpEscape(token.replace('\\', '')));
+             a = new RegExp(regexpEscape(unescapeFormat(token.replace('\\', '')), "i"));
+             return a;
          }
      }
  
              config._useUTC = true;
              config._tzm = timezoneMinutesFromString(input);
              break;
 +        case 'w':
 +        case 'ww':
 +        case 'W':
 +        case 'WW':
 +        case 'd':
 +        case 'dd':
 +        case 'ddd':
 +        case 'dddd':
 +        case 'e':
 +        case 'E':
 +            token = token.substr(0, 1);
 +            /* falls through */
 +        case 'gg':
 +        case 'gggg':
 +        case 'GG':
 +        case 'GGGG':
 +        case 'GGGGG':
 +            token = token.substr(0, 2);
 +            if (input) {
 +                config._w = config._w || {};
 +                config._w[token] = input;
 +            }
 +            break;
          }
-         // if the input is null, the date is not valid
-         if (input == null) {
-             config._isValid = false;
-         }
      }
  
      // convert an array to a date.
      // the array should mirror the parameters below
      // note: all values past the year are optional and will default to the lowest possible value.
      // [year, month, day , hour, minute, second, millisecond]
-     function dateFromArray(config) {
-         var i, date, input = [], currentDate;
+     function dateFromConfig(config) {
 -        var i, date, input = [], currentDate, yearToUse;
++        var i, date, input = [], currentDate, yearToUse, fixYear, w, temp, lang;
  
          if (config._d) {
              return;
          }
  
 -            if (config._dayOfYear > (isLeapYear(yearToUse) ? 364 : 365)) {
+         currentDate = currentDateArray(config);
++        //compute day of the year form weeks and weekdays
++        if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
++            fixYear = function (val) {
++                return val ?
++                  (val.length < 3 ? (parseInt(val, 10) > 68 ? '19' + val : '20' + val) : val) :
++                  (config._a[YEAR] != null ? currentDate[YEAR] :
++                    (config._a[YEAR] == null ? currentDate[YEAR] : config._a[YEAR]));
++            };
++
++            w = config._w;
++            if (w.GG != null || w.W != null || w.E != null) {
++                temp = dayOfYearFromWeeks(fixYear(w.GG), w.W, parseWeekday(w.E, config._l), 4, 1);
++            }
++            else {
++                lang = getLangDefinition(config._l);
++                temp = dayOfYearFromWeeks(fixYear(w.gg), w.w, parseWeekday(w.e || w.d, lang), lang._week.doy, lang._week.dow);
++            }
++
++            config._a[YEAR] = temp.year;
++            config._dayOfYear = temp.dayOfYear;
++        }
++
+         //if the day of the year is set, figure out what it is
+         if (config._dayOfYear) {
+             yearToUse = config._a[YEAR] == null ? currentDate[YEAR] : config._a[YEAR];
++            if (config._dayOfYear > daysInYear(yearToUse)) {
+                 config._pf._overflowDayOfYear = true;
+             }
+             date = makeUTCDate(yearToUse, 0, config._dayOfYear);
+             config._a[MONTH] = date.getUTCMonth();
+             config._a[DATE] = date.getUTCDate();
+         }
          // Default to current date.
          // * if no year, month, day of month are given, default to today
          // * if day of month is given, default month and year
  
      // date from string and format string
      function makeDateFromStringAndFormat(config) {
-         if (config._strict) {
-             makeDateFromStringAndStrictFormat(config);
-             return;
-         }
 +
+         config._a = [];
+         config._pf.empty = true;
  
          // This array is used to make a Date, either with `new Date` or `Date.UTC`
          var lang = getLangDefinition(config._l),
          }
      }
  
+     function makeDate(y, m, d, h, M, s, ms) {
+         //can't just apply() to create a date:
+         //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply
+         var date = new Date(y, m, d, h, M, s, ms);
+         //the date constructor doesn't accept years < 1970
+         if (y < 1970) {
+             date.setFullYear(y);
+         }
+         return date;
+     }
+     function makeUTCDate(y) {
+         var date = new Date(Date.UTC.apply(null, arguments));
+         if (y < 1970) {
+             date.setUTCFullYear(y);
+         }
+         return date;
+     }
++    function parseWeekday(input, language) {
++        if (typeof input === 'string') {
++            if (!isNaN(input)) {
++                input = parseInt(input, 10);
++            }
++            else {
++                input = language.weekdaysParse(input);
++                if (typeof input !== 'number') {
++                    return null;
++                }
++            }
++        }
++        return input;
++    }
 +
      /************************************
          Relative Time
      ************************************/
          };
      }
  
++    function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) {
++        var d = new Date(year, 0).getUTCDay(),
++            daysToAdd, dayOfYear;
++
++        week = week || 1;
++        weekday = weekday != null ? weekday : firstDayOfWeek;
++        daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0);
++        dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1;
++
++        return {
++            year: dayOfYear > 0 ? year : year - 1,
++            dayOfYear: dayOfYear > 0 ?  dayOfYear : daysInYear(year - 1) + dayOfYear
++        };
++    }
  
      /************************************
          Top Level Functions
          day : function (input) {
              var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
              if (input != null) {
--                if (typeof input === 'string') {
-                     if (!isNaN(input)) {
-                         input = parseInt(input, 10);
-                     }
-                     else {
-                         input = this.lang().weekdaysParse(input);
-                         if (typeof input !== 'number') {
-                             return this;
-                         }
 -                    input = this.lang().weekdaysParse(input);
 -                    if (typeof input !== 'number') {
 -                        return this;
--                    }
--                }
++                input = parseWeekday(input, this.lang());
                  return this.add({ d : input - day });
              } else {
                  return day;
index 761b96094e76d52a6089607a616ed35c43ee3c7a,1b82f06cf7baee5b202e1b4b2a7986ca241d6694..ba14f165589ac0829e03659ec93a99ccf9e20c19
@@@ -529,54 -548,6 +548,55 @@@ exports.create = 
          test.equal(moment('2012 july', 'YYYY MMM', 'en').month(), 6, "should be able to parse in a specific language");
  
          moment.lang('parselang', null);
-         test.equal(moment('99', 'gg').format('YYYY MM DD'), "1998 12 27", 'week-year two digits');
-         test.equal(moment('1999', 'gggg').format('YYYY MM DD'), "1998 12 27", 'week-year four digits');
 +        test.done();
 +    },
 +
 +    "parsing week and weekday information" : function (test) {
 +
 +        //year
-         test.equal(moment('3', 'ee').weekday(), 3, "day sets the day by itself");
-         test.equal(moment('2013 07 03', 'YYYY MM ee').month(), 6, "weekday keeps the parsed year and month");
-         test.equal(moment('3', 'EE').isoWeekday(), 3, "iso day sets the day by itself");
-         test.equal(moment('2013 07 03', 'YYYY MM EE').month(), 6, "iso weekday keeps the parsed year and month");
++        test.equal(moment('12', 'gg').format('YYYY MM DD'), "2012 01 01", 'week-year two digits');
++        test.equal(moment('2012', 'gggg').format('YYYY MM DD'), "2012 01 01", 'week-year four digits');
++
++        test.equal(moment('99', 'gg').format('YYYY MM DD'), "1998 12 27", 'week-year two digits previous year');
++        test.equal(moment('1999', 'gggg').format('YYYY MM DD'), "1998 12 27", 'week-year four digits previous year');
++
 +        test.equal(moment('99', 'GG').format('YYYY MM DD'), "1999 01 04", 'iso week-year two digits');
 +        test.equal(moment('1999', 'GGGG').format('YYYY MM DD'), "1999 01 04", 'iso week-year four digits');
 +
++        test.equal(moment('13', 'GG').format('YYYY MM DD'), "2012 12 31", 'iso week-year two digits previous year');
++        test.equal(moment('2013', 'GGGG').format('YYYY MM DD'), "2012 12 31", 'iso week-year four digits previous year');
++
 +        //year + week
 +        test.equal(moment('1999 37', 'gggg w').format('YYYY MM DD'), "1999 09 05", 'week');
 +        test.equal(moment('1999 37', 'gggg ww').format('YYYY MM DD'), "1999 09 05", 'week double');
 +        test.equal(moment('1999 37', 'GGGG W').format('YYYY MM DD'), "1999 09 13", 'iso week');
 +        test.equal(moment('1999 37', 'GGGG WW').format('YYYY MM DD'), "1999 09 13", 'iso week double');
 +
 +        //year + week + day
 +        test.equal(moment('1999 37 4', 'gggg ww e').format('YYYY MM DD'), "1999 09 09", 'day');
 +        test.equal(moment('1999 37 4', 'gggg ww ee').format('YYYY MM DD'), "1999 09 09", 'day double');
 +        test.equal(moment('1999 37 4', 'GGGG WW E').format('YYYY MM DD'), "1999 09 16", 'iso day');
 +        test.equal(moment('1999 37 4', 'GGGG WW EE').format('YYYY MM DD'), "1999 09 16", 'iso day double');
 +
 +        //d
 +        test.equal(moment('1999 37 4', 'gggg ww d').format('YYYY MM DD'), "1999 09 09", 'd');
 +        test.equal(moment('1999 37 Th', 'gggg ww dd').format('YYYY MM DD'), "1999 09 09", 'dd');
 +        test.equal(moment('1999 37 Thu', 'gggg ww ddd').format('YYYY MM DD'), "1999 09 09", 'ddd');
 +        test.equal(moment('1999 37 Thursday', 'gggg ww dddd').format('YYYY MM DD'), "1999 09 09", 'dddd');
 +
 +        //lower-order only
 +        test.equal(moment('22', 'ww').week(), 22, "week sets the week by itself");
 +        test.equal(moment('22', 'ww').year(), moment().year(), "week keeps this year");
 +        test.equal(moment('2013 22', 'YYYY ww').year(), 2013, "week keeps parsed year");
 +
 +        test.equal(moment('22', 'WW').isoWeek(), 22, "iso week sets the week by itself");
 +        test.equal(moment('2013 22', 'YYYY WW').year(), 2013, "iso week keeps parsed year");
 +        test.equal(moment('22', 'WW').year(), moment().year(), "iso week keeps this year");
 +
 +        //order
 +        test.equal(moment('6 2013 2', 'e gggg w').format('YYYY MM DD'), "2013 01 12", "order doesn't matter");
 +        test.equal(moment('6 2013 2', 'E GGGG W').format('YYYY MM DD'), "2013 01 12", "iso order doesn't matter");
 +
          test.done();
      }
  };
index fc47860dbf25e54c35e1ee139793b1540c947706,216d0346bd793b636e3f1ceec05d9de0a70f4200..6fc65163a2a9347506cef298ff836074c0c0396c
@@@ -221,5 -227,36 +227,37 @@@ exports.is_valid = 
  
          test.equal(moment(" ", "X").isValid(), false, 'string space');
          test.done();
-     }
+     },
+     "empty" : function (test) {
+         test.equal(moment(null).isValid(), false, 'null');
+         test.equal(moment('').isValid(), false, 'empty string');
+         test.equal(moment(' ').isValid(), false, 'empty when trimmed');
+         test.equal(moment(null, 'YYYY').isValid(), false, 'format + null');
+         test.equal(moment('', 'YYYY').isValid(), false, 'format + empty string');
+         test.equal(moment(' ', 'YYYY').isValid(), false, 'format + empty when trimmed');
+         test.done();
+     },
+     "days of the year" : function (test) {
+         test.equal(moment('2010 300', 'YYYY DDDD').isValid(), true, 'day 300 of year valid');
+         test.equal(moment('2010 365', 'YYYY DDDD').isValid(), true, 'day 365 of year valid');
+         test.equal(moment('2010 366', 'YYYY DDDD').isValid(), false, 'day 366 of year invalid');
 -        test.equal(moment('2012 364', 'YYYY DDDD').isValid(), true, 'day 364 of leap year valid');
 -        test.equal(moment('2012 365', 'YYYY DDDD').isValid(), false, 'day 365 of leap year invalid');
++        test.equal(moment('2012 365', 'YYYY DDDD').isValid(), true, 'day 365 of leap year valid');
++        test.equal(moment('2012 366', 'YYYY DDDD').isValid(), true, 'day 366 of leap year valid');
++        test.equal(moment('2012 367', 'YYYY DDDD').isValid(), false, 'day 367 of leap year invalid');
+         test.done();
+     },
+     "oddball permissiveness" : function (test) {
+         //https://github.com/moment/moment/issues/1128
+         test.ok(moment("2010-10-3199", ["MM/DD/YYYY", "MM-DD-YYYY", "YYYY-MM-DD"]).isValid());
+         //https://github.com/moment/moment/issues/1122
+         test.ok(moment("3:25", ["h:mma", "hh:mma", "H:mm", "HH:mm"]).isValid());
+         test.done();
+     },
  };
index 0000000000000000000000000000000000000000,4a5d079c67ddaaea7c512fa7f073255c9dfd0154..ae73d27bf2678ff007b63b8d3994f66ced2855d3
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,178 +1,178 @@@
 -        test.equal(flags('2012 364', 'YYYY DDDD').overflow, -1, 'day 364 of leap year valid');
 -        test.equal(flags('2012 365', 'YYYY DDDD').overflow, 2, 'day 365 of leap year invalid');
+ var moment = require('../../moment'),
+     flags = function () {
+         return moment.apply(null, arguments).parsingFlags();
+     };
+ exports.parsing_flags = {
+     'overflow with array' : function (test) {
+         //months
+         test.equal(flags([2010, 0]).overflow, -1, 'month 0 valid');
+         test.equal(flags([2010, 1]).overflow, -1, 'month 1 valid');
+         test.equal(flags([2010, -1]).overflow, 1, 'month -1 invalid');
+         test.equal(flags([2100, 12]).overflow, 1, 'month 12 invalid');
+         //days
+         test.equal(flags([2010, 1, 16]).overflow, -1, 'date valid');
+         test.equal(flags([2010, 1, -1]).overflow, 2, 'date -1 invalid');
+         test.equal(flags([2010, 1, 0]).overflow, 2, 'date 0 invalid');
+         test.equal(flags([2010, 1, 32]).overflow, 2, 'date 32 invalid');
+         test.equal(flags([2012, 1, 29]).overflow, -1, 'date leap year valid');
+         test.equal(flags([2010, 1, 29]).overflow, 2, 'date leap year invalid');
+         //hours
+         test.equal(flags([2010, 1, 1, 8]).overflow, -1, 'hour valid');
+         test.equal(flags([2010, 1, 1, 0]).overflow, -1, 'hour 0 valid');
+         test.equal(flags([2010, 1, 1, -1]).overflow, 3, 'hour -1 invalid');
+         test.equal(flags([2010, 1, 1, 24]).overflow, 3, 'hour 24 invalid');
+         //minutes
+         test.equal(flags([2010, 1, 1, 8, 15]).overflow, -1, 'minute valid');
+         test.equal(flags([2010, 1, 1, 8, 0]).overflow, -1, 'minute 0 valid');
+         test.equal(flags([2010, 1, 1, 8, -1]).overflow, 4, 'minute -1 invalid');
+         test.equal(flags([2010, 1, 1, 8, 60]).overflow, 4, 'minute 60 invalid');
+         //seconds
+         test.equal(flags([2010, 1, 1, 8, 15, 12]).overflow, -1, 'second valid');
+         test.equal(flags([2010, 1, 1, 8, 15, 0]).overflow, -1, 'second 0 valid');
+         test.equal(flags([2010, 1, 1, 8, 15, -1]).overflow, 5, 'second -1 invalid');
+         test.equal(flags([2010, 1, 1, 8, 15, 60]).overflow, 5, 'second 60 invalid');
+         //milliseconds
+         test.equal(flags([2010, 1, 1, 8, 15, 12, 345]).overflow, -1, 'millisecond valid');
+         test.equal(flags([2010, 1, 1, 8, 15, 12, 0]).overflow, -1, 'millisecond 0 valid');
+         test.equal(flags([2010, 1, 1, 8, 15, 12, -1]).overflow, 6, 'millisecond -1 invalid');
+         test.equal(flags([2010, 1, 1, 8, 15, 12, 1000]).overflow, 6, 'millisecond 1000 invalid');
+         test.done();
+     },
+     'overflow without format' : function (test) {
+         //months
+         test.equal(flags('2001-01', 'YYYY-MM').overflow, -1, 'month 1 valid');
+         test.equal(flags('2001-12', 'YYYY-MM').overflow, -1, 'month 12 valid');
+         test.equal(flags('2001-13', 'YYYY-MM').overflow, 1, 'month 13 invalid');
+         //days
+         test.equal(flags('2010-01-16', 'YYYY-MM-DD').overflow, -1, 'date 16 valid');
+         test.equal(flags('2010-01-0',  'YYYY-MM-DD').overflow, 2, 'date 0 invalid');
+         test.equal(flags('2010-01-32', 'YYYY-MM-DD').overflow, 2, 'date 32 invalid');
+         test.equal(flags('2012-02-29', 'YYYY-MM-DD').overflow, -1, 'date leap year valid');
+         test.equal(flags('2010-02-29', 'YYYY-MM-DD').overflow, 2, 'date leap year invalid');
+         //days of the year
+         test.equal(flags('2010 300', 'YYYY DDDD').overflow, -1, 'day 300 of year valid');
+         test.equal(flags('2010 365', 'YYYY DDDD').overflow, -1, 'day 365 of year valid');
+         test.equal(flags('2010 366', 'YYYY DDDD').overflow, 2, 'day 366 of year invalid');
++        test.equal(flags('2012 366', 'YYYY DDDD').overflow, -1, 'day 366 of leap year valid');
++        test.equal(flags('2012 367', 'YYYY DDDD').overflow, 2, 'day 367 of leap year invalid');
+         //hours
+         test.equal(flags('08', 'HH').overflow, -1, 'hour valid');
+         test.equal(flags('00', 'HH').overflow, -1, 'hour 0 valid');
+         test.equal(flags('24', 'HH').overflow, 3, 'hour 24 invalid');
+         //minutes
+         test.equal(flags('08:15', 'HH:mm').overflow, -1, 'minute valid');
+         test.equal(flags('08:00', 'HH:mm').overflow, -1, 'minute 0 valid');
+         test.equal(flags('08:60', 'HH:mm').overflow, 4, 'minute 60 invalid');
+         //seconds
+         test.equal(flags('08:15:12', 'HH:mm:ss').overflow, -1, 'second valid');
+         test.equal(flags('08:15:00', 'HH:mm:ss').overflow, -1, 'second 0 valid');
+         test.equal(flags('08:15:60', 'HH:mm:ss').overflow, 5, 'second 60 invalid');
+         //milliseconds
+         test.equal(flags('08:15:12:345', 'HH:mm:ss:SSSS').overflow, -1, 'millisecond valid');
+         test.equal(flags('08:15:12:000', 'HH:mm:ss:SSSS').overflow, -1, 'millisecond 0 valid');
+         //this is OK because we don't match the last digit, so it's 100 ms
+         test.equal(flags('08:15:12:1000', 'HH:mm:ss:SSSS').overflow, -1, 'millisecond 1000 actually valid');
+         test.done();
+     },
+     'extra tokens' : function (test) {
+         test.deepEqual(flags('1982-05-25', 'YYYY-MM-DD').unusedTokens, [], 'nothing extra');
+         test.deepEqual(flags('1982-05', 'YYYY-MM-DD').unusedTokens, ['DD'], 'extra formatting token');
+         test.deepEqual(flags('1982', 'YYYY-MM-DD').unusedTokens, ['MM', 'DD'], 'multiple extra formatting tokens');
+         test.deepEqual(flags('1982-05', 'YYYY-MM-').unusedTokens, [], 'extra non-formatting token');
+         test.deepEqual(flags('1982-05-', 'YYYY-MM-DD').unusedTokens, ['DD'], 'non-extra non-formatting token');
+         test.deepEqual(flags('1982 05 1982', 'YYYY-MM-DD').unusedTokens, [], 'different non-formatting token');
+         test.done();
+     },
+     'extra tokens strict' : function (test) {
+         test.deepEqual(flags('1982-05-25', 'YYYY-MM-DD', true).unusedTokens, [], 'nothing extra');
+         test.deepEqual(flags('1982-05', 'YYYY-MM-DD', true).unusedTokens, ['-', 'DD'], 'extra formatting token');
+         test.deepEqual(flags('1982', 'YYYY-MM-DD', true).unusedTokens, ['-', 'MM', '-', 'DD'], 'multiple extra formatting tokens');
+         test.deepEqual(flags('1982-05', 'YYYY-MM-', true).unusedTokens, ['-'], 'extra non-formatting token');
+         test.deepEqual(flags('1982-05-', 'YYYY-MM-DD', true).unusedTokens, ['DD'], 'non-extra non-formatting token');
+         test.deepEqual(flags('1982 05 1982', 'YYYY-MM-DD', true).unusedTokens, ['-', '-'], 'different non-formatting token');
+         test.done();
+     },
+     'unused input' : function (test) {
+         test.deepEqual(flags('1982-05-25', 'YYYY-MM-DD').unusedInput, [], 'normal input');
+         test.deepEqual(flags('1982-05-25 this is more stuff', 'YYYY-MM-DD').unusedInput, [' this is more stuff'], 'trailing nonsense');
+         test.deepEqual(flags('1982-05-25 09:30', 'YYYY-MM-DD').unusedInput, [' 09:30'], ['trailing legit-looking input']);
+         test.deepEqual(flags('1982-05-25 some junk', 'YYYY-MM-DD [some junk]').unusedInput, [], 'junk that actually gets matched');
+         test.deepEqual(flags('stuff at beginning 1982-05-25', 'YYYY-MM-DD').unusedInput, ['stuff at beginning '], 'leading junk');
+         test.deepEqual(flags('junk 1982 more junk 05 yet more junk25', 'YYYY-MM-DD').unusedInput, ['junk ', ' more junk ', ' yet more junk'], 'interstitial junk');
+         test.done();
+     },
+     'unused input strict' : function (test) {
+         test.deepEqual(flags('1982-05-25', 'YYYY-MM-DD', true).unusedInput, [], 'normal input');
+         test.deepEqual(flags('1982-05-25 this is more stuff', 'YYYY-MM-DD', true).unusedInput, [' this is more stuff'], 'trailing nonsense');
+         test.deepEqual(flags('1982-05-25 09:30', 'YYYY-MM-DD', true).unusedInput, [' 09:30'], ['trailing legit-looking input']);
+         test.deepEqual(flags('1982-05-25 some junk', 'YYYY-MM-DD [some junk]', true).unusedInput, [], 'junk that actually gets matched');
+         test.deepEqual(flags('stuff at beginning 1982-05-25', 'YYYY-MM-DD', true).unusedInput, ['stuff at beginning '], 'leading junk');
+         test.deepEqual(flags('junk 1982 more junk 05 yet more junk25', 'YYYY-MM-DD', true).unusedInput, ['junk ', ' more junk ', ' yet more junk'], 'interstitial junk');
+         test.done();
+     },
+     'chars left over' : function (test) {
+         test.equal(flags('1982-05-25', 'YYYY-MM-DD').charsLeftOver, 0, 'normal input');
+         test.equal(flags('1982-05-25 this is more stuff', 'YYYY-MM-DD').charsLeftOver, ' this is more stuff'.length, 'trailing nonsense');
+         test.equal(flags('1982-05-25 09:30', 'YYYY-MM-DD').charsLeftOver, ' 09:30'.length, 'trailing legit-looking input');
+         test.equal(flags('stuff at beginning 1982-05-25', 'YYYY-MM-DD').charsLeftOver, 'stuff at beginning '.length, 'leading junk');
+         test.equal(flags('1982 junk 05 more junk25', 'YYYY-MM-DD').charsLeftOver, [' junk ', ' more junk'].join('').length, 'interstitial junk');
+         test.equal(flags('stuff at beginning 1982 junk 05 more junk25', 'YYYY-MM-DD').charsLeftOver, ['stuff at beginning ', ' junk ', ' more junk'].join('').length, 'leading and interstitial junk');
+         test.done();
+     },
+     'empty' : function (test) {
+         test.equal(flags('1982-05-25', 'YYYY-MM-DD').empty, false, 'normal input');
+         test.equal(flags('nothing here', 'YYYY-MM-DD').empty, true, 'pure garbage');
+         test.equal(flags('junk but has the number 2000 in it', 'YYYY-MM-DD').empty, false, 'only mostly garbage');
+         test.equal(flags('', 'YYYY-MM-DD').empty, true, 'empty string');
+         test.equal(flags('', 'YYYY-MM-DD').empty, true, 'blank string');
+         test.done();
+     },
+     'null' : function (test) {
+         test.equal(flags('1982-05-25', 'YYYY-MM-DD').nullInput, false, 'normal input');
+         test.equal(flags(null).nullInput, true, 'just null');
+         test.equal(flags(null, 'YYYY-MM-DD').nullInput, true, 'null with format');
+         test.done();
+     },
+     'invalid month' : function (test) {
+         test.equal(flags('1982 May', 'YYYY MMMM').invalidMonth, null, 'normal input');
+         test.equal(flags('1982 Laser', 'YYYY MMMM').invalidMonth, 'Laser', 'bad month name');
+         test.done();
+     }
+ };