Skip to content

Commit 3f26ee6

Browse files
committed
API/BUG: a timeseries/index localized to UTC when inserted into a Series/DataFrame will preserve the UTC timezone (rather than being a naive datetime64[ns]) as object dtype (GH8411)
1 parent 68fdacd commit 3f26ee6

File tree

6 files changed

+81
-29
lines changed

6 files changed

+81
-29
lines changed

doc/source/basics.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1130,6 +1130,12 @@ You can easily produces tz aware transformations:
11301130
stz
11311131
stz.dt.tz
11321132
1133+
You can also chain these types of operations:
1134+
1135+
.. ipython:: python
1136+
1137+
s.dt.tz_localize('UTC').dt.tz_convert('US/Eastern')
1138+
11331139
The ``.dt`` accessor works for period and timedelta dtypes.
11341140

11351141
.. ipython:: python

doc/source/v0.15.0.txt

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@ users upgrade to this version.
2626

2727
- :ref:`Other Enhancements <whatsnew_0150.enhancements>`
2828

29-
- :ref:`API Changes <whatsnew_0150.api>`, and :ref:`Rolling/Expanding Moments API Changes <whatsnew_0150.roll>`
29+
- :ref:`API Changes <whatsnew_0150.api>`
30+
31+
- :ref:`Timezone API Change <whatsnew_0150.tz>`
32+
33+
- :ref:`Rolling/Expanding Moments API Changes <whatsnew_0150.roll>`
3034

3135
- :ref:`Performance Improvements <whatsnew_0150.performance>`
3236

@@ -137,27 +141,6 @@ API changes
137141
In [3]: idx.isin(['a', 'c', 'e'], level=1)
138142
Out[3]: array([ True, False, True, True, False, True], dtype=bool)
139143

140-
- ``tz_localize(None)`` for tz-aware ``Timestamp`` and ``DatetimeIndex`` now removes timezone holding local time,
141-
previously results in ``Exception`` or ``TypeError`` (:issue:`7812`)
142-
143-
.. ipython:: python
144-
145-
ts = Timestamp('2014-08-01 09:00', tz='US/Eastern')
146-
ts
147-
ts.tz_localize(None)
148-
149-
didx = DatetimeIndex(start='2014-08-01 09:00', freq='H', periods=10, tz='US/Eastern')
150-
didx
151-
didx.tz_localize(None)
152-
153-
- ``tz_localize`` now accepts the ``ambiguous`` keyword which allows for passing an array of bools
154-
indicating whether the date belongs in DST or not, 'NaT' for setting transition times to NaT,
155-
'infer' for inferring DST/non-DST, and 'raise' (default) for an AmbiguousTimeError to be raised. See :ref:`the docs<timeseries.timezone_ambiguous>` for more details (:issue:`7943`)
156-
157-
- ``DataFrame.tz_localize`` and ``DataFrame.tz_convert`` now accepts an optional ``level`` argument
158-
for localizing a specific level of a MultiIndex (:issue:`7846`)
159-
- ``Timestamp.tz_localize`` and ``Timestamp.tz_convert`` now raise ``TypeError`` in error cases, rather than ``Exception`` (:issue:`8025`)
160-
- ``Timestamp.__repr__`` displays ``dateutil.tz.tzoffset`` info (:issue:`7907`)
161144
- ``merge``, ``DataFrame.merge``, and ``ordered_merge`` now return the same type
162145
as the ``left`` argument. (:issue:`7737`)
163146

@@ -305,6 +288,12 @@ You can easily produce tz aware transformations:
305288
stz
306289
stz.dt.tz
307290

291+
You can also chain these types of operations:
292+
293+
.. ipython:: python
294+
295+
s.dt.tz_localize('UTC').dt.tz_convert('US/Eastern')
296+
308297
The ``.dt`` accessor works for period and timedelta dtypes.
309298

