Repository: pandas-dev/pandas

Issue:
BUG: pd.date_range gives wrong overflow error type when freq='B'
#### Code Sample, a copy-pastable example if possible

```python
>>> date = pd.Timestamp.max.floor("D").to_pydatetime().date()  # datetime.date(2262, 4, 11)
>>> freq = 'B'
>>> pd.date_range(date, periods=1, freq=freq)
OverflowError: int too big to convert
>>> freq = 'D'
>>> pd.date_range(date, periods=1, freq=freq)
OutOfBoundsDatetime: Cannot generate range with start=9223286400000000000 and periods=1
```
#### Problem description

These two date_range overflows return different error types. Both should reasonablyreturn ``OutOfBoundsDatetime``.

<details>

INSTALLED VERSIONS
------------------
commit: 8221ee91d15b01a190d73ea10d47af81de296c90
python: 3.7.1.final.0
python-bits: 64
OS: Windows
OS-release: 10
machine: AMD64
processor: Intel64 Family 6 Model 78 Stepping 3, GenuineIntel
byteorder: little
LC_ALL: None
LANG: None
LOCALE: None.None

pandas: 0.24.0.dev0+1257.g8221ee91d
pytest: 4.0.0
pip: 18.1
setuptools: 40.6.2
Cython: 0.29
numpy: 1.15.4
scipy: None
pyarrow: 0.11.1
xarray: None
IPython: 7.1.1
sphinx: None
patsy: None
dateutil: 2.7.5
pytz: 2018.7
blosc: None
bottleneck: None
tables: None
numexpr: None
feather: 0.4.0
matplotlib: 3.0.1
openpyxl: None
xlrd: 1.1.0
xlwt: None
xlsxwriter: None
lxml.etree: None
bs4: None
html5lib: None
sqlalchemy: None
pymysql: None
psycopg2: None
jinja2: 2.10
s3fs: None
fastparquet: None
pandas_gbq: None
pandas_datareader: None
gcsfs: None

</details>



Discussion:
I suspect that generally `Tick` based offset will raise the later error while `DateOffset` based offsets will raise the former.

Additionally the `OutOfBoundsDatetime` looks like it can be improved; it's probably more beneficial to specify the date instead of the nanoseconds epoch time.
Hey, @mroeschke, did your PR resolve this issue?

I'm in doubt, as the second example now raises a OverflowError, while I assumed the correct solution would be for both to raise a OutOfBoundsDatetime.
I don't think @jbrockmendel solved this issue. On master the first example still raises an `OverflowError` while the 2nd example actually returns something on master

```
In [5]: >>> freq = 'D'
   ...: >>> pd.date_range(date, periods=1, freq=freq)
Out[5]: DatetimeIndex(['2262-04-11'], dtype='datetime64[ns]', freq='D')
```

Resolving patch:
diff --git a/pandas/tseries/offsets.py b/pandas/tseries/offsets.py
--- a/pandas/tseries/offsets.py
+++ b/pandas/tseries/offsets.py
@@ -97,6 +97,8 @@ def wrapper(self, other):
             if tz is not None and result.tzinfo is None:
                 result = conversion.localize_pydatetime(result, tz)
 
+            result = Timestamp(result)
+
         return result
     return wrapper
 
@@ -2330,7 +2332,7 @@ def apply(self, other):
             # an exception, when we call using the + operator,
             # we directly call the known method
             result = other.__add__(self)
-            if result == NotImplemented:
+            if result is NotImplemented:
                 raise OverflowError
             return result
         elif isinstance(other, (datetime, np.datetime64, date)):
@@ -2467,6 +2469,11 @@ def generate_range(start=None, end=None, periods=None, offset=BDay()):
         while cur <= end:
             yield cur
 
+            if cur == end:
+                # GH#24252 avoid overflows by not performing the addition
+                # in offset.apply unless we have to
+                break
+
             # faster than cur + offset
             next_date = offset.apply(cur)
             if next_date <= cur:
@@ -2477,6 +2484,11 @@ def generate_range(start=None, end=None, periods=None, offset=BDay()):
         while cur >= end:
             yield cur
 
+            if cur == end:
+                # GH#24252 avoid overflows by not performing the addition
+                # in offset.apply unless we have to
+                break
+
             # faster than cur + offset
             next_date = offset.apply(cur)
             if next_date >= cur:


Test patch:
diff --git a/pandas/tests/arithmetic/test_timedelta64.py b/pandas/tests/arithmetic/test_timedelta64.py
--- a/pandas/tests/arithmetic/test_timedelta64.py
+++ b/pandas/tests/arithmetic/test_timedelta64.py
@@ -5,7 +5,8 @@
 import numpy as np
 import pytest
 
