Repository: pandas-dev/pandas

Issue:
ENH: Convert datetimelike index to strings with astype(str) 
xref #10110 
- [x] Fix `.astype(str)` on `DatetimeIndex` to use call `.format()` (Index method)

~~\- [ ] Add `Series.dt.format()` accessor to call `.format()`~~ (#10110 does this with `.strftime`)
- [x] Change `Series.astype(str)` to call equiv of `Series.dt.format()` for `datetime64[ns]` dtype

Recently the ability to convert a datetimelike series to strings with `astype(str)` was introduced (#9758 by @evanpw):

```
In [27]: pd.Series(pd.date_range('2012-01-01', periods=3))
Out[27]:
0   2012-01-01
1   2012-01-02
2   2012-01-03
dtype: datetime64[ns]

In [28]: pd.Series(pd.date_range('2012-01-01', periods=3)).astype(str)
Out[28]:
0    2012-01-01T01:00:00.000000000+0100
1    2012-01-02T01:00:00.000000000+0100
2    2012-01-03T01:00:00.000000000+0100
dtype: object
```

it would be nice if this also possible on a index (now you still get the same error as previously for a series):

```
In [29]: pd.date_range('2012-01-01', periods=3).astype(str)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-29-68a28f79c8e9> in <module>()
----> 1 pd.date_range('2012-01-01', periods=3).astype(str)

C:\Users\vdbosscj\Scipy\pandas-joris\pandas\tseries\index.py in astype(self, dty
pe)
    692             return self.asi8.copy()
    693         else:  # pragma: no cover
--> 694             raise ValueError('Cannot cast DatetimeIndex to dtype %s' % d
type)
    695
    696     def _get_time_micros(self):

ValueError: Cannot cast DatetimeIndex to dtype |S0
```



Discussion:
sure, this is pretty easy.

this is competes a bit with `.format()` (which takes a formatting argument and is 'internal'; and the `.strptime`, which I think is still outstanding). But ok by me

yes, for more control over the conversion the `.format` is certainly the preferred way to do, but for consistency with series this would be good.

Something else I was thinking: we are now using the numpy machinery I suppose to do the conversion, with the result that this also uses the odd numpy representation in local time:

```
`In [46]: s.astype(str)[0]
Out[46]: '2012-01-01T01:00:00.000000000+0100'

In [47]: str(s[0])
Out[47]: '2012-01-01 00:00:00'
```

Would it be possible to also have the second output for `astype`?

hmm, that's a problem, these should all go thru format (with no format specified) for consistency. Further I really though that we added `.strptime`, but if we didn't ok. should also expose this as `Series.dt.format` then. I'll add a list to the top. All these pretty straightforward.


Resolving patch:
diff --git a/pandas/tseries/index.py b/pandas/tseries/index.py
--- a/pandas/tseries/index.py
+++ b/pandas/tseries/index.py
@@ -756,6 +756,8 @@ def astype(self, dtype):
             return self.asi8.copy()
         elif dtype == _NS_DTYPE and self.tz is not None:
             return self.tz_convert('UTC').tz_localize(None)
+        elif dtype == str:
+            return self._shallow_copy(values=self.format(), infer=True)
         else:  # pragma: no cover
             raise ValueError('Cannot cast DatetimeIndex to dtype %s' % dtype)
 


Test patch:
diff --git a/pandas/tseries/tests/test_base.py b/pandas/tseries/tests/test_base.py
--- a/pandas/tseries/tests/test_base.py
+++ b/pandas/tseries/tests/test_base.py
@@ -45,6 +45,32 @@ def test_ops_properties_basic(self):
         self.assertEqual(s.day,10)
         self.assertRaises(AttributeError, lambda : s.weekday)
 
+    def test_astype_str(self):
+        # test astype string - #10442
+        result = date_range('2012-01-01', periods=4, name='test_name').astype(str)
+        expected = Index(['2012-01-01', '2012-01-02', '2012-01-03','2012-01-04'],
+                            name='test_name', dtype=object)
+        tm.assert_index_equal(result, expected)
+
+        # test astype string with tz and name
+        result = date_range('2012-01-01', periods=3, name='test_name', tz='US/Eastern').astype(str)
+        expected = Index(['2012-01-01 00:00:00-05:00', '2012-01-02 00:00:00-05:00',
+                          '2012-01-03 00:00:00-05:00'], name='test_name', dtype=object)
+        tm.assert_index_equal(result, expected)
+
+        # test astype string with freqH and name
+        result = date_range('1/1/2011', periods=3, freq='H', name='test_name').astype(str)
+        expected = Index(['2011-01-01 00:00:00', '2011-01-01 01:00:00', '2011-01-01 02:00:00'],
+                         name='test_name', dtype=object)
+        tm.assert_index_equal(result, expected)
+
+        # test astype string with freqH and timezone
+        result = date_range('3/6/2012 00:00', periods=2, freq='H',
+                            tz='Europe/London', name='test_name').astype(str)
+        expected = Index(['2012-03-06 00:00:00+00:00', '2012-03-06 01:00:00+00:00'],
+                         dtype=object, name='test_name')
+        tm.assert_index_equal(result, expected)
+
     def test_asobject_tolist(self):
         idx = pd.date_range(start='2013-01-01', periods=4, freq='M', name='idx')
         expected_list = [pd.Timestamp('2013-01-31'), pd.Timestamp('2013-02-28'),
@@ -503,7 +529,6 @@ def test_infer_freq(self):
             tm.assert_index_equal(idx, result)
             self.assertEqual(result.freq, freq)
 
-
 class TestTimedeltaIndexOps(Ops):
 
     def setUp(self):
diff --git a/pandas/tseries/tests/test_timeseries.py b/pandas/tseries/tests/test_timeseries.py
--- a/pandas/tseries/tests/test_timeseries.py
+++ b/pandas/tseries/tests/test_timeseries.py
@@ -2223,6 +2223,7 @@ def test_append_join_nondatetimeindex(self):
         # it works
         rng.join(idx, how='outer')
 
+
     def test_astype(self):
         rng = date_range('1/1/2000', periods=10)
 
@@ -2235,6 +2236,17 @@ def test_astype(self):
         expected = date_range('1/1/2000', periods=10, tz='US/Eastern').tz_convert('UTC').tz_localize(None)
         tm.assert_index_equal(result, expected)
 
+        # BUG#10442 : testing astype(str) is correct for Series/DatetimeIndex
+        result = pd.Series(pd.date_range('2012-01-01', periods=3)).astype(str)
+        expected = pd.Series(['2012-01-01', '2012-01-02', '2012-01-03'], dtype=object)
+        tm.assert_series_equal(result, expected)
+
+        result = Series(pd.date_range('2012-01-01', periods=3, tz='US/Eastern')).astype(str)
+        expected = Series(['2012-01-01 00:00:00-05:00', '2012-01-02 00:00:00-05:00', '2012-01-03 00:00:00-05:00'],
+                          dtype=object)
+        tm.assert_series_equal(result, expected)
+
+
     def test_to_period_nofreq(self):
         idx = DatetimeIndex(['2000-01-01', '2000-01-02', '2000-01-04'])
         self.assertRaises(ValueError, idx.to_period)