Skip to content

Commit 9b16d2e

Browse files
committed
Merge pull request #7852 from sinhrks/reset_tz
ENH: tz_localize(None) allows to reset tz
2 parents 1ffaab5 + e3a8f46 commit 9b16d2e

File tree

6 files changed

+130
-15
lines changed

6 files changed

+130
-15
lines changed

doc/source/timeseries.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1454,6 +1454,19 @@ to determine the right offset.
14541454
rng_hourly_eastern = rng_hourly.tz_localize('US/Eastern', infer_dst=True)
14551455
rng_hourly_eastern.values
14561456
1457+
1458+
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.
1459+
1460+
.. ipython:: python
1461+
1462+
didx = DatetimeIndex(start='2014-08-01 09:00', freq='H', periods=10, tz='US/Eastern')
1463+
didx
1464+
didx.tz_localize(None)
1465+
didx.tz_convert(None)
1466+
1467+
# tz_convert(None) is identical with tz_convert('UTC').tz_localize(None)
1468+
didx.tz_convert('UCT').tz_localize(None)
1469+
14571470
.. _timeseries.timedeltas:
14581471

14591472
Time Deltas

doc/source/v0.15.0.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,18 @@ API changes
142142
In [3]: idx.isin(['a', 'c', 'e'], level=1)
143143
Out[3]: array([ True, False, True, True, False, True], dtype=bool)
144144

145+
- ``tz_localize(None)`` for tz-aware ``Timestamp`` and ``DatetimeIndex`` now removes timezone holding local time,
146+
previously results in ``Exception`` or ``TypeError`` (:issue:`7812`)
147+
148+
.. ipython:: python
149+
150+
ts = Timestamp('2014-08-01 09:00', tz='US/Eastern')
151+
ts
152+
ts.tz_localize(None)
153+
154+
didx = DatetimeIndex(start='2014-08-01 09:00', freq='H', periods=10, tz='US/Eastern')
155+
didx
156+
didx.tz_localize(None)
145157

146158
.. _whatsnew_0150.cat:
147159

pandas/tseries/index.py

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1618,7 +1618,14 @@ def _view_like(self, ndarray):
16181618

