Repository: pandas-dev/pandas

Issue:
Period converted to overflowing Timestamp in DataFrame.to_csv
#### Code Sample

```python
import pandas
dates = ["1990-01-01", "2000-01-01", "3005-01-01"]
index = pandas.PeriodIndex(dates, freq="D")
df = pandas.DataFrame([4, 5, 6], index=index)
print(df)
df.to_csv("bug.csv")
```
#### Problem description

I have some data with dates in the far future (e.g. 4000-01-01). Following the documentation I am using `pandas.Period`: http://pandas.pydata.org/pandas-docs/stable/timeseries.html#representing-out-of-bounds-spans

This works, until I come to export the dataframe to a CSV (demonstrated in the code above). Printing the dataframe works as expected:

>             0
> 1990-01-01  4
> 2000-01-01  5
> 3005-01-01  6

But the CSV files looks like this:

> ,0
> 1990-01-01 00:00:00.000000000,4
> 2000-01-01 00:00:00.000000000,5
> 1835-11-23 00:50:52.580896768,6

It looks like the `Period` has been converted to a `Timestamp` and silently overflowed.

Related to #13346, but I think worth a distinct issue.

#### Output of ``pd.show_versions()``

<details>
INSTALLED VERSIONS
------------------
commit: None
python: 3.6.0.final.0
python-bits: 64
OS: Windows
OS-release: 7
machine: AMD64
processor: Intel64 Family 6 Model 58 Stepping 9, GenuineIntel
byteorder: little
LC_ALL: None
LANG: None
LOCALE: None.None

pandas: 0.19.2
nose: None
pip: 9.0.1
setuptools: 33.1.0.post20170122
Cython: 0.25.2
numpy: 1.11.3
scipy: 0.18.1
statsmodels: 0.8.0
xarray: None
IPython: 5.3.0
sphinx: None
patsy: 0.4.1
dateutil: 2.6.0
pytz: 2016.10
blosc: None
bottleneck: None
tables: 3.3.0
numexpr: 2.6.2
matplotlib: 2.0.0
openpyxl: 2.4.1
xlrd: 1.0.0
xlwt: None
xlsxwriter: None
lxml: None
bs4: None
html5lib: 0.999
httplib2: None
apiclient: None
sqlalchemy: None
pymysql: None
psycopg2: None
jinja2: 2.9.5
boto: 2.46.1
pandas_datareader: None
</details>



Discussion:
This is only an issue when the period is in the index:

```python
In [5]: df.reset_index().to_csv("bug.csv", index=False)

In [6]: !cat bug.csv
index,0
1990-01-01,4
2000-01-01,5
3005-01-01,6
```

cc @gfyoung if you're interested in taking a look.
This is what is called ultimately. Should be straightforward to fix.

```
In [6]: index
Out[6]: PeriodIndex(['1990-01-01', '2000-01-01', '3005-01-01'], dtype='period[D]', freq='D')

In [7]: index.to_native_types()
Out[7]: 
array(['1990-01-01', '2000-01-01', '3005-01-01'], 
      dtype='<U10')
```
@jreback : Right, but WAY before that, we call `index.to_timestamp()` when we initialize `CSVFormatter`.  That ultimately is why the bug (i.e. overflow) occurs.

