|
| 1 | +# -*- coding: utf-8 -*- |
| 2 | +import warnings |
| 3 | +from datetime import datetime, timedelta |
| 4 | + |
| 5 | +import pytest |
| 6 | + |
| 7 | +import numpy as np |
| 8 | + |
| 9 | +import pandas as pd |
| 10 | +import pandas.util.testing as tm |
| 11 | +from pandas.errors import PerformanceWarning |
| 12 | +from pandas import (Timestamp, Timedelta, Series, |
| 13 | + DatetimeIndex, TimedeltaIndex, |
| 14 | + date_range) |
| 15 | + |
| 16 | + |
| 17 | +class TestDatetimeIndexArithmetic(object): |
| 18 | + tz = [None, 'UTC', 'Asia/Tokyo', 'US/Eastern', 'dateutil/Asia/Singapore', |
| 19 | + 'dateutil/US/Pacific'] |
| 20 | + |
| 21 | + def test_add_iadd(self): |
| 22 | + for tz in self.tz: |
| 23 | + |
| 24 | + # offset |
| 25 | + offsets = [pd.offsets.Hour(2), timedelta(hours=2), |
| 26 | + np.timedelta64(2, 'h'), Timedelta(hours=2)] |
| 27 | + |
| 28 | + for delta in offsets: |
| 29 | + rng = pd.date_range('2000-01-01', '2000-02-01', tz=tz) |
| 30 | + result = rng + delta |
| 31 | + expected = pd.date_range('2000-01-01 02:00', |
| 32 | + '2000-02-01 02:00', tz=tz) |
| 33 | + tm.assert_index_equal(result, expected) |
| 34 | + rng += delta |
| 35 | + tm.assert_index_equal(rng, expected) |
| 36 | + |
| 37 | + # int |
| 38 | + rng = pd.date_range('2000-01-01 09:00', freq='H', periods=10, |
| 39 | + tz=tz) |
| 40 | + result = rng + 1 |
| 41 | + expected = pd.date_range('2000-01-01 10:00', freq='H', periods=10, |
| 42 | + tz=tz) |
| 43 | + tm.assert_index_equal(result, expected) |
| 44 | + rng += 1 |
| 45 | + tm.assert_index_equal(rng, expected) |
| 46 | + |
| 47 | + idx = DatetimeIndex(['2011-01-01', '2011-01-02']) |
| 48 | + msg = "cannot add DatetimeIndex and Timestamp" |
| 49 | + with tm.assert_raises_regex(TypeError, msg): |
| 50 | + idx + Timestamp('2011-01-01') |
| 51 | + |
| 52 | + with tm.assert_raises_regex(TypeError, msg): |
| 53 | + Timestamp('2011-01-01') + idx |
| 54 | + |
| 55 | + def test_sub_isub(self): |
| 56 | + for tz in self.tz: |
| 57 | + |
| 58 | + # offset |
| 59 | + offsets = [pd.offsets.Hour(2), timedelta(hours=2), |
| 60 | + np.timedelta64(2, 'h'), Timedelta(hours=2)] |
| 61 | + |
| 62 | + for delta in offsets: |
| 63 | + rng = pd.date_range('2000-01-01', '2000-02-01', tz=tz) |
| 64 | + expected = pd.date_range('1999-12-31 22:00', |
| 65 | + '2000-01-31 22:00', tz=tz) |
| 66 | + |
| 67 | + result = rng - delta |
| 68 | + tm.assert_index_equal(result, expected) |
| 69 | + rng -= delta |
| 70 | + tm.assert_index_equal(rng, expected) |
| 71 | + |
| 72 | + # int |
| 73 | + rng = pd.date_range('2000-01-01 09:00', freq='H', periods=10, |
| 74 | + tz=tz) |
| 75 | + result = rng - 1 |
| 76 | + expected = pd.date_range('2000-01-01 08:00', freq='H', periods=10, |
| 77 | + tz=tz) |
| 78 | + tm.assert_index_equal(result, expected) |
| 79 | + rng -= 1 |
| 80 | + tm.assert_index_equal(rng, expected) |
| 81 | + |
| 82 | + @pytest.mark.parametrize('addend', [ |
| 83 | + datetime(2011, 1, 1), |
| 84 | + DatetimeIndex(['2011-01-01', '2011-01-02']), |
| 85 | + DatetimeIndex(['2011-01-01', '2011-01-02']).tz_localize('US/Eastern'), |
| 86 | + np.datetime64('2011-01-01'), |
| 87 | + Timestamp('2011-01-01')]) |
| 88 | + def test_add_datetimelike_and_dti(self, addend): |
| 89 | + # GH#9631 |
| 90 | + dti = DatetimeIndex(['2011-01-01', '2011-01-02']) |
| 91 | + msg = 'cannot add DatetimeIndex and {0}'.format( |
| 92 | + type(addend).__name__) |
| 93 | + with tm.assert_raises_regex(TypeError, msg): |
| 94 | + dti + addend |
| 95 | + with tm.assert_raises_regex(TypeError, msg): |
| 96 | + addend + dti |
| 97 | + |
| 98 | + @pytest.mark.parametrize('addend', [ |
| 99 | + datetime(2011, 1, 1), |
| 100 | + DatetimeIndex(['2011-01-01', '2011-01-02']), |
| 101 | + DatetimeIndex(['2011-01-01', '2011-01-02']).tz_localize('US/Eastern'), |
| 102 | + np.datetime64('2011-01-01'), |
| 103 | + Timestamp('2011-01-01')]) |
| 104 | + def test_add_datetimelike_and_dti_tz(self, addend): |
| 105 | + # GH#9631 |
| 106 | + dti_tz = DatetimeIndex(['2011-01-01', |
| 107 | + '2011-01-02']).tz_localize('US/Eastern') |
| 108 | + msg = 'cannot add DatetimeIndex and {0}'.format( |
| 109 | + type(addend).__name__) |
| 110 | + with tm.assert_raises_regex(TypeError, msg): |
| 111 | + dti_tz + addend |
| 112 | + with tm.assert_raises_regex(TypeError, msg): |
| 113 | + addend + dti_tz |
| 114 | + |
| 115 | + def test_sub_dti_dti(self): |
| 116 | + # previously performed setop (deprecated in 0.16.0), now changed to |
| 117 | + # return subtraction -> TimeDeltaIndex (GH ...) |
| 118 | + |
| 119 | + dti = date_range('20130101', periods=3) |
| 120 | + dti_tz = date_range('20130101', periods=3).tz_localize('US/Eastern') |
| 121 | + dti_tz2 = date_range('20130101', periods=3).tz_localize('UTC') |
| 122 | + expected = TimedeltaIndex([0, 0, 0]) |
| 123 | + |
| 124 | + result = dti - dti |
| 125 | + tm.assert_index_equal(result, expected) |
| 126 | + |
| 127 | + result = dti_tz - dti_tz |
| 128 | + tm.assert_index_equal(result, expected) |
| 129 | + |
| 130 | + with pytest.raises(TypeError): |
| 131 | + dti_tz - dti |
| 132 | + |
| 133 | + with pytest.raises(TypeError): |
| 134 | + dti - dti_tz |
| 135 | + |
| 136 | + with pytest.raises(TypeError): |
| 137 | + dti_tz - dti_tz2 |
| 138 | + |
| 139 | + # isub |
| 140 | + dti -= dti |
| 141 | + tm.assert_index_equal(dti, expected) |
| 142 | + |
| 143 | + # different length raises ValueError |
| 144 | + dti1 = date_range('20130101', periods=3) |
| 145 | + dti2 = date_range('20130101', periods=4) |
| 146 | + with pytest.raises(ValueError): |
| 147 | + dti1 - dti2 |
| 148 | + |
| 149 | + # NaN propagation |
| 150 | + dti1 = DatetimeIndex(['2012-01-01', np.nan, '2012-01-03']) |
| 151 | + dti2 = DatetimeIndex(['2012-01-02', '2012-01-03', np.nan]) |
| 152 | + expected = TimedeltaIndex(['1 days', np.nan, np.nan]) |
| 153 | + result = dti2 - dti1 |
| 154 | + tm.assert_index_equal(result, expected) |
| 155 | + |
| 156 | + def test_sub_period(self): |
| 157 | + # GH 13078 |
| 158 | + # not supported, check TypeError |
| 159 | + p = pd.Period('2011-01-01', freq='D') |
| 160 | + |
| 161 | + for freq in [None, 'D']: |
| 162 | + idx = pd.DatetimeIndex(['2011-01-01', '2011-01-02'], freq=freq) |
| 163 | + |
| 164 | + with pytest.raises(TypeError): |
| 165 | + idx - p |
| 166 | + |
| 167 | + with pytest.raises(TypeError): |
| 168 | + p - idx |
| 169 | + |
| 170 | + def test_ufunc_coercions(self): |
| 171 | + idx = date_range('2011-01-01', periods=3, freq='2D', name='x') |
| 172 | + |
| 173 | + delta = np.timedelta64(1, 'D') |
| 174 | + for result in [idx + delta, np.add(idx, delta)]: |
| 175 | + assert isinstance(result, DatetimeIndex) |
| 176 | + exp = date_range('2011-01-02', periods=3, freq='2D', name='x') |
| 177 | + tm.assert_index_equal(result, exp) |
| 178 | + assert result.freq == '2D' |
| 179 | + |
| 180 | + for result in [idx - delta, np.subtract(idx, delta)]: |
| 181 | + assert isinstance(result, DatetimeIndex) |
| 182 | + exp = date_range('2010-12-31', periods=3, freq='2D', name='x') |
| 183 | + tm.assert_index_equal(result, exp) |
| 184 | + assert result.freq == '2D' |
| 185 | + |
| 186 | + delta = np.array([np.timedelta64(1, 'D'), np.timedelta64(2, 'D'), |
| 187 | + np.timedelta64(3, 'D')]) |
| 188 | + for result in [idx + delta, np.add(idx, delta)]: |
| 189 | + assert isinstance(result, DatetimeIndex) |
| 190 | + exp = DatetimeIndex(['2011-01-02', '2011-01-05', '2011-01-08'], |
| 191 | + freq='3D', name='x') |
| 192 | + tm.assert_index_equal(result, exp) |
| 193 | + assert result.freq == '3D' |
| 194 | + |
| 195 | + for result in [idx - delta, np.subtract(idx, delta)]: |
| 196 | + assert isinstance(result, DatetimeIndex) |
| 197 | + exp = DatetimeIndex(['2010-12-31', '2011-01-01', '2011-01-02'], |
| 198 | + freq='D', name='x') |
| 199 | + tm.assert_index_equal(result, exp) |
| 200 | + assert result.freq == 'D' |
| 201 | + |
| 202 | + def test_overflow_offset(self): |
| 203 | + # xref https://github.com/statsmodels/statsmodels/issues/3374 |
| 204 | + # ends up multiplying really large numbers which overflow |
| 205 | + |
| 206 | + t = Timestamp('2017-01-13 00:00:00', freq='D') |
| 207 | + offset = 20169940 * pd.offsets.Day(1) |
| 208 | + |
| 209 | + def f(): |
| 210 | + t + offset |
| 211 | + pytest.raises(OverflowError, f) |
| 212 | + |
| 213 | + def f(): |
| 214 | + offset + t |
| 215 | + pytest.raises(OverflowError, f) |
| 216 | + |
| 217 | + def f(): |
| 218 | + t - offset |
| 219 | + pytest.raises(OverflowError, f) |
| 220 | + |
| 221 | + |
| 222 | +# GH 10699 |
| 223 | +@pytest.mark.parametrize('klass,assert_func', zip([Series, DatetimeIndex], |
| 224 | + [tm.assert_series_equal, |
| 225 | + tm.assert_index_equal])) |
| 226 | +def test_datetime64_with_DateOffset(klass, assert_func): |
| 227 | + s = klass(date_range('2000-01-01', '2000-01-31'), name='a') |
| 228 | + result = s + pd.DateOffset(years=1) |
| 229 | + result2 = pd.DateOffset(years=1) + s |
| 230 | + exp = klass(date_range('2001-01-01', '2001-01-31'), name='a') |
| 231 | + assert_func(result, exp) |
| 232 | + assert_func(result2, exp) |
| 233 | + |
| 234 | + result = s - pd.DateOffset(years=1) |
| 235 | + exp = klass(date_range('1999-01-01', '1999-01-31'), name='a') |
| 236 | + assert_func(result, exp) |
| 237 | + |
| 238 | + s = klass([Timestamp('2000-01-15 00:15:00', tz='US/Central'), |
| 239 | + pd.Timestamp('2000-02-15', tz='US/Central')], name='a') |
| 240 | + result = s + pd.offsets.Day() |
| 241 | + result2 = pd.offsets.Day() + s |
| 242 | + exp = klass([Timestamp('2000-01-16 00:15:00', tz='US/Central'), |
| 243 | + Timestamp('2000-02-16', tz='US/Central')], name='a') |
| 244 | + assert_func(result, exp) |
| 245 | + assert_func(result2, exp) |
| 246 | + |
| 247 | + s = klass([Timestamp('2000-01-15 00:15:00', tz='US/Central'), |
| 248 | + pd.Timestamp('2000-02-15', tz='US/Central')], name='a') |
| 249 | + result = s + pd.offsets.MonthEnd() |
| 250 | + result2 = pd.offsets.MonthEnd() + s |
| 251 | + exp = klass([Timestamp('2000-01-31 00:15:00', tz='US/Central'), |
| 252 | + Timestamp('2000-02-29', tz='US/Central')], name='a') |
| 253 | + assert_func(result, exp) |
| 254 | + assert_func(result2, exp) |
| 255 | + |
| 256 | + # array of offsets - valid for Series only |
| 257 | + if klass is Series: |
| 258 | + with tm.assert_produces_warning(PerformanceWarning): |
| 259 | + s = klass([Timestamp('2000-1-1'), Timestamp('2000-2-1')]) |
| 260 | + result = s + Series([pd.offsets.DateOffset(years=1), |
| 261 | + pd.offsets.MonthEnd()]) |
| 262 | + exp = klass([Timestamp('2001-1-1'), Timestamp('2000-2-29') |
| 263 | + ]) |
| 264 | + assert_func(result, exp) |
| 265 | + |
| 266 | + # same offset |
| 267 | + result = s + Series([pd.offsets.DateOffset(years=1), |
| 268 | + pd.offsets.DateOffset(years=1)]) |
| 269 | + exp = klass([Timestamp('2001-1-1'), Timestamp('2001-2-1')]) |
| 270 | + assert_func(result, exp) |
| 271 | + |
| 272 | + s = klass([Timestamp('2000-01-05 00:15:00'), |
| 273 | + Timestamp('2000-01-31 00:23:00'), |
| 274 | + Timestamp('2000-01-01'), |
| 275 | + Timestamp('2000-03-31'), |
| 276 | + Timestamp('2000-02-29'), |
| 277 | + Timestamp('2000-12-31'), |
| 278 | + Timestamp('2000-05-15'), |
| 279 | + Timestamp('2001-06-15')]) |
| 280 | + |
| 281 | + # DateOffset relativedelta fastpath |
| 282 | + relative_kwargs = [('years', 2), ('months', 5), ('days', 3), |
| 283 | + ('hours', 5), ('minutes', 10), ('seconds', 2), |
| 284 | + ('microseconds', 5)] |
| 285 | + for i, kwd in enumerate(relative_kwargs): |
| 286 | + op = pd.DateOffset(**dict([kwd])) |
| 287 | + assert_func(klass([x + op for x in s]), s + op) |
| 288 | + assert_func(klass([x - op for x in s]), s - op) |
| 289 | + op = pd.DateOffset(**dict(relative_kwargs[:i + 1])) |
| 290 | + assert_func(klass([x + op for x in s]), s + op) |
| 291 | + assert_func(klass([x - op for x in s]), s - op) |
| 292 | + |
| 293 | + # assert these are equal on a piecewise basis |
| 294 | + offsets = ['YearBegin', ('YearBegin', {'month': 5}), |
| 295 | + 'YearEnd', ('YearEnd', {'month': 5}), |
| 296 | + 'MonthBegin', 'MonthEnd', |
| 297 | + 'SemiMonthEnd', 'SemiMonthBegin', |
| 298 | + 'Week', ('Week', {'weekday': 3}), |
| 299 | + 'BusinessDay', 'BDay', 'QuarterEnd', 'QuarterBegin', |
| 300 | + 'CustomBusinessDay', 'CDay', 'CBMonthEnd', |
| 301 | + 'CBMonthBegin', 'BMonthBegin', 'BMonthEnd', |
| 302 | + 'BusinessHour', 'BYearBegin', 'BYearEnd', |
| 303 | + 'BQuarterBegin', ('LastWeekOfMonth', {'weekday': 2}), |
| 304 | + ('FY5253Quarter', {'qtr_with_extra_week': 1, |
| 305 | + 'startingMonth': 1, |
| 306 | + 'weekday': 2, |
| 307 | + 'variation': 'nearest'}), |
| 308 | + ('FY5253', {'weekday': 0, |
| 309 | + 'startingMonth': 2, |
| 310 | + 'variation': |
| 311 | + 'nearest'}), |
| 312 | + ('WeekOfMonth', {'weekday': 2, |
| 313 | + 'week': 2}), |
| 314 | + 'Easter', ('DateOffset', {'day': 4}), |
| 315 | + ('DateOffset', {'month': 5})] |
| 316 | + |
| 317 | + with warnings.catch_warnings(record=True): |
| 318 | + for normalize in (True, False): |
| 319 | + for do in offsets: |
| 320 | + if isinstance(do, tuple): |
| 321 | + do, kwargs = do |
| 322 | + else: |
| 323 | + do = do |
| 324 | + kwargs = {} |
| 325 | + |
| 326 | + for n in [0, 5]: |
| 327 | + if (do in ['WeekOfMonth', 'LastWeekOfMonth', |
| 328 | + 'FY5253Quarter', 'FY5253'] and n == 0): |
| 329 | + continue |
| 330 | + op = getattr(pd.offsets, do)(n, |
| 331 | + normalize=normalize, |
| 332 | + **kwargs) |
| 333 | + assert_func(klass([x + op for x in s]), s + op) |
| 334 | + assert_func(klass([x - op for x in s]), s - op) |
| 335 | + assert_func(klass([op + x for x in s]), op + s) |
0 commit comments