16191619
def tz_convert(self, tz):
16201620
"""
1621-
Convert DatetimeIndex from one time zone to another (using pytz/dateutil)
1621+
Convert tz-aware DatetimeIndex from one time zone to another (using pytz/dateutil)
1622+
1623+
Parameters
1624+
----------
1625+
tz : string, pytz.timezone, dateutil.tz.tzfile or None
1626+
Time zone for time. Corresponding timestamps would be converted to
1627+
time zone of the TimeSeries.
1628+
None will remove timezone holding UTC time.
16221629
16231630
Returns
16241631
-------
@@ -1636,13 +1643,15 @@ def tz_convert(self, tz):
16361643

16371644
def tz_localize(self, tz, infer_dst=False):
16381645
"""
1639-
Localize tz-naive DatetimeIndex to given time zone (using pytz/dateutil)
1646+
Localize tz-naive DatetimeIndex to given time zone (using pytz/dateutil),
1647+
or remove timezone from tz-aware DatetimeIndex
16401648
16411649
Parameters
16421650
----------
1643-
tz : string or pytz.timezone or dateutil.tz.tzfile
1651+
tz : string, pytz.timezone, dateutil.tz.tzfile or None
16441652
Time zone for time. Corresponding timestamps would be converted to
1645-
time zone of the TimeSeries
1653+
time zone of the TimeSeries.
1654+
None will remove timezone holding local time.
16461655
infer_dst : boolean, default False
16471656
Attempt to infer fall dst-transition hours based on order
16481657
@@ -1651,13 +1660,15 @@ def tz_localize(self, tz, infer_dst=False):
16511660
localized : DatetimeIndex
16521661
"""
16531662
if self.tz is not None:
1654-
raise TypeError("Already tz-aware, use tz_convert to convert.")
1655-
tz = tslib.maybe_get_tz(tz)
1656-
1657-
# Convert to UTC
1658-
new_dates = tslib.tz_localize_to_utc(self.asi8, tz, infer_dst=infer_dst)
1663+
if tz is None:
1664+
new_dates = tslib.tz_convert(self.asi8, 'UTC', self.tz)
1665+
else:
1666+
raise TypeError("Already tz-aware, use tz_convert to convert.")
1667+
else:
1668+
tz = tslib.maybe_get_tz(tz)
1669+
# Convert to UTC
1670+
new_dates = tslib.tz_localize_to_utc(self.asi8, tz, infer_dst=infer_dst)
16591671
new_dates = new_dates.view(_NS_DTYPE)
1660-
16611672
return self._simple_new(new_dates, self.name, self.offset, tz)
16621673

16631674
def indexer_at_time(self, time, asof=False):

pandas/tseries/tests/test_timezones.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -863,6 +863,7 @@ def test_cache_keys_are_distinct_for_pytz_vs_dateutil(self):
863863

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

867868
def setUp(self):
868869
tm._skip_if_no_pytz()
@@ -882,6 +883,24 @@ def test_tz_localize_naive(self):
882883

883884
self.assertTrue(conv.equals(exp))
884885

886+
def test_tz_localize_roundtrip(self):
887+
for tz in self.timezones:
888+
idx1 = date_range(start='2014-01-01', end='2014-12-31', freq='M')
889+
idx2 = date_range(start='2014-01-01', end='2014-12-31', freq='D')
890+
idx3 = date_range(start='2014-01-01', end='2014-03-01', freq='H')
891+
idx4 = date_range(start='2014-08-01', end='2014-10-31', freq='T')
892+
for idx in [idx1, idx2, idx3, idx4]:
893+
localized = idx.tz_localize(tz)
894+
expected = date_range(start=idx[0], end=idx[-1], freq=idx.freq, tz=tz)
895+
tm.assert_index_equal(localized, expected)
896+
897+
with tm.assertRaises(TypeError):
898+
localized.tz_localize(tz)
899+
900+
reset = localized.tz_localize(None)
901+
tm.assert_index_equal(reset, idx)
902+
self.assertTrue(reset.tzinfo is None)
903+
885904
def test_series_frame_tz_localize(self):
886905

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

952+
def test_tz_convert_roundtrip(self):
953+
for tz in self.timezones:
954+
idx1 = date_range(start='2014-01-01', end='2014-12-31', freq='M', tz='UTC')
955+
exp1 = date_range(start='2014-01-01', end='2014-12-31', freq='M')
956+
957+
idx2 = date_range(start='2014-01-01', end='2014-12-31', freq='D', tz='UTC')
958+
exp2 = date_range(start='2014-01-01', end='2014-12-31', freq='D')
959+
960+
idx3 = date_range(start='2014-01-01', end='2014-03-01', freq='H', tz='UTC')
961+
exp3 = date_range(start='2014-01-01', end='2014-03-01', freq='H')
962+
963+
idx4 = date_range(start='2014-08-01', end='2014-10-31', freq='T', tz='UTC')
964+
exp4 = date_range(start='2014-08-01', end='2014-10-31', freq='T')
965+
966+
967+
for idx, expected in [(idx1, exp1), (idx2, exp2), (idx3, exp3), (idx4, exp4)]:
968+
converted = idx.tz_convert(tz)
969+
reset = converted.tz_convert(None)
970+
tm.assert_index_equal(reset, expected)
971+
self.assertTrue(reset.tzinfo is None)
972+
tm.assert_index_equal(reset, converted.tz_convert('UTC').tz_localize(None))
973+
974+
933975
def test_join_utc_convert(self):
934976
rng = date_range('1/1/2011', periods=100, freq='H', tz='utc')
935977

pandas/tseries/tests/test_tslib.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,33 @@ def test_tz(self):
9797
self.assertEqual(conv.nanosecond, 5)
9898
self.assertEqual(conv.hour, 19)
9999

100+
def test_tz_localize_roundtrip(self):
101+
for tz in ['UTC', 'Asia/Tokyo', 'US/Eastern', 'dateutil/US/Pacific']:
102+
for t in ['2014-02-01 09:00', '2014-07-08 09:00', '2014-11-01 17:00',
103+
'2014-11-05 00:00']:
104+
ts = Timestamp(t)
105+
localized = ts.tz_localize(tz)
106+
self.assertEqual(localized, Timestamp(t, tz=tz))
107+
108+
with tm.assertRaises(Exception):
109+
localized.tz_localize(tz)
110+
111+
reset = localized.tz_localize(None)
112+
self.assertEqual(reset, ts)
113+
self.assertTrue(reset.tzinfo is None)
114+
115+
def test_tz_convert_roundtrip(self):
116+
for tz in ['UTC', 'Asia/Tokyo', 'US/Eastern', 'dateutil/US/Pacific']:
117+
for t in ['2014-02-01 09:00', '2014-07-08 09:00', '2014-11-01 17:00',
118+
'2014-11-05 00:00']:
119+
ts = Timestamp(t, tz='UTC')
120+
converted = ts.tz_convert(tz)
121+
122+
reset = converted.tz_convert(None)
123+
self.assertEqual(reset, Timestamp(t))
124+
self.assertTrue(reset.tzinfo is None)
125+
self.assertEqual(reset, converted.tz_convert('UTC').tz_localize(None))
126+
100127
def test_barely_oob_dts(self):
101128
one_us = np.timedelta64(1).astype('timedelta64[us]')
102129

pandas/tslib.pyx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -373,11 +373,14 @@ class Timestamp(_Timestamp):
373373

374374
def tz_localize(self, tz, infer_dst=False):
375375
"""
376-
Convert naive Timestamp to local time zone
376+
Convert naive Timestamp to local time zone, or remove
377+
timezone from tz-aware Timestamp.
377378
378379
Parameters
379380
----------
380-
tz : pytz.timezone or dateutil.tz.tzfile
381+
tz : string, pytz.timezone, dateutil.tz.tzfile or None
382+
Time zone for time which Timestamp will be converted to.
383+
None will remove timezone holding local time.
381384
infer_dst : boolean, default False
382385
Attempt to infer fall dst-transition hours based on order
383386
@@ -392,8 +395,13 @@ class Timestamp(_Timestamp):
392395
infer_dst=infer_dst)[0]
393396
return Timestamp(value, tz=tz)
394397
else:
395-
raise Exception('Cannot localize tz-aware Timestamp, use '
396-
'tz_convert for conversions')
398+
if tz is None:
399+
# reset tz
400+
value = tz_convert_single(self.value, 'UTC', self.tz)
401+
return Timestamp(value, tz=None)
402+
else:
403+
raise Exception('Cannot localize tz-aware Timestamp, use '
404+
'tz_convert for conversions')
397405

398406
def tz_convert(self, tz):
399407
"""
@@ -402,7 +410,9 @@ class Timestamp(_Timestamp):
402410
403411
Parameters
404412
----------
405-
tz : pytz.timezone or dateutil.tz.tzfile
413+
tz : string, pytz.timezone, dateutil.tz.tzfile or None
414+
Time zone for time which Timestamp will be converted to.
415+
None will remove timezone holding UTC time.
406416
407417
Returns
408418
-------

0 commit comments

Comments
 (0)