-from pandas.errors import NullFrequencyError, PerformanceWarning
+from pandas.errors import (
+    NullFrequencyError, OutOfBoundsDatetime, PerformanceWarning)
 
 import pandas as pd
 from pandas import (
@@ -479,10 +480,10 @@ def test_tdi_add_timestamp_nat_masking(self):
 
     def test_tdi_add_overflow(self):
         # See GH#14068
-        msg = "too (big|large) to convert"
-        with pytest.raises(OverflowError, match=msg):
+        # preliminary test scalar analogue of vectorized tests below
+        with pytest.raises(OutOfBoundsDatetime):
             pd.to_timedelta(106580, 'D') + Timestamp('2000')
-        with pytest.raises(OverflowError, match=msg):
+        with pytest.raises(OutOfBoundsDatetime):
             Timestamp('2000') + pd.to_timedelta(106580, 'D')
 
         _NaT = int(pd.NaT) + 1
diff --git a/pandas/tests/frame/test_constructors.py b/pandas/tests/frame/test_constructors.py
--- a/pandas/tests/frame/test_constructors.py
+++ b/pandas/tests/frame/test_constructors.py
@@ -5,6 +5,7 @@
 
 import numpy as np
 import numpy.ma as ma
+import numpy.ma.mrecords as mrecords
 import pytest
 
 from pandas.compat import PY36, is_platform_little_endian
@@ -839,7 +840,7 @@ def test_constructor_maskedrecarray_dtype(self):
         data = np.ma.array(
             np.ma.zeros(5, dtype=[('date', '<f8'), ('price', '<f8')]),
             mask=[False] * 5)
-        data = data.view(ma.mrecords.mrecarray)
+        data = data.view(mrecords.mrecarray)
         result = pd.DataFrame(data, dtype=int)
         expected = pd.DataFrame(np.zeros((5, 2), dtype=int),
                                 columns=['date', 'price'])
@@ -868,7 +869,7 @@ def test_constructor_mrecarray(self):
         # call assert_frame_equal for all selections of 3 arrays
         for comb in itertools.combinations(arrays, 3):
             names, data = zip(*comb)
-            mrecs = ma.mrecords.fromarrays(data, names=names)
+            mrecs = mrecords.fromarrays(data, names=names)
 
             # fill the comb
             comb = {k: (v.filled() if hasattr(v, 'filled') else v)
diff --git a/pandas/tests/indexes/datetimes/test_date_range.py b/pandas/tests/indexes/datetimes/test_date_range.py
--- a/pandas/tests/indexes/datetimes/test_date_range.py
+++ b/pandas/tests/indexes/datetimes/test_date_range.py
@@ -740,6 +740,19 @@ def test_bdays_and_open_boundaries(self, closed):
         expected = pd.date_range(bday_start, bday_end, freq='D')
         tm.assert_index_equal(result, expected)
 
+    def test_bday_near_overflow(self):
+        # GH#24252 avoid doing unnecessary addition that _would_ overflow
+        start = pd.Timestamp.max.floor("D").to_pydatetime()
+        rng = pd.date_range(start, end=None, periods=1, freq='B')
+        expected = pd.DatetimeIndex([start], freq='B')
+        tm.assert_index_equal(rng, expected)
+
+    def test_bday_overflow_error(self):
+        # GH#24252 check that we get OutOfBoundsDatetime and not OverflowError
+        start = pd.Timestamp.max.floor("D").to_pydatetime()
+        with pytest.raises(OutOfBoundsDatetime):
+            pd.date_range(start, periods=2, freq='B')
+
 
 class TestCustomDateRange:
 
diff --git a/pandas/tests/scalar/timestamp/test_timestamp.py b/pandas/tests/scalar/timestamp/test_timestamp.py
--- a/pandas/tests/scalar/timestamp/test_timestamp.py
+++ b/pandas/tests/scalar/timestamp/test_timestamp.py
@@ -463,6 +463,13 @@ def test_invalid_date_kwarg_with_string_input(self, arg):
         with pytest.raises(ValueError):
             Timestamp('2010-10-10 12:59:59.999999999', **kwarg)
 
+    def test_out_of_bounds_integer_value(self):
+        # GH#26651 check that we raise OutOfBoundsDatetime, not OverflowError
+        with pytest.raises(OutOfBoundsDatetime):
+            Timestamp(Timestamp.max.value * 2)
+        with pytest.raises(OutOfBoundsDatetime):
+            Timestamp(Timestamp.min.value * 2)
+
     def test_out_of_bounds_value(self):
         one_us = np.timedelta64(1).astype('timedelta64[us]')
 
diff --git a/pandas/tests/tseries/offsets/test_offsets.py b/pandas/tests/tseries/offsets/test_offsets.py
--- a/pandas/tests/tseries/offsets/test_offsets.py
+++ b/pandas/tests/tseries/offsets/test_offsets.py
@@ -115,7 +115,7 @@ def test_apply_out_of_range(self, tz_naive_fixture):
             assert t.tzinfo == result.tzinfo
 
         except OutOfBoundsDatetime:
-            raise
+            pass
         except (ValueError, KeyError):
             # we are creating an invalid offset
             # so ignore