diff --git a/arrow/locales.py b/arrow/locales.py index 757df480..235d1ee5 100644 --- a/arrow/locales.py +++ b/arrow/locales.py @@ -307,25 +307,31 @@ class EnglishLocale(Locale): timeframes = { "now": "just now", "second": "a second", - "seconds": "{0} seconds", + "seconds": {"singular": "{0} second", "plural": "{0} seconds"}, "minute": "a minute", - "minutes": "{0} minutes", + "minutes": {"singular": "{0} minute", "plural": "{0} minutes"}, "hour": "an hour", - "hours": "{0} hours", + "hours": {"singular": "{0} hour", "plural": "{0} hours"}, "day": "a day", - "days": "{0} days", + "days": {"singular": "{0} day", "plural": "{0} days"}, "week": "a week", - "weeks": "{0} weeks", + "weeks": {"singular": "{0} week", "plural": "{0} weeks"}, "month": "a month", - "months": "{0} months", + "months": {"singular": "{0} month", "plural": "{0} months"}, "quarter": "a quarter", - "quarters": "{0} quarters", + "quarters": {"singular": "{0} quarter", "plural": "{0} quarters"}, "year": "a year", - "years": "{0} years", + "years": {"singular": "{0} year", "plural": "{0} years"}, } meridians = {"am": "am", "pm": "pm", "AM": "AM", "PM": "PM"} + def _format_timeframe(self, timeframe: TimeFrameLiteral, delta: int) -> str: + form = self.timeframes[timeframe] + if isinstance(form, Mapping): + form = form["singular"] if abs(delta) == 1 else form["plural"] + return form.format(trunc(abs(delta))) + month_names = [ "", "January", diff --git a/tests/test_arrow.py b/tests/test_arrow.py index b595e4e2..a1ec26fa 100644 --- a/tests/test_arrow.py +++ b/tests/test_arrow.py @@ -2987,6 +2987,34 @@ def test_czech_slovak(self): assert arw.dehumanize(past_string, locale=lang) == past assert arw.dehumanize(future_string, locale=lang) == future + def test_singular_nouns_english(self): + """Test that dehumanize recognizes '1 day ago', '1 hour ago', etc. + + Regression test for https://github.com/arrow-py/arrow/issues/1150 + """ + arw = arrow.Arrow(2000, 6, 18, 5, 55, 0) + + # All singular units should work with numeric "1" + assert arw.dehumanize("1 second ago") == arw.shift(seconds=-1) + assert arw.dehumanize("1 minute ago") == arw.shift(minutes=-1) + assert arw.dehumanize("1 hour ago") == arw.shift(hours=-1) + assert arw.dehumanize("1 day ago") == arw.shift(days=-1) + assert arw.dehumanize("1 week ago") == arw.shift(weeks=-1) + assert arw.dehumanize("1 month ago") == arw.shift(months=-1) + assert arw.dehumanize("1 year ago") == arw.shift(years=-1) + + # Future tense + assert arw.dehumanize("in 1 day") == arw.shift(days=1) + assert arw.dehumanize("in 1 hour") == arw.shift(hours=1) + + # "a/an" forms should still work + assert arw.dehumanize("a day ago") == arw.shift(days=-1) + assert arw.dehumanize("an hour ago") == arw.shift(hours=-1) + + # Plural forms should still work + assert arw.dehumanize("2 days ago") == arw.shift(days=-2) + assert arw.dehumanize("3 hours ago") == arw.shift(hours=-3) + class TestArrowIsBetween: def test_start_before_end(self):