Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 44 additions & 9 deletions arrow/arrow.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
from dateutil.relativedelta import relativedelta
import calendar
import sys
import warnings


from arrow import util, locales, parser, formatter

Expand Down Expand Up @@ -378,16 +380,17 @@ def replace(self, **kwargs):
>>> arw.replace(year=2014, month=6)
<Arrow [2014-06-11T22:27:34.787885+00:00]>

Use plural property names to shift their current value relatively:

>>> arw.replace(years=1, months=-1)
<Arrow [2014-04-11T22:27:34.787885+00:00]>

You can also provide a timezone expression can also be replaced:

>>> arw.replace(tzinfo=tz.tzlocal())
<Arrow [2013-05-11T22:27:34.787885-07:00]>

NOTE: Deprecated in next release
Use plural property names to shift their current value relatively:

>>> arw.replace(years=1, months=-1)
<Arrow [2014-04-11T22:27:34.787885+00:00]>

Recognized timezone expressions:

- A ``tzinfo`` object.
Expand All @@ -398,21 +401,25 @@ 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')
elif key !='tzinfo':
raise AttributeError()

current = self._datetime.replace(**absolute_kwargs)
current += relativedelta(**relative_kwargs)
current += relativedelta(**relative_kwargs) # TODO: DEPRECATED

tzinfo = kwargs.get('tzinfo')

Expand All @@ -422,9 +429,37 @@ def replace(self, **kwargs):

return self.fromdatetime(current)

def shift(self, **kwargs):
''' Returns a new :class:`Arrow <arrow.arrow.Arrow>` object with
attributes updated according to inputs.

Use plural property names to shift their current value relatively:

>>> import arrow
>>> arw = arrow.utcnow()
>>> arw
<Arrow [2013-05-11T22:27:34.787885+00:00]>
>>> arw.shift(years=1, months=-1)
<Arrow [2014-04-11T22:27:34.787885+00:00]>

'''

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 <arrow.arrow.Arrow>` object, converted to the target
timezone.
''' Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, converted
to the target timezone.

:param tz: an expression representing a timezone.

Expand Down
109 changes: 69 additions & 40 deletions tests/arrow_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)

Expand All @@ -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')
Expand All @@ -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):

Expand Down Expand Up @@ -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')
Expand All @@ -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')
Expand All @@ -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')
Expand All @@ -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')
Expand All @@ -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')
Expand All @@ -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')
Expand All @@ -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')
Expand All @@ -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')
Expand All @@ -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')
Expand All @@ -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')
Expand All @@ -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')
Expand Down