diff --git a/arrow/arrow.py b/arrow/arrow.py index 07947c116..5bec032cb 100644 --- a/arrow/arrow.py +++ b/arrow/arrow.py @@ -12,6 +12,8 @@ from dateutil.relativedelta import relativedelta import calendar import sys +import warnings + from arrow import util, locales, parser, formatter @@ -378,16 +380,17 @@ def replace(self, **kwargs): >>> arw.replace(year=2014, month=6) - Use plural property names to shift their current value relatively: - - >>> arw.replace(years=1, months=-1) - - You can also provide a timezone expression can also be replaced: >>> arw.replace(tzinfo=tz.tzlocal()) + NOTE: Deprecated in next release + Use plural property names to shift their current value relatively: + + >>> arw.replace(years=1, months=-1) + + Recognized timezone expressions: - A ``tzinfo`` object. @@ -398,13 +401,17 @@ def replace(self, **kwargs): ''' absolute_kwargs = {} - relative_kwargs = {} + relative_kwargs = {} # TODO: DEPRECATED; remove in next release for key, value in kwargs.items(): if key in self._ATTRS: absolute_kwargs[key] = value elif key in self._ATTRS_PLURAL or key == 'weeks': + # TODO: DEPRECATED + warnings.warn("replace() with plural property to shift value" + "is deprecated, use shift() instead", + DeprecationWarning) relative_kwargs[key] = value elif key == 'week': raise AttributeError('setting absolute week is not supported') @@ -412,7 +419,7 @@ def replace(self, **kwargs): raise AttributeError() current = self._datetime.replace(**absolute_kwargs) - current += relativedelta(**relative_kwargs) + current += relativedelta(**relative_kwargs) # TODO: DEPRECATED tzinfo = kwargs.get('tzinfo') @@ -422,9 +429,37 @@ def replace(self, **kwargs): return self.fromdatetime(current) + def shift(self, **kwargs): + ''' Returns a new :class:`Arrow ` object with + attributes updated according to inputs. + + Use plural property names to shift their current value relatively: + + >>> import arrow + >>> arw = arrow.utcnow() + >>> arw + + >>> arw.shift(years=1, months=-1) + + + ''' + + relative_kwargs = {} + + for key, value in kwargs.items(): + + if key in self._ATTRS_PLURAL or key == 'weeks': + relative_kwargs[key] = value + else: + raise AttributeError() + + current = self._datetime + relativedelta(**relative_kwargs) + + return self.fromdatetime(current) + def to(self, tz): - ''' Returns a new :class:`Arrow ` object, converted to the target - timezone. + ''' Returns a new :class:`Arrow ` object, converted + to the target timezone. :param tz: an expression representing a timezone. diff --git a/tests/arrow_tests.py b/tests/arrow_tests.py index 882479573..e14208dc7 100644 --- a/tests/arrow_tests.py +++ b/tests/arrow_tests.py @@ -7,6 +7,7 @@ from datetime import date, datetime, timedelta from dateutil import tz import simplejson as json +import warnings import calendar import pickle import time @@ -203,9 +204,31 @@ def test_ne(self): assertFalse(self.arrow != self.arrow.datetime) assertTrue(self.arrow != 'abc') + def test_deprecated_replace(self): + + with warnings.catch_warnings(record=True) as w: + # Cause all warnings to always be triggered. + warnings.simplefilter("always") + # Trigger a warning. + self.arrow.replace(weeks=1) + # Verify some things + assert len(w) == 1 + assert issubclass(w[-1].category, DeprecationWarning) + assert "deprecated" in str(w[-1].message) + + with warnings.catch_warnings(record=True) as w: + # Cause all warnings to always be triggered. + warnings.simplefilter("always") + # Trigger a warning. + self.arrow.replace(hours=1) + # Verify some things + assert len(w) == 1 + assert issubclass(w[-1].category, DeprecationWarning) + assert "deprecated" in str(w[-1].message) + def test_gt(self): - arrow_cmp = self.arrow.replace(minutes=1) + arrow_cmp = self.arrow.shift(minutes=1) assertFalse(self.arrow > self.arrow) assertFalse(self.arrow > self.arrow.datetime) @@ -226,7 +249,7 @@ def test_ge(self): def test_lt(self): - arrow_cmp = self.arrow.replace(minutes=1) + arrow_cmp = self.arrow.shift(minutes=1) assertFalse(self.arrow < self.arrow) assertFalse(self.arrow < self.arrow.datetime) @@ -440,7 +463,7 @@ def test_not_attr(self): with assertRaises(AttributeError): arrow.Arrow.utcnow().replace(abc=1) - def test_replace_absolute(self): + def test_replace(self): arw = arrow.Arrow(2013, 5, 5, 12, 30, 45) @@ -451,32 +474,6 @@ def test_replace_absolute(self): assertEqual(arw.replace(minute=1), arrow.Arrow(2013, 5, 5, 12, 1, 45)) assertEqual(arw.replace(second=1), arrow.Arrow(2013, 5, 5, 12, 30, 1)) - def test_replace_relative(self): - - arw = arrow.Arrow(2013, 5, 5, 12, 30, 45) - - assertEqual(arw.replace(years=1), arrow.Arrow(2014, 5, 5, 12, 30, 45)) - assertEqual(arw.replace(months=1), arrow.Arrow(2013, 6, 5, 12, 30, 45)) - assertEqual(arw.replace(weeks=1), arrow.Arrow(2013, 5, 12, 12, 30, 45)) - assertEqual(arw.replace(days=1), arrow.Arrow(2013, 5, 6, 12, 30, 45)) - assertEqual(arw.replace(hours=1), arrow.Arrow(2013, 5, 5, 13, 30, 45)) - assertEqual(arw.replace(minutes=1), arrow.Arrow(2013, 5, 5, 12, 31, 45)) - assertEqual(arw.replace(seconds=1), arrow.Arrow(2013, 5, 5, 12, 30, 46)) - - def test_replace_relative_negative(self): - - arw = arrow.Arrow(2013, 5, 5, 12, 30, 45) - - assertEqual(arw.replace(years=-1), arrow.Arrow(2012, 5, 5, 12, 30, 45)) - assertEqual(arw.replace(months=-1), arrow.Arrow(2013, 4, 5, 12, 30, 45)) - assertEqual(arw.replace(weeks=-1), arrow.Arrow(2013, 4, 28, 12, 30, 45)) - assertEqual(arw.replace(days=-1), arrow.Arrow(2013, 5, 4, 12, 30, 45)) - assertEqual(arw.replace(hours=-1), arrow.Arrow(2013, 5, 5, 11, 30, 45)) - assertEqual(arw.replace(minutes=-1), arrow.Arrow(2013, 5, 5, 12, 29, 45)) - assertEqual(arw.replace(seconds=-1), arrow.Arrow(2013, 5, 5, 12, 30, 44)) - assertEqual(arw.replace(microseconds=-1), arrow.Arrow(2013, 5, 5, 12, 30, 44, 999999)) - - def test_replace_tzinfo(self): arw = arrow.Arrow.utcnow().to('US/Eastern') @@ -495,6 +492,38 @@ def test_replace_other_kwargs(self): with assertRaises(AttributeError): arrow.utcnow().replace(abc='def') +class ArrowShiftTests(Chai): + + def test_not_attr(self): + + with assertRaises(AttributeError): + arrow.Arrow.utcnow().replace(abc=1) + + def test_shift(self): + + arw = arrow.Arrow(2013, 5, 5, 12, 30, 45) + + assertEqual(arw.shift(years=1), arrow.Arrow(2014, 5, 5, 12, 30, 45)) + assertEqual(arw.shift(months=1), arrow.Arrow(2013, 6, 5, 12, 30, 45)) + assertEqual(arw.shift(weeks=1), arrow.Arrow(2013, 5, 12, 12, 30, 45)) + assertEqual(arw.shift(days=1), arrow.Arrow(2013, 5, 6, 12, 30, 45)) + assertEqual(arw.shift(hours=1), arrow.Arrow(2013, 5, 5, 13, 30, 45)) + assertEqual(arw.shift(minutes=1), arrow.Arrow(2013, 5, 5, 12, 31, 45)) + assertEqual(arw.shift(seconds=1), arrow.Arrow(2013, 5, 5, 12, 30, 46)) + + def test_shift_negative(self): + + arw = arrow.Arrow(2013, 5, 5, 12, 30, 45) + + assertEqual(arw.shift(years=-1), arrow.Arrow(2012, 5, 5, 12, 30, 45)) + assertEqual(arw.shift(months=-1), arrow.Arrow(2013, 4, 5, 12, 30, 45)) + assertEqual(arw.shift(weeks=-1), arrow.Arrow(2013, 4, 28, 12, 30, 45)) + assertEqual(arw.shift(days=-1), arrow.Arrow(2013, 5, 4, 12, 30, 45)) + assertEqual(arw.shift(hours=-1), arrow.Arrow(2013, 5, 5, 11, 30, 45)) + assertEqual(arw.shift(minutes=-1), arrow.Arrow(2013, 5, 5, 12, 29, 45)) + assertEqual(arw.shift(seconds=-1), arrow.Arrow(2013, 5, 5, 12, 30, 44)) + assertEqual(arw.shift(microseconds=-1), arrow.Arrow(2013, 5, 5, 12, 30, 44, 999999)) + class ArrowRangeTests(Chai): @@ -909,7 +938,7 @@ def setUp(self): def test_seconds(self): - later = self.now.replace(seconds=10) + later = self.now.shift(seconds=10) assertEqual(self.now.humanize(later), 'seconds ago') assertEqual(later.humanize(self.now), 'in seconds') @@ -919,7 +948,7 @@ def test_seconds(self): def test_minute(self): - later = self.now.replace(minutes=1) + later = self.now.shift(minutes=1) assertEqual(self.now.humanize(later), 'a minute ago') assertEqual(later.humanize(self.now), 'in a minute') @@ -930,7 +959,7 @@ def test_minute(self): def test_minutes(self): - later = self.now.replace(minutes=2) + later = self.now.shift(minutes=2) assertEqual(self.now.humanize(later), '2 minutes ago') assertEqual(later.humanize(self.now), 'in 2 minutes') @@ -940,7 +969,7 @@ def test_minutes(self): def test_hour(self): - later = self.now.replace(hours=1) + later = self.now.shift(hours=1) assertEqual(self.now.humanize(later), 'an hour ago') assertEqual(later.humanize(self.now), 'in an hour') @@ -950,7 +979,7 @@ def test_hour(self): def test_hours(self): - later = self.now.replace(hours=2) + later = self.now.shift(hours=2) assertEqual(self.now.humanize(later), '2 hours ago') assertEqual(later.humanize(self.now), 'in 2 hours') @@ -960,7 +989,7 @@ def test_hours(self): def test_day(self): - later = self.now.replace(days=1) + later = self.now.shift(days=1) assertEqual(self.now.humanize(later), 'a day ago') assertEqual(later.humanize(self.now), 'in a day') @@ -970,7 +999,7 @@ def test_day(self): def test_days(self): - later = self.now.replace(days=2) + later = self.now.shift(days=2) assertEqual(self.now.humanize(later), '2 days ago') assertEqual(later.humanize(self.now), 'in 2 days') @@ -980,7 +1009,7 @@ def test_days(self): def test_month(self): - later = self.now.replace(months=1) + later = self.now.shift(months=1) assertEqual(self.now.humanize(later), 'a month ago') assertEqual(later.humanize(self.now), 'in a month') @@ -990,7 +1019,7 @@ def test_month(self): def test_months(self): - later = self.now.replace(months=2) + later = self.now.shift(months=2) assertEqual(self.now.humanize(later), '2 months ago') assertEqual(later.humanize(self.now), 'in 2 months') @@ -1000,7 +1029,7 @@ def test_months(self): def test_year(self): - later = self.now.replace(years=1) + later = self.now.shift(years=1) assertEqual(self.now.humanize(later), 'a year ago') assertEqual(later.humanize(self.now), 'in a year') @@ -1010,7 +1039,7 @@ def test_year(self): def test_years(self): - later = self.now.replace(years=2) + later = self.now.shift(years=2) assertEqual(self.now.humanize(later), '2 years ago') assertEqual(later.humanize(self.now), 'in 2 years')