diff --git a/doc/source/v0.14.1.txt b/doc/source/v0.14.1.txt index 3e06a705487df..20067b75755bb 100644 --- a/doc/source/v0.14.1.txt +++ b/doc/source/v0.14.1.txt @@ -123,3 +123,5 @@ Bug Fixes - Bug where a string column name assignment to a ``DataFrame`` with a ``Float64Index`` raised a ``TypeError`` during a call to ``np.isnan`` (:issue:`7366`). +- BUG in ``DatetimeIndex.insert`` doesn't preserve ``name`` and ``tz`` (:issue:`7299`) +- BUG in ``DatetimeIndex.asobject`` doesn't preserve ``name`` (:issue:`7299`) diff --git a/pandas/tseries/index.py b/pandas/tseries/index.py index 42cc80cc5dc63..f23a7a6fd20ac 100644 --- a/pandas/tseries/index.py +++ b/pandas/tseries/index.py @@ -784,7 +784,7 @@ def tolist(self): def _get_object_index(self): boxfunc = lambda x: Timestamp(x, offset=self.offset, tz=self.tz) boxed_values = lib.map_infer(self.asi8, boxfunc) - return Index(boxed_values, dtype=object) + return Index(boxed_values, dtype=object, name=self.name) def to_pydatetime(self): """ @@ -1594,15 +1594,30 @@ def insert(self, loc, item): ------- new_index : Index """ + freq = None if isinstance(item, datetime): + zone = tslib.get_timezone(self.tz) + izone = tslib.get_timezone(getattr(item, 'tzinfo', None)) + if zone != izone: + raise ValueError('Passed item and index have different timezone') + + # check freq can be preserved on edge cases + if self.freq is not None: + if (loc == 0 or loc == -len(self)) and item + self.freq == self[0]: + freq = self.freq + elif (loc == len(self)) and item - self.freq == self[-1]: + freq = self.freq + item = _to_m8(item, tz=self.tz) + try: - new_index = np.concatenate((self[:loc].asi8, - [item.view(np.int64)], - self[loc:].asi8)) - return DatetimeIndex(new_index, freq='infer') - except (AttributeError, TypeError): + new_dates = np.concatenate((self[:loc].asi8, [item.view(np.int64)], + self[loc:].asi8)) + if self.tz is not None: + new_dates = tslib.date_normalize(new_dates, self.tz) + return DatetimeIndex(new_dates, name=self.name, freq=freq, tz=self.tz) + except (AttributeError, TypeError): # fall back to object index if isinstance(item,compat.string_types): return self.asobject.insert(loc, item) diff --git a/pandas/tseries/tests/test_timeseries.py b/pandas/tseries/tests/test_timeseries.py index 83cc5dcc7485f..52e813aaeffda 100644 --- a/pandas/tseries/tests/test_timeseries.py +++ b/pandas/tseries/tests/test_timeseries.py @@ -2273,24 +2273,101 @@ def test_order(self): self.assertTrue(ordered[::-1].is_monotonic) self.assert_numpy_array_equal(dexer, [0, 2, 1]) + def test_asobject(self): + idx = date_range(start='2013-01-01', periods=4, freq='M', name='idx') + expected = Index([Timestamp('2013-01-31'), Timestamp('2013-02-28'), + Timestamp('2013-03-31'), Timestamp('2013-04-30')], + dtype=object, name='idx') + + result = idx.asobject + self.assertTrue(result.equals(expected)) + self.assertEqual(result.name, expected.name) + def test_insert(self): - idx = DatetimeIndex(['2000-01-04', '2000-01-01', '2000-01-02']) + idx = DatetimeIndex(['2000-01-04', '2000-01-01', '2000-01-02'], name='idx') result = idx.insert(2, datetime(2000, 1, 5)) - exp = DatetimeIndex(['2000-01-04', '2000-01-01', '2000-01-05', - '2000-01-02']) - self.assertTrue(result.equals(exp)) + expected = DatetimeIndex(['2000-01-04', '2000-01-01', '2000-01-05', + '2000-01-02'], name='idx') + self.assertTrue(result.equals(expected)) + self.assertEqual(result.name, expected.name) # insertion of non-datetime should coerce to object index result = idx.insert(1, 'inserted') expected = Index([datetime(2000, 1, 4), 'inserted', datetime(2000, 1, 1), - datetime(2000, 1, 2)]) + datetime(2000, 1, 2)], name='idx') self.assertNotIsInstance(result, DatetimeIndex) tm.assert_index_equal(result, expected) + self.assertEqual(result.name, expected.name) + + idx = date_range('1/1/2000', periods=3, freq='M', name='idx') + + # preserve freq + expected_0 = DatetimeIndex(['1999-12-31', '2000-01-31', '2000-02-29', + '2000-03-31'], name='idx', freq='M') + expected_3 = DatetimeIndex(['2000-01-31', '2000-02-29', '2000-03-31', + '2000-04-30'], name='idx', freq='M') + + # reset freq to None + expected_1_nofreq = DatetimeIndex(['2000-01-31', '2000-01-31', '2000-02-29', + '2000-03-31'], name='idx', freq=None) + expected_3_nofreq = DatetimeIndex(['2000-01-31', '2000-02-29', '2000-03-31', + '2000-01-02'], name='idx', freq=None) + + cases = [(0, datetime(1999, 12, 31), expected_0), + (-3, datetime(1999, 12, 31), expected_0), + (3, datetime(2000, 4, 30), expected_3), + (1, datetime(2000, 1, 31), expected_1_nofreq), + (3, datetime(2000, 1, 2), expected_3_nofreq)] + + for n, d, expected in cases: + result = idx.insert(n, d) + self.assertTrue(result.equals(expected)) + self.assertEqual(result.name, expected.name) + self.assertEqual(result.freq, expected.freq) - idx = date_range('1/1/2000', periods=3, freq='M') - result = idx.insert(3, datetime(2000, 4, 30)) - self.assertEqual(result.freqstr, 'M') + # reset freq to None + result = idx.insert(3, datetime(2000, 1, 2)) + expected = DatetimeIndex(['2000-01-31', '2000-02-29', '2000-03-31', + '2000-01-02'], name='idx', freq=None) + self.assertTrue(result.equals(expected)) + self.assertEqual(result.name, expected.name) + self.assertTrue(result.freq is None) + + # GH 7299 + _skip_if_no_pytz() + import pytz + + idx = date_range('1/1/2000', periods=3, freq='D', tz='US/Pacific', name='idx') + with tm.assertRaises(ValueError): + result = idx.insert(3, pd.Timestamp('2000-01-04')) + with tm.assertRaises(ValueError): + result = idx.insert(3, datetime(2000, 1, 4)) + with tm.assertRaises(ValueError): + result = idx.insert(3, pd.Timestamp('2000-01-04', tz='US/Eastern')) + with tm.assertRaises(ValueError): + result = idx.insert(3, datetime(2000, 1, 4, tzinfo=pytz.timezone('US/Eastern'))) + + # preserve freq + expected = date_range('1/1/2000', periods=4, freq='D', tz='US/Pacific', name='idx') + for d in [pd.Timestamp('2000-01-04', tz='US/Pacific'), + datetime(2000, 1, 4, tzinfo=pytz.timezone('US/Pacific'))]: + + result = idx.insert(3, d) + self.assertTrue(result.equals(expected)) + self.assertEqual(result.name, expected.name) + self.assertEqual(result.freqstr, 'D') + + expected = DatetimeIndex(['2000-01-01', '2000-01-02', '2000-01-03', + '2000-01-02'], name='idx', + tz='US/Pacific', freq=None) + # reset freq to None + for d in [pd.Timestamp('2000-01-02', tz='US/Pacific'), + datetime(2000, 1, 2, tzinfo=pytz.timezone('US/Pacific'))]: + result = idx.insert(3, d) + self.assertTrue(result.equals(expected)) + self.assertEqual(result.name, expected.name) + self.assertTrue(result.freq is None) def test_delete(self): idx = date_range(start='2000-01-01', periods=5, freq='M', name='idx')