Resolving patch:
diff --git a/pandas/formats/format.py b/pandas/formats/format.py
--- a/pandas/formats/format.py
+++ b/pandas/formats/format.py
@@ -1564,10 +1564,7 @@ def __init__(self, obj, path_or_buf=None, sep=",", na_rep='',
         self.chunksize = int(chunksize)
 
         self.data_index = obj.index
-        if isinstance(obj.index, PeriodIndex):
-            self.data_index = obj.index.to_timestamp()
-
-        if (isinstance(self.data_index, DatetimeIndex) and
+        if (isinstance(self.data_index, (DatetimeIndex, PeriodIndex)) and
                 date_format is not None):
             self.data_index = Index([x.strftime(date_format) if notnull(x) else
                                      '' for x in self.data_index])
diff --git a/pandas/indexes/base.py b/pandas/indexes/base.py
--- a/pandas/indexes/base.py
+++ b/pandas/indexes/base.py
@@ -1820,7 +1820,26 @@ def _format_with_header(self, header, na_rep='NaN', **kwargs):
         return header + result
 
     def to_native_types(self, slicer=None, **kwargs):
-        """ slice and dice then format """
+        """
+        Format specified values of `self` and return them.
+
+        Parameters
+        ----------
+        slicer : int, array-like
+            An indexer into `self` that specifies which values
+            are used in the formatting process.
+        kwargs : dict
+            Options for specifying how the values should be formatted.
+            These options include the following:
+
+            1) na_rep : str
+                The value that serves as a placeholder for NULL values
+            2) quoting : bool or None
+                Whether or not there are quoted values in `self`
+            3) date_format : str
+                The format used to represent date-like values
+        """
+
         values = self
         if slicer is not None:
             values = values[slicer]


Test patch:
diff --git a/pandas/tests/frame/test_to_csv.py b/pandas/tests/frame/test_to_csv.py
--- a/pandas/tests/frame/test_to_csv.py
+++ b/pandas/tests/frame/test_to_csv.py
@@ -1143,3 +1143,31 @@ def test_to_csv_quoting(self):
         df = df.set_index(['a', 'b'])
         expected = '"a","b","c"\n"1","3","5"\n"2","4","6"\n'
         self.assertEqual(df.to_csv(quoting=csv.QUOTE_ALL), expected)
+
+    def test_period_index_date_overflow(self):
+        # see gh-15982
+
+        dates = ["1990-01-01", "2000-01-01", "3005-01-01"]
+        index = pd.PeriodIndex(dates, freq="D")
+
+        df = pd.DataFrame([4, 5, 6], index=index)
+        result = df.to_csv()
+
+        expected = ',0\n1990-01-01,4\n2000-01-01,5\n3005-01-01,6\n'
+        assert result == expected
+
+        date_format = "%m-%d-%Y"
+        result = df.to_csv(date_format=date_format)
+
+        expected = ',0\n01-01-1990,4\n01-01-2000,5\n01-01-3005,6\n'
+        assert result == expected
+
+        # Overflow with pd.NaT
+        dates = ["1990-01-01", pd.NaT, "3005-01-01"]
+        index = pd.PeriodIndex(dates, freq="D")
+
+        df = pd.DataFrame([4, 5, 6], index=index)
+        result = df.to_csv()
+
+        expected = ',0\n1990-01-01,4\n,5\n3005-01-01,6\n'
+        assert result == expected
diff --git a/pandas/tests/indexes/datetimes/test_formats.py b/pandas/tests/indexes/datetimes/test_formats.py
new file mode 100644
--- /dev/null
+++ b/pandas/tests/indexes/datetimes/test_formats.py
@@ -0,0 +1,47 @@
+from pandas import DatetimeIndex
+
+import numpy as np
+
+import pandas.util.testing as tm
+import pandas as pd
+
+
+def test_to_native_types():
+    index = DatetimeIndex(freq='1D', periods=3, start='2017-01-01')
+
+    # First, with no arguments.
+    expected = np.array(['2017-01-01', '2017-01-02',
+                         '2017-01-03'], dtype=object)
+
+    result = index.to_native_types()
+    tm.assert_numpy_array_equal(result, expected)
+
+    # No NaN values, so na_rep has no effect
+    result = index.to_native_types(na_rep='pandas')
+    tm.assert_numpy_array_equal(result, expected)
+
+    # Make sure slicing works
+    expected = np.array(['2017-01-01', '2017-01-03'], dtype=object)
+
+    result = index.to_native_types([0, 2])
+    tm.assert_numpy_array_equal(result, expected)
+
+    # Make sure date formatting works
+    expected = np.array(['01-2017-01', '01-2017-02',
+                         '01-2017-03'], dtype=object)
+
+    result = index.to_native_types(date_format='%m-%Y-%d')
+    tm.assert_numpy_array_equal(result, expected)
+
+    # NULL object handling should work
+    index = DatetimeIndex(['2017-01-01', pd.NaT, '2017-01-03'])
+    expected = np.array(['2017-01-01', 'NaT', '2017-01-03'], dtype=object)
+
+    result = index.to_native_types()
+    tm.assert_numpy_array_equal(result, expected)
+
+    expected = np.array(['2017-01-01', 'pandas',
+                         '2017-01-03'], dtype=object)
+
+    result = index.to_native_types(na_rep='pandas')
+    tm.assert_numpy_array_equal(result, expected)
diff --git a/pandas/tests/indexes/period/test_formats.py b/pandas/tests/indexes/period/test_formats.py
new file mode 100644
--- /dev/null
+++ b/pandas/tests/indexes/period/test_formats.py
@@ -0,0 +1,48 @@
+from pandas import PeriodIndex
+
+import numpy as np
+
+import pandas.util.testing as tm
+import pandas as pd
+
+
+def test_to_native_types():
+    index = PeriodIndex(['2017-01-01', '2017-01-02',
+                         '2017-01-03'], freq='D')
+
+    # First, with no arguments.
+    expected = np.array(['2017-01-01', '2017-01-02',
+                         '2017-01-03'], dtype='<U10')
+
+    result = index.to_native_types()
+    tm.assert_numpy_array_equal(result, expected)
+
+    # No NaN values, so na_rep has no effect
+    result = index.to_native_types(na_rep='pandas')
+    tm.assert_numpy_array_equal(result, expected)
+
+    # Make sure slicing works
+    expected = np.array(['2017-01-01', '2017-01-03'], dtype='<U10')
+
+    result = index.to_native_types([0, 2])
+    tm.assert_numpy_array_equal(result, expected)
+
+    # Make sure date formatting works
+    expected = np.array(['01-2017-01', '01-2017-02',
+                         '01-2017-03'], dtype='<U10')
+
+    result = index.to_native_types(date_format='%m-%Y-%d')
+    tm.assert_numpy_array_equal(result, expected)
+
+    # NULL object handling should work
+    index = PeriodIndex(['2017-01-01', pd.NaT, '2017-01-03'], freq='D')
+    expected = np.array(['2017-01-01', 'NaT', '2017-01-03'], dtype=object)
+
+    result = index.to_native_types()
+    tm.assert_numpy_array_equal(result, expected)
+
+    expected = np.array(['2017-01-01', 'pandas',
+                         '2017-01-03'], dtype=object)
+
+    result = index.to_native_types(na_rep='pandas')
+    tm.assert_numpy_array_equal(result, expected)