diff --git a/doc/source/whatsnew/v0.18.2.txt b/doc/source/whatsnew/v0.18.2.txt index c8a8b8eb0547b..a2f7981a85aa8 100644 --- a/doc/source/whatsnew/v0.18.2.txt +++ b/doc/source/whatsnew/v0.18.2.txt @@ -524,3 +524,5 @@ Bug Fixes - Bug in ``Categorical.remove_unused_categories()`` changes ``.codes`` dtype to platform int (:issue:`13261`) + +- Bug in ``DatetimeIndex.is_normalized`` returns False for normalized date_range in case of local timezones (:issue:`13459`) diff --git a/pandas/io/tests/test_pytables.py b/pandas/io/tests/test_pytables.py index 9c13162bd774c..ab5362da21a7d 100644 --- a/pandas/io/tests/test_pytables.py +++ b/pandas/io/tests/test_pytables.py @@ -34,7 +34,8 @@ assert_panel_equal, assert_frame_equal, assert_series_equal, - assert_produces_warning) + assert_produces_warning, + set_timezone) from pandas import concat, Timestamp from pandas import compat from pandas.compat import range, lrange, u @@ -5309,14 +5310,6 @@ def test_store_timezone(self): # issue storing datetime.date with a timezone as it resets when read # back in a new timezone - import platform - if platform.system() == "Windows": - raise nose.SkipTest("timezone setting not supported on windows") - - import datetime - import time - import os - # original method with ensure_clean_store(self.path) as store: @@ -5327,34 +5320,17 @@ def test_store_timezone(self): assert_frame_equal(result, df) # with tz setting - orig_tz = os.environ.get('TZ') - - def setTZ(tz): - if tz is None: - try: - del os.environ['TZ'] - except: - pass - else: - os.environ['TZ'] = tz - time.tzset() - - try: - - with ensure_clean_store(self.path) as store: + with ensure_clean_store(self.path) as store: - setTZ('EST5EDT') + with set_timezone('EST5EDT'): today = datetime.date(2013, 9, 10) df = DataFrame([1, 2, 3], index=[today, today, today]) store['obj1'] = df - setTZ('CST6CDT') + with set_timezone('CST6CDT'): result = store['obj1'] - assert_frame_equal(result, df) - - finally: - setTZ(orig_tz) + assert_frame_equal(result, df) def test_legacy_datetimetz_object(self): # legacy from < 0.17.0 diff --git a/pandas/tseries/tests/test_timezones.py b/pandas/tseries/tests/test_timezones.py index afe9d0652db19..d68ff793c9b6a 100644 --- a/pandas/tseries/tests/test_timezones.py +++ b/pandas/tseries/tests/test_timezones.py @@ -18,7 +18,7 @@ import pandas.util.testing as tm from pandas.types.api import DatetimeTZDtype -from pandas.util.testing import assert_frame_equal +from pandas.util.testing import assert_frame_equal, set_timezone from pandas.compat import lrange, zip try: @@ -1398,6 +1398,26 @@ def test_normalize_tz(self): self.assertTrue(result.is_normalized) self.assertFalse(rng.is_normalized) + def test_normalize_tz_local(self): + # GH 13459 + from dateutil.tz import tzlocal + + timezones = ['US/Pacific', 'US/Eastern', 'UTC', 'Asia/Kolkata', + 'Asia/Shanghai', 'Australia/Canberra'] + + for timezone in timezones: + with set_timezone(timezone): + rng = date_range('1/1/2000 9:30', periods=10, freq='D', + tz=tzlocal()) + + result = rng.normalize() + expected = date_range('1/1/2000', periods=10, freq='D', + tz=tzlocal()) + self.assert_index_equal(result, expected) + + self.assertTrue(result.is_normalized) + self.assertFalse(rng.is_normalized) + def test_tzaware_offset(self): dates = date_range('2012-11-01', periods=3, tz='US/Pacific') offset = dates + offsets.Hour(5) diff --git a/pandas/tslib.pyx b/pandas/tslib.pyx index 7de62fbe71615..8837881af0b6c 100644 --- a/pandas/tslib.pyx +++ b/pandas/tslib.pyx @@ -4810,12 +4810,10 @@ def dates_normalized(ndarray[int64_t] stamps, tz=None): elif _is_tzlocal(tz): for i in range(n): pandas_datetime_to_datetimestruct(stamps[i], PANDAS_FR_ns, &dts) - if (dts.min + dts.sec + dts.us) > 0: - return False dt = datetime(dts.year, dts.month, dts.day, dts.hour, dts.min, dts.sec, dts.us, tz) dt = dt + tz.utcoffset(dt) - if dt.hour > 0: + if (dt.hour + dt.minute + dt.second + dt.microsecond) > 0: return False else: trans, deltas, typ = _get_dst_info(tz) diff --git a/pandas/util/testing.py b/pandas/util/testing.py index 8c4d2f838ee8d..2961b2fb2241f 100644 --- a/pandas/util/testing.py +++ b/pandas/util/testing.py @@ -2667,3 +2667,50 @@ def patch(ob, attr, value): delattr(ob, attr) else: setattr(ob, attr, old) + + +@contextmanager +def set_timezone(tz): + """Context manager for temporarily setting a timezone. + + Parameters + ---------- + tz : str + A string representing a valid timezone. + + Examples + -------- + + >>> from datetime import datetime + >>> from dateutil.tz import tzlocal + >>> tzlocal().tzname(datetime.now()) + 'IST' + + >>> with set_timezone('US/Eastern'): + ... tzlocal().tzname(datetime.now()) + ... + 'EDT' + """ + if is_platform_windows(): + import nose + raise nose.SkipTest("timezone setting not supported on windows") + + import os + import time + + def setTZ(tz): + if tz is None: + try: + del os.environ['TZ'] + except: + pass + else: + os.environ['TZ'] = tz + time.tzset() + + orig_tz = os.environ.get('TZ') + setTZ(tz) + try: + yield + finally: + setTZ(orig_tz)