Repository: dateutil/dateutil (Python)

Issue:
InvalidOperation: `[<class 'decimal.InvalidOperation'>]` while parsing `Nan`
Hi there!

We discovered a problem with the latest release (2.7.2):
```
import dateutil
dateutil.parser.parse('Nan')
```

Is raising `InvalidOperation` with a traceback:

<details>

```ipython
Traceback (most recent call last)
<ipython-input-8-aea1750fc455> in <module>()
----> 1 dateutil.parser.parse('Nan')

~/.virtualenvs/backend/lib/python3.6/site-packages/dateutil/parser/_parser.py in parse(timestr, parserinfo, **kwargs)
   1310         return parser(parserinfo).parse(timestr, **kwargs)
   1311     else:
-> 1312         return DEFAULTPARSER.parse(timestr, **kwargs)
   1313
   1314

~/.virtualenvs/backend/lib/python3.6/site-packages/dateutil/parser/_parser.py in parse(self, timestr, default, ignoretz, tzinfos, **kwargs)
    602                                                       second=0, microsecond=0)
    603
--> 604         res, skipped_tokens = self._parse(timestr, **kwargs)
    605
    606         if res is None:

~/.virtualenvs/backend/lib/python3.6/site-packages/dateutil/parser/_parser.py in _parse(self, timestr, dayfirst, yearfirst, fuzzy, fuzzy_with_tokens)
    699                 if value is not None:
    700                     # Numeric token
--> 701                     i = self._parse_numeric_token(l, i, info, ymd, res, fuzzy)
    702
    703                 # Check weekday

~/.virtualenvs/backend/lib/python3.6/site-packages/dateutil/parser/_parser.py in _parse_numeric_token(self, tokens, idx, info, ymd, res, fuzzy)
    948             else:
    949                 # Year, month or day
--> 950                 ymd.append(value)
    951             idx += 1
    952

~/.virtualenvs/backend/lib/python3.6/site-packages/dateutil/parser/_parser.py in append(self, val, label)
    428                     raise ValueError(label)
    429                 label = 'Y'
--> 430         elif val > 100:
    431             self.century_specified = True
    432             if label not in [None, 'Y']:  # pragma: no cover

InvalidOperation: [<class 'decimal.InvalidOperation'>]
```

</details>
<hr/>


My environment:
```
>>> import dateutil; print(dateutil.__version__)
2.7.2

13:16 $ python --version
Python 3.6.4
```

I am not sure how to properly tackle this problem yet. If you are having busy times with the other bugs, please, point me to where it would be better to handle this exception and I will provide a bugfix!

Thank you for the package!


Hints:
This is very similar to the test case in #491.  Most of that PR is unnecessary for fixing this, but you could extract the relevant function and test case pretty easily.
Hm.. this is similar to #636. I'll have to think about the best way to solve this. I suppose we could switch this over to just catching all `InvalidOperation` errors and re-raising them as `ValueError` at the end to prevent *all* exceptions like this.

I think that's a much broader change though. In this case I think the right path forward is to modify [`_to_decimal`](https://github.com/dateutil/dateutil/blob/5f2052faf7532424203c23f979f1f8f7b2653571/dateutil/parser/_parser.py#L1203) to raise `ValueError` if `value.is_finite()` is `False`. I think if you're getting a non-finite number in the `_to_decimal` path, you're already in an error condition.

Patch:
diff --git a/dateutil/parser/_parser.py b/dateutil/parser/_parser.py
--- a/dateutil/parser/_parser.py
+++ b/dateutil/parser/_parser.py
@@ -1202,10 +1202,15 @@ def _assign_tzname(self, dt, tzname):
 
     def _to_decimal(self, val):
         try:
-            return Decimal(val)
+            decimal_value = Decimal(val)
+            # See GH 662, edge case, infinite value should not be converted via `_to_decimal`
+            if not decimal_value.is_finite():
+                raise ValueError("Converted decimal value is infinite or NaN")
         except Exception as e:
             msg = "Could not convert %s to decimal" % val
             six.raise_from(ValueError(msg), e)
+        else:
+            return decimal_value
 
 
 DEFAULTPARSER = parser()


Test patch:
diff --git a/dateutil/test/test_parser.py b/dateutil/test/test_parser.py
--- a/dateutil/test/test_parser.py
+++ b/dateutil/test/test_parser.py
@@ -1093,9 +1093,9 @@ def test_rounding_floatlike_strings(dtstr, dt):
     assert parse(dtstr, default=datetime(2003, 9, 25)) == dt
 
 
-def test_decimal_error():
-    # GH 632 - decimal.Decimal raises some non-ValueError exception when
+@pytest.mark.parametrize('value', ['1: test', 'Nan'])
+def test_decimal_error(value):
+    # GH 632, GH 662 - decimal.Decimal raises some non-ValueError exception when
     # constructed with an invalid value
     with pytest.raises(ValueError):
-        parse('1: test')
-
+        parse(value)