moment/moment — pull request #1140 by icambron (2013-09-27 21:58:18+00:00)
Title: Feature/invalid moments

Apologies for the number of commits here; it was hard to squash them because I merged in `develop` instead of rebasing (which I had to do because part of the branch was already merged. Here's a summary of the changes:

# Parsing flags

We generate a set of parsing flags when parsing with a format. They are:

```js
{
empty: false, //did we find anything to parse at all
unusedTokens: ['MM', 'DD'], //tokens we didn't use in the parse
unusedInput: ['stuff', 'stuff'], //other input not parsed
charsLeftOver: 15, //formally ._il, the total number of unused input characters. Sum of the strlens for unusedInput
overflowMonthOk: false, //whether or not we consider an overflowed date OK, as in "DDD" parsing
overflow: 3, //array index of where overflow occurred (i.e. invalidAt())
nullInput: false, //was the input null?
invalidMonth: false, //month name that didn't exist, moment('splurk', 'MMMM')
userInvalidated: false //user used moment.invalid() without specifying why
}
```

Access with `moment().parsingFlags()`. Note that the flags do **not** include `dstInvalid`.

# Redefined isValid

`isValid()` is now a function of three things:

* Is `date.valueOf()` just `NaN`?
* What parsing flags are set?
* Are we parsing as strict?

Which parsing flags matter is determined by `strict`. This makes it a lot easier to understand and diagnose validity. There are some behavioral changes. In general, `isValid()` is more lenient. Some examples:

`moment('xx-xx-2001', 'DD-MM-YY')` was invalid and now isn't. That's because it parses as Jan 20. I think this is correct. Use strict if you don't want that.

`moment('123456790.12', 'X.SS') was invalid and now isn't.

Open questions:

* `isValid()` completely ignores whether the date parsed corresponds to a time that doesn't exist because of DST. This is a significant change breaking change. Should it check that?

# Improved format scoring

As of #1094, we do a much better job of picking a parsing format out of a list by checking how many unparsed characters there are. We can use the parsing flags to improve that:

1. Prefer valid moments to all invalid moments, as measured by `isValid()`
2. Prefer formats with fewer unused tokens
3. Prefer formats that result in fewer unparsed input characters

# Reworked strict parsing

Strict parsing doesn't need its own separate function to check things. We just have `strict` behave differently in these ways:

1. Prepend `^` to the token regexes. This works because we `slice()` the string progressively
2. Place non-format characters in the `unsedTokens` parsing flag
3. Make `isValid()` consider additional parsing flags

Open stuff:

* I need additional tests for that third item.

# isDSTShifted

We want to know if you asked for 1:30 AM on March 3 (assuming you're in the US). `isDSTShifted()` answers that. There are a few questions/issues there:

* I'm not sure how to write tests for it that work across time zones. We could have it skip the test if you're not in a US timezone with DST. Anyway, currently no tests.

* is `isDSTShifted` the right term for this? `isDSTInvalid()`? `isUnexpectedTime`? Don't know what to call it.

* Does it work reliably? It works by assuming that if the date is valid && the `overflowMonthOk` flag is off, that the only reason for `_a != moment(_a).toArray()` is that DST messed with the date. I'm not 100% sure that's true.

# format() for invalid moments

`format()` always returns "Invalid date" for `!m.isValid()`. The string can be localized.

Open issues:

* Is this what we want to do?
* Could use more tests in general
* Need to test localization

# Little stuff

`moment.invalid()` now takes an optional object of parsing flags. So if you're making a third-party plugin, you can generate moments with the appropriate flags, even made-up ones. If you don't specify flags, it sets the `userInvalidated` flag for you.

# Other internal changes

```js
YEAR = 0,
MONTH = 1,
DATE = 2,
HOUR = 3,
MINUTE = 4,
SECOND = 5,
MILLISECOND = 6,
```

I'll add those missing tests (specifically around using "Invalid date" in `format()` and strict parsing in `isValid()`), but then I think this is good to go.