310299
.. ipython:: python
@@ -324,6 +313,37 @@ The ``.dt`` accessor works for period and timedelta dtypes.
324313
s.dt.seconds
325314
s.dt.components
326315

316+
.. _whatsnew_0150.tz:
317+
318+
Timezone API changes
319+
~~~~~~~~~~~~~~~~~~~~
320+
321+
- ``tz_localize(None)`` for tz-aware ``Timestamp`` and ``DatetimeIndex`` now removes timezone holding local time,
322+
previously this resulted in ``Exception`` or ``TypeError`` (:issue:`7812`)
323+
324+
.. ipython:: python
325+
326+
ts = Timestamp('2014-08-01 09:00', tz='US/Eastern')
327+
ts
328+
ts.tz_localize(None)
329+
330+
didx = DatetimeIndex(start='2014-08-01 09:00', freq='H', periods=10, tz='US/Eastern')
331+
didx
332+
didx.tz_localize(None)
333+
334+
- ``tz_localize`` now accepts the ``ambiguous`` keyword which allows for passing an array of bools
335+
indicating whether the date belongs in DST or not, 'NaT' for setting transition times to NaT,
336+
'infer' for inferring DST/non-DST, and 'raise' (default) for an AmbiguousTimeError to be raised. See :ref:`the docs<timeseries.timezone_ambiguous>` for more details (:issue:`7943`)
337+
338+
- ``DataFrame.tz_localize`` and ``DataFrame.tz_convert`` now accepts an optional ``level`` argument
339+
for localizing a specific level of a MultiIndex (:issue:`7846`)
340+
341+
- ``Timestamp.tz_localize`` and ``Timestamp.tz_convert`` now raise ``TypeError`` in error cases, rather than ``Exception`` (:issue:`8025`)
342+
343+
- a timeseries/index localized to UTC when inserted into a Series/DataFrame will preserve the UTC timezone (rather than being a naive ``datetime64[ns]``) as ``object`` dtype (:issue:`8411`)
344+
345+
- ``Timestamp.__repr__`` displays ``dateutil.tz.tzoffset`` info (:issue:`7907`)
346+
327347
.. _whatsnew_0150.roll:
328348

329349
Rolling/Expanding Moments API changes

pandas/core/frame.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from pandas.core.common import (isnull, notnull, PandasError, _try_sort,
2727
_default_index, _maybe_upcast, _is_sequence,
2828
_infer_dtype_from_scalar, _values_from_object,
29-
is_list_like, _get_dtype)
29+
is_list_like, _get_dtype, _maybe_box_datetimelike)
3030
from pandas.core.generic import NDFrame, _shared_docs
3131
from pandas.core.index import Index, MultiIndex, _ensure_index
3232
from pandas.core.indexing import (_maybe_droplevels,
@@ -1539,7 +1539,7 @@ def get_value(self, index, col, takeable=False):
15391539

15401540
if takeable:
15411541
series = self._iget_item_cache(col)
1542-
return series.values[index]
1542+
return _maybe_box_datetimelike(series.values[index])
15431543

15441544
series = self._get_item_cache(col)
15451545
engine = self.index._engine

pandas/tests/test_frame.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3528,7 +3528,7 @@ def test_column_dups_indexing2(self):
35283528

35293529
# GH 8363
35303530
# datetime ops with a non-unique index
3531-
df = DataFrame({'A' : np.arange(5,dtype='int64'),
3531+
df = DataFrame({'A' : np.arange(5,dtype='int64'),
35323532
'B' : np.arange(1,6,dtype='int64')},
35333533
index=[2,2,3,3,4])
35343534
result = df.B-df.A
@@ -3654,6 +3654,18 @@ def test_constructor_with_datetimes(self):
36543654
self.assertEqual(df.iat[0,0],dt)
36553655
assert_series_equal(df.dtypes,Series({'End Date' : np.dtype('object') }))
36563656

3657+
# tz-aware (UTC and other tz's)
3658+
# GH 8411
3659+
dr = date_range('20130101',periods=3)
3660+
df = DataFrame({ 'value' : dr})
3661+
self.assertTrue(df.iat[0,0].tz is None)
3662+
dr = date_range('20130101',periods=3,tz='UTC')
3663+
df = DataFrame({ 'value' : dr})
3664+
self.assertTrue(str(df.iat[0,0].tz) == 'UTC')
3665+
dr = date_range('20130101',periods=3,tz='US/Eastern')
3666+
df = DataFrame({ 'value' : dr})
3667+
self.assertTrue(str(df.iat[0,0].tz) == 'US/Eastern')
3668+
36573669
# GH 7822
36583670
# preserver an index with a tz on dict construction
36593671
i = date_range('1/1/2011', periods=5, freq='10s', tz = 'US/Eastern')
@@ -7654,7 +7666,7 @@ def test_fillna_dataframe(self):
76547666
index = list('VWXYZ'))
76557667

76567668
assert_frame_equal(result, expected)
7657-
7669+
76587670
def test_fillna_columns(self):
76597671
df = DataFrame(np.random.randn(10, 10))
76607672
df.values[:, ::2] = np.nan
@@ -12791,8 +12803,8 @@ def test_consolidate_datetime64(self):
1279112803
df.starting = ser_starting.index
1279212804
df.ending = ser_ending.index
1279312805

12794-
assert_array_equal(df.starting.values, ser_starting.index.values)
12795-
assert_array_equal(df.ending.values, ser_ending.index.values)
12806+
tm.assert_index_equal(pd.DatetimeIndex(df.starting), ser_starting.index)
12807+
tm.assert_index_equal(pd.DatetimeIndex(df.ending), ser_ending.index)
1279612808

1279712809
def _check_bool_op(self, name, alternative, frame=None, has_skipna=True,
1279812810
has_bool_only=False):

pandas/tests/test_series.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ def compare(s, name):
135135
freq_result = s.dt.freq
136136
self.assertEqual(freq_result, DatetimeIndex(s.values, freq='infer').freq)
137137

138+
# let's localize, then convert
139+
result = s.dt.tz_localize('UTC').dt.tz_convert('US/Eastern')
140+
expected = Series(DatetimeIndex(s.values).tz_localize('UTC').tz_convert('US/Eastern'),index=s.index)
141+
tm.assert_series_equal(result, expected)
142+
138143
# timedeltaindex
139144
for s in [Series(timedelta_range('1 day',periods=5)),
140145
Series(timedelta_range('1 day 01:23:45',periods=5,freq='s')),
@@ -810,6 +815,15 @@ def test_constructor_dtype_datetime64(self):
810815
s = Series([pd.NaT, np.nan, '2013-08-05 15:30:00.000001'])
811816
self.assertEqual(s.dtype,'datetime64[ns]')
812817

818+
# tz-aware (UTC and other tz's)
819+
# GH 8411
820+
dr = date_range('20130101',periods=3)
821+
self.assertTrue(Series(dr).iloc[0].tz is None)
822+
dr = date_range('20130101',periods=3,tz='UTC')
823+
self.assertTrue(str(Series(dr).iloc[0].tz) == 'UTC')
824+
dr = date_range('20130101',periods=3,tz='US/Eastern')
825+
self.assertTrue(str(Series(dr).iloc[0].tz) == 'US/Eastern')
826+
813827
def test_constructor_periodindex(self):
814828
# GH7932
815829
# converting a PeriodIndex when put in a Series

pandas/tseries/index.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -744,7 +744,7 @@ def _to_embed(self, keep_tz=False):
744744
745745
This is for internal compat
746746
"""
747-
if keep_tz and self.tz is not None and str(self.tz) != 'UTC':
747+
if keep_tz and self.tz is not None:
748748
return self.asobject.values
749749
return self.values
750750

0 commit comments

Comments
 (0)