Skip to content

ENH: tz_localize(None) allows to reset tz #7852

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 5, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions doc/source/timeseries.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1454,6 +1454,19 @@ to determine the right offset.
rng_hourly_eastern = rng_hourly.tz_localize('US/Eastern', infer_dst=True)
rng_hourly_eastern.values


To remove timezone from tz-aware ``DatetimeIndex``, use ``tz_localize(None)`` or ``tz_convert(None)``. ``tz_localize(None)`` will remove timezone holding local time representations. ``tz_convert(None)`` will remove timezone after converting to UTC time.

.. ipython:: python

didx = DatetimeIndex(start='2014-08-01 09:00', freq='H', periods=10, tz='US/Eastern')
didx
didx.tz_localize(None)
didx.tz_convert(None)

# tz_convert(None) is identical with tz_convert('UTC').tz_localize(None)
didx.tz_convert('UCT').tz_localize(None)

.. _timeseries.timedeltas:

Time Deltas
Expand Down
12 changes: 12 additions & 0 deletions doc/source/v0.15.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,18 @@ API changes
In [3]: idx.isin(['a', 'c', 'e'], level=1)
Out[3]: array([ True, False, True, True, False, True], dtype=bool)

- ``tz_localize(None)`` for tz-aware ``Timestamp`` and ``DatetimeIndex`` now removes timezone holding local time,
previously results in ``Exception`` or ``TypeError`` (:issue:`7812`)

.. ipython:: python

ts = Timestamp('2014-08-01 09:00', tz='US/Eastern')
ts
ts.tz_localize(None)

didx = DatetimeIndex(start='2014-08-01 09:00', freq='H', periods=10, tz='US/Eastern')
didx
didx.tz_localize(None)

.. _whatsnew_0150.cat:

Expand Down
31 changes: 21 additions & 10 deletions pandas/tseries/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -1618,7 +1618,14 @@ def _view_like(self, ndarray):

def tz_convert(self, tz):
"""
Convert DatetimeIndex from one time zone to another (using pytz/dateutil)
Convert tz-aware DatetimeIndex from one time zone to another (using pytz/dateutil)

Parameters
----------
tz : string, pytz.timezone, dateutil.tz.tzfile or None
Time zone for time. Corresponding timestamps would be converted to
time zone of the TimeSeries.
None will remove timezone holding UTC time.

Returns
-------
Expand All @@ -1636,13 +1643,15 @@ def tz_convert(self, tz):

def tz_localize(self, tz, infer_dst=False):
"""
Localize tz-naive DatetimeIndex to given time zone (using pytz/dateutil)
Localize tz-naive DatetimeIndex to given time zone (using pytz/dateutil),
or remove timezone from tz-aware DatetimeIndex

Parameters
----------
tz : string or pytz.timezone or dateutil.tz.tzfile
tz : string, pytz.timezone, dateutil.tz.tzfile or None
Time zone for time. Corresponding timestamps would be converted to
time zone of the TimeSeries
time zone of the TimeSeries.
None will remove timezone holding local time.
infer_dst : boolean, default False
Attempt to infer fall dst-transition hours based on order

Expand All @@ -1651,13 +1660,15 @@ def tz_localize(self, tz, infer_dst=False):
localized : DatetimeIndex
"""
if self.tz is not None:
raise TypeError("Already tz-aware, use tz_convert to convert.")
tz = tslib.maybe_get_tz(tz)

# Convert to UTC
new_dates = tslib.tz_localize_to_utc(self.asi8, tz, infer_dst=infer_dst)
if tz is None:
new_dates = tslib.tz_convert(self.asi8, 'UTC', self.tz)
else:
raise TypeError("Already tz-aware, use tz_convert to convert.")
else:
tz = tslib.maybe_get_tz(tz)
# Convert to UTC
new_dates = tslib.tz_localize_to_utc(self.asi8, tz, infer_dst=infer_dst)
new_dates = new_dates.view(_NS_DTYPE)

return self._simple_new(new_dates, self.name, self.offset, tz)

def indexer_at_time(self, time, asof=False):
Expand Down
42 changes: 42 additions & 0 deletions pandas/tseries/tests/test_timezones.py
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,7 @@ def test_cache_keys_are_distinct_for_pytz_vs_dateutil(self):

class TestTimeZones(tm.TestCase):
_multiprocess_can_split_ = True
timezones = ['UTC', 'Asia/Tokyo', 'US/Eastern', 'dateutil/US/Pacific']

def setUp(self):
tm._skip_if_no_pytz()
Expand All @@ -882,6 +883,24 @@ def test_tz_localize_naive(self):

self.assertTrue(conv.equals(exp))

def test_tz_localize_roundtrip(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you test tz_convert here as well?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added tests for DatetimeIndex and Timestamp.

for tz in self.timezones:
idx1 = date_range(start='2014-01-01', end='2014-12-31', freq='M')
idx2 = date_range(start='2014-01-01', end='2014-12-31', freq='D')
idx3 = date_range(start='2014-01-01', end='2014-03-01', freq='H')
idx4 = date_range(start='2014-08-01', end='2014-10-31', freq='T')
for idx in [idx1, idx2, idx3, idx4]:
localized = idx.tz_localize(tz)
expected = date_range(start=idx[0], end=idx[-1], freq=idx.freq, tz=tz)
tm.assert_index_equal(localized, expected)

with tm.assertRaises(TypeError):
localized.tz_localize(tz)

reset = localized.tz_localize(None)
tm.assert_index_equal(reset, idx)
self.assertTrue(reset.tzinfo is None)

def test_series_frame_tz_localize(self):

rng = date_range('1/1/2011', periods=100, freq='H')
Expand Down Expand Up @@ -930,6 +949,29 @@ def test_series_frame_tz_convert(self):
ts = Series(1, index=rng)
tm.assertRaisesRegexp(TypeError, "Cannot convert tz-naive", ts.tz_convert, 'US/Eastern')

def test_tz_convert_roundtrip(self):
for tz in self.timezones:
idx1 = date_range(start='2014-01-01', end='2014-12-31', freq='M', tz='UTC')
exp1 = date_range(start='2014-01-01', end='2014-12-31', freq='M')

idx2 = date_range(start='2014-01-01', end='2014-12-31', freq='D', tz='UTC')
exp2 = date_range(start='2014-01-01', end='2014-12-31', freq='D')

idx3 = date_range(start='2014-01-01', end='2014-03-01', freq='H', tz='UTC')
exp3 = date_range(start='2014-01-01', end='2014-03-01', freq='H')

idx4 = date_range(start='2014-08-01', end='2014-10-31', freq='T', tz='UTC')
exp4 = date_range(start='2014-08-01', end='2014-10-31', freq='T')


for idx, expected in [(idx1, exp1), (idx2, exp2), (idx3, exp3), (idx4, exp4)]:
converted = idx.tz_convert(tz)
reset = converted.tz_convert(None)
tm.assert_index_equal(reset, expected)
self.assertTrue(reset.tzinfo is None)
tm.assert_index_equal(reset, converted.tz_convert('UTC').tz_localize(None))


def test_join_utc_convert(self):
rng = date_range('1/1/2011', periods=100, freq='H', tz='utc')

Expand Down
27 changes: 27 additions & 0 deletions pandas/tseries/tests/test_tslib.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,33 @@ def test_tz(self):
self.assertEqual(conv.nanosecond, 5)
self.assertEqual(conv.hour, 19)

def test_tz_localize_roundtrip(self):
for tz in ['UTC', 'Asia/Tokyo', 'US/Eastern', 'dateutil/US/Pacific']:
for t in ['2014-02-01 09:00', '2014-07-08 09:00', '2014-11-01 17:00',
'2014-11-05 00:00']:
ts = Timestamp(t)
localized = ts.tz_localize(tz)
self.assertEqual(localized, Timestamp(t, tz=tz))

with tm.assertRaises(Exception):
localized.tz_localize(tz)

reset = localized.tz_localize(None)
self.assertEqual(reset, ts)
self.assertTrue(reset.tzinfo is None)

def test_tz_convert_roundtrip(self):
for tz in ['UTC', 'Asia/Tokyo', 'US/Eastern', 'dateutil/US/Pacific']:
for t in ['2014-02-01 09:00', '2014-07-08 09:00', '2014-11-01 17:00',
'2014-11-05 00:00']:
ts = Timestamp(t, tz='UTC')
converted = ts.tz_convert(tz)

reset = converted.tz_convert(None)
self.assertEqual(reset, Timestamp(t))
self.assertTrue(reset.tzinfo is None)
self.assertEqual(reset, converted.tz_convert('UTC').tz_localize(None))

def test_barely_oob_dts(self):
one_us = np.timedelta64(1).astype('timedelta64[us]')

Expand Down
20 changes: 15 additions & 5 deletions pandas/tslib.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -373,11 +373,14 @@ class Timestamp(_Timestamp):

def tz_localize(self, tz, infer_dst=False):
"""
Convert naive Timestamp to local time zone
Convert naive Timestamp to local time zone, or remove
timezone from tz-aware Timestamp.

Parameters
----------
tz : pytz.timezone or dateutil.tz.tzfile
tz : string, pytz.timezone, dateutil.tz.tzfile or None
Time zone for time which Timestamp will be converted to.
None will remove timezone holding local time.
infer_dst : boolean, default False
Attempt to infer fall dst-transition hours based on order

Expand All @@ -392,8 +395,13 @@ class Timestamp(_Timestamp):
infer_dst=infer_dst)[0]
return Timestamp(value, tz=tz)
else:
raise Exception('Cannot localize tz-aware Timestamp, use '
'tz_convert for conversions')
if tz is None:
# reset tz
value = tz_convert_single(self.value, 'UTC', self.tz)
return Timestamp(value, tz=None)
else:
raise Exception('Cannot localize tz-aware Timestamp, use '
'tz_convert for conversions')

def tz_convert(self, tz):
"""
Expand All @@ -402,7 +410,9 @@ class Timestamp(_Timestamp):

Parameters
----------
tz : pytz.timezone or dateutil.tz.tzfile
tz : string, pytz.timezone, dateutil.tz.tzfile or None
Time zone for time which Timestamp will be converted to.
None will remove timezone holding UTC time.

Returns
-------
Expand Down