Repository: arrow-py/arrow

Issue:
Czech/Slovak Locales Humanize Error When Zero Delta
<!--
Thanks for taking the time to submit this bug report.

Please provide us with a detailed description of the bug and a bit of information about your system.
-->

## Issue Description

The Czech and Slovak locales fail when using Humanize on a delta of zero. See the following example below. 

```python
arw = arrow.Arrow(2000, 2, 18, 1, 50, 30)

past = arw.shift(minutes=0, days=-1)
               
past_string = past.humanize(
         arw, locale='cs', granularity=["minute", "day"]
       )
```

I suspect this is due to the fact that typically humanize when it sees a zero delta, defaults to the plural timeframe (i.e second vs seconds). This causes an issue for these locales since the "zero" string for a timeframe is located in the non-plural timeframe unit within the timeframe object. 

## System Info

- 🖥  **OS name and version**: macOS 10.15.7 <!-- Replace with OS name and version (e.g. macOS 10.15.7). -->
- 🐍  **Python version**: 3.9.6 <!-- Replace with Python version (e.g. Python 3.8.5). -->
- 🏹  **Arrow version**: 1.2.1 <!-- Replace with Arrow version. Run arrow.__version__ to find out! -->




Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/arrow-py/arrow/pull/1079?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=arrow-py) Report
> Merging [#1079](https://codecov.io/gh/arrow-py/arrow/pull/1079?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=arrow-py) (e2c232f) into [master](https://codecov.io/gh/arrow-py/arrow/commit/a2ebb7e20b4897ee7b7bf4676a5ac3e6c0f27f47?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=arrow-py) (a2ebb7e) will **not change** coverage.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/arrow-py/arrow/pull/1079/graphs/tree.svg?width=650&height=150&src=pr&token=4EkGnFsIGn&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=arrow-py)](https://codecov.io/gh/arrow-py/arrow/pull/1079?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=arrow-py)

```diff
@@            Coverage Diff            @@
##            master     #1079   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           10        10           
  Lines         2237      2238    +1     
  Branches       439       439           
=========================================
+ Hits          2237      2238    +1     
```


| [Impacted Files](https://codecov.io/gh/arrow-py/arrow/pull/1079?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=arrow-py) | Coverage Δ | |
|---|---|---|
| [arrow/constants.py](https://codecov.io/gh/arrow-py/arrow/pull/1079/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=arrow-py#diff-YXJyb3cvY29uc3RhbnRzLnB5) | `100.00% <ø> (ø)` | |
| [arrow/locales.py](https://codecov.io/gh/arrow-py/arrow/pull/1079/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=arrow-py#diff-YXJyb3cvbG9jYWxlcy5weQ==) | `100.00% <ø> (ø)` | |
| [arrow/arrow.py](https://codecov.io/gh/arrow-py/arrow/pull/1079/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=arrow-py#diff-YXJyb3cvYXJyb3cucHk=) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/arrow-py/arrow/pull/1079?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=arrow-py).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=arrow-py)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/arrow-py/arrow/pull/1079?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=arrow-py). Last update [a2ebb7e...e2c232f](https://codecov.io/gh/arrow-py/arrow/pull/1079?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=arrow-py). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=arrow-py).


Patch:
diff --git a/arrow/constants.py b/arrow/constants.py
index d26bc0d..1189d07 100644
--- a/arrow/constants.py
+++ b/arrow/constants.py
@@ -110,6 +110,10 @@ DEHUMANIZE_LOCALES = {
     "da-dk",
     "ml",
     "hi",
+    "cs",
+    "cs-cz",
+    "sk",
+    "sk-sk",
     "fa",
     "fa-ir",
     "mr",
diff --git a/arrow/locales.py b/arrow/locales.py
index 6b1627e..ddbecb7 100644
--- a/arrow/locales.py
+++ b/arrow/locales.py
@@ -3067,44 +3067,51 @@ class CzechLocale(Locale):
 
     timeframes: ClassVar[Mapping[TimeFrameLiteral, Union[str, Mapping[str, str]]]] = {
         "now": "Teď",
-        "second": {"past": "vteřina", "future": "vteřina", "zero": "vteřina"},
+        "second": {"past": "vteřina", "future": "vteřina"},
         "seconds": {
+            "zero": "vteřina",
             "past": "{0} sekundami",
             "future-singular": "{0} sekundy",
             "future-paucal": "{0} sekund",
         },
-        "minute": {"past": "minutou", "future": "minutu", "zero": "{0} minut"},
+        "minute": {"past": "minutou", "future": "minutu"},
         "minutes": {
+            "zero": "{0} minut",
             "past": "{0} minutami",
             "future-singular": "{0} minuty",
             "future-paucal": "{0} minut",
         },
-        "hour": {"past": "hodinou", "future": "hodinu", "zero": "{0} hodin"},
+        "hour": {"past": "hodinou", "future": "hodinu"},
         "hours": {
+            "zero": "{0} hodin",
             "past": "{0} hodinami",
             "future-singular": "{0} hodiny",
             "future-paucal": "{0} hodin",
         },
-        "day": {"past": "dnem", "future": "den", "zero": "{0} dnů"},
+        "day": {"past": "dnem", "future": "den"},
         "days": {
+            "zero": "{0} dnů",
             "past": "{0} dny",
             "future-singular": "{0} dny",
             "future-paucal": "{0} dnů",
         },
-        "week": {"past": "týdnem", "future": "týden", "zero": "{0} týdnů"},
+        "week": {"past": "týdnem", "future": "týden"},
         "weeks": {
+            "zero": "{0} týdnů",
             "past": "{0} týdny",
             "future-singular": "{0} týdny",
             "future-paucal": "{0} týdnů",
         },
-        "month": {"past": "měsícem", "future": "měsíc", "zero": "{0} měsíců"},
+        "month": {"past": "měsícem", "future": "měsíc"},
         "months": {
+            "zero": "{0} měsíců",
             "past": "{0} měsíci",
             "future-singular": "{0} měsíce",
             "future-paucal": "{0} měsíců",
         },
-        "year": {"past": "rokem", "future": "rok", "zero": "{0} let"},
+        "year": {"past": "rokem", "future": "rok"},
         "years": {
+            "zero": "{0} let",
             "past": "{0} lety",
             "future-singular": "{0} roky",
             "future-paucal": "{0} let",
@@ -3190,44 +3197,51 @@ class SlovakLocale(Locale):
 
     timeframes: ClassVar[Mapping[TimeFrameLiteral, Union[str, Mapping[str, str]]]] = {
         "now": "Teraz",
-        "second": {"past": "sekundou", "future": "sekundu", "zero": "{0} sekúnd"},
+        "second": {"past": "sekundou", "future": "sekundu"},
         "seconds": {
+            "zero": "{0} sekúnd",
             "past": "{0} sekundami",
             "future-singular": "{0} sekundy",
             "future-paucal": "{0} sekúnd",
         },
-        "minute": {"past": "minútou", "future": "minútu", "zero": "{0} minút"},
+        "minute": {"past": "minútou", "future": "minútu"},
         "minutes": {
+            "zero": "{0} minút",
             "past": "{0} minútami",
             "future-singular": "{0} minúty",
             "future-paucal": "{0} minút",
         },
-        "hour": {"past": "hodinou", "future": "hodinu", "zero": "{0} hodín"},
+        "hour": {"past": "hodinou", "future": "hodinu"},
         "hours": {
+            "zero": "{0} hodín",
             "past": "{0} hodinami",
             "future-singular": "{0} hodiny",
             "future-paucal": "{0} hodín",
         },
-        "day": {"past": "dňom", "future": "deň", "zero": "{0} dní"},
+        "day": {"past": "dňom", "future": "deň"},
         "days": {
+            "zero": "{0} dní",
             "past": "{0} dňami",
             "future-singular": "{0} dni",
             "future-paucal": "{0} dní",
         },
-        "week": {"past": "týždňom", "future": "týždeň", "zero": "{0} týždňov"},
+        "week": {"past": "týždňom", "future": "týždeň"},
         "weeks": {
+            "zero": "{0} týždňov",
             "past": "{0} týždňami",
             "future-singular": "{0} týždne",
             "future-paucal": "{0} týždňov",
         },
-        "month": {"past": "mesiacom", "future": "mesiac", "zero": "{0} mesiacov"},
+        "month": {"past": "mesiacom", "future": "mesiac"},
         "months": {
+            "zero": "{0} mesiacov",
             "past": "{0} mesiacmi",
             "future-singular": "{0} mesiace",
             "future-paucal": "{0} mesiacov",
         },
-        "year": {"past": "rokom", "future": "rok", "zero": "{0} rokov"},
+        "year": {"past": "rokom", "future": "rok"},
         "years": {
+            "zero": "{0} rokov",
             "past": "{0} rokmi",
             "future-singular": "{0} roky",
             "future-paucal": "{0} rokov",


Test patch:
diff --git a/tests/test_arrow.py b/tests/test_arrow.py
index a2f0881..60f3a64 100644
--- a/tests/test_arrow.py
+++ b/tests/test_arrow.py
@@ -2410,6 +2410,10 @@ def locale_list_no_weeks() -> List[str]:
         "da-dk",
         "ml",
         "hi",
+        "cs",
+        "cs-cz",
+        "sk",
+        "sk-sk",
         "fa",
         "fa-ir",
         "mr",
@@ -2511,6 +2515,10 @@ def locale_list_with_weeks() -> List[str]:
         "pt",
         "pt-pt",
         "pt-br",
+        "cs",
+        "cs-cz",
+        "sk",
+        "sk-sk",
         "tl",
         "tl-ph",
         "vi",
@@ -2954,6 +2962,34 @@ class TestArrowDehumanize:
                 assert arw.dehumanize(past_string, locale=lang) == past
                 assert arw.dehumanize(future_string, locale=lang) == future
 
+    def test_czech_slovak(self):
+
+        # Relevant units for Slavic locale plural logic
+        units = [
+            0,
+            1,
+            2,
+            5,
+        ]
+
+        # Only need to test on seconds as logic holds for all slavic plural units
+        for lang in ["cs"]:
+            for unit in units:
+                arw = arrow.Arrow(2000, 2, 18, 1, 50, 30)
+
+                past = arw.shift(minutes=-1 * unit, days=-1)
+                future = arw.shift(minutes=unit, days=1)
+
+                past_string = past.humanize(
+                    arw, locale=lang, granularity=["minute", "day"]
+                )
+                future_string = future.humanize(
+                    arw, locale=lang, granularity=["minute", "day"]
+                )
+
+                assert arw.dehumanize(past_string, locale=lang) == past
+                assert arw.dehumanize(future_string, locale=lang) == future
+
 
 class TestArrowIsBetween:
     def test_start_before_end(self):
diff --git a/tests/test_locales.py b/tests/test_locales.py
index 54f99ef..54536c0 100644
--- a/tests/test_locales.py
+++ b/tests/test_locales.py
@@ -613,8 +613,8 @@ class TestCzechLocale:
 
         # Second(s)
         assert self.locale._format_timeframe("second", -1) == "vteřina"
-        assert self.locale._format_timeframe("second", 0) == "vteřina"
         assert self.locale._format_timeframe("second", 1) == "vteřina"
+        assert self.locale._format_timeframe("seconds", 0) == "vteřina"
         assert self.locale._format_timeframe("seconds", -2) == "2 sekundami"
         assert self.locale._format_timeframe("seconds", -5) == "5 sekundami"
         assert self.locale._format_timeframe("seconds", 2) == "2 sekundy"
@@ -622,8 +622,8 @@ class TestCzechLocale:
 
         # Minute(s)
         assert self.locale._format_timeframe("minute", -1) == "minutou"
-        assert self.locale._format_timeframe("minute", 0) == "0 minut"
         assert self.locale._format_timeframe("minute", 1) == "minutu"
+        assert self.locale._format_timeframe("minutes", 0) == "0 minut"
         assert self.locale._format_timeframe("minutes", -2) == "2 minutami"
         assert self.locale._format_timeframe("minutes", -5) == "5 minutami"
         assert self.locale._format_timeframe("minutes", 2) == "2 minuty"
@@ -631,8 +631,8 @@ class TestCzechLocale:
 
         # Hour(s)
         assert self.locale._format_timeframe("hour", -1) == "hodinou"
-        assert self.locale._format_timeframe("hour", 0) == "0 hodin"
         assert self.locale._format_timeframe("hour", 1) == "hodinu"
+        assert self.locale._format_timeframe("hours", 0) == "0 hodin"
         assert self.locale._format_timeframe("hours", -2) == "2 hodinami"
         assert self.locale._format_timeframe("hours", -5) == "5 hodinami"
         assert self.locale._format_timeframe("hours", 2) == "2 hodiny"
@@ -640,8 +640,8 @@ class TestCzechLocale:
 
         # Day(s)
         assert self.locale._format_timeframe("day", -1) == "dnem"
-        assert self.locale._format_timeframe("day", 0) == "0 dnů"
         assert self.locale._format_timeframe("day", 1) == "den"
+        assert self.locale._format_timeframe("days", 0) == "0 dnů"
         assert self.locale._format_timeframe("days", -2) == "2 dny"
         assert self.locale._format_timeframe("days", -5) == "5 dny"
         assert self.locale._format_timeframe("days", 2) == "2 dny"
@@ -649,8 +649,8 @@ class TestCzechLocale:
 
         # Weeks(s)
         assert self.locale._format_timeframe("week", -1) == "týdnem"
-        assert self.locale._format_timeframe("week", 0) == "0 týdnů"
         assert self.locale._format_timeframe("week", 1) == "týden"
+        assert self.locale._format_timeframe("weeks", 0) == "0 týdnů"
         assert self.locale._format_timeframe("weeks", -2) == "2 týdny"
         assert self.locale._format_timeframe("weeks", -5) == "5 týdny"
         assert self.locale._format_timeframe("weeks", 2) == "2 týdny"
@@ -658,8 +658,8 @@ class TestCzechLocale:
 
         # Month(s)
         assert self.locale._format_timeframe("month", -1) == "měsícem"
-        assert self.locale._format_timeframe("month", 0) == "0 měsíců"
         assert self.locale._format_timeframe("month", 1) == "měsíc"
+        assert self.locale._format_timeframe("months", 0) == "0 měsíců"
         assert self.locale._format_timeframe("months", -2) == "2 měsíci"
         assert self.locale._format_timeframe("months", -5) == "5 měsíci"
         assert self.locale._format_timeframe("months", 2) == "2 měsíce"
@@ -667,8 +667,8 @@ class TestCzechLocale:
 
         # Year(s)
         assert self.locale._format_timeframe("year", -1) == "rokem"
-        assert self.locale._format_timeframe("year", 0) == "0 let"
         assert self.locale._format_timeframe("year", 1) == "rok"
+        assert self.locale._format_timeframe("years", 0) == "0 let"
         assert self.locale._format_timeframe("years", -2) == "2 lety"
         assert self.locale._format_timeframe("years", -5) == "5 lety"
         assert self.locale._format_timeframe("years", 2) == "2 roky"
@@ -697,7 +697,7 @@ class TestSlovakLocale:
         assert self.locale._format_timeframe("seconds", -5) == "5 sekundami"
         assert self.locale._format_timeframe("seconds", -2) == "2 sekundami"
         assert self.locale._format_timeframe("second", -1) == "sekundou"
-        assert self.locale._format_timeframe("second", 0) == "0 sekúnd"
+        assert self.locale._format_timeframe("seconds", 0) == "0 sekúnd"
         assert self.locale._format_timeframe("second", 1) == "sekundu"
         assert self.locale._format_timeframe("seconds", 2) == "2 sekundy"
         assert self.locale._format_timeframe("seconds", 5) == "5 sekúnd"
@@ -705,7 +705,7 @@ class TestSlovakLocale:
         assert self.locale._format_timeframe("minutes", -5) == "5 minútami"
         assert self.locale._format_timeframe("minutes", -2) == "2 minútami"
         assert self.locale._format_timeframe("minute", -1) == "minútou"
-        assert self.locale._format_timeframe("minute", 0) == "0 minút"
+        assert self.locale._format_timeframe("minutes", 0) == "0 minút"
         assert self.locale._format_timeframe("minute", 1) == "minútu"
         assert self.locale._format_timeframe("minutes", 2) == "2 minúty"
         assert self.locale._format_timeframe("minutes", 5) == "5 minút"
@@ -713,7 +713,7 @@ class TestSlovakLocale:
         assert self.locale._format_timeframe("hours", -5) == "5 hodinami"
         assert self.locale._format_timeframe("hours", -2) == "2 hodinami"
         assert self.locale._format_timeframe("hour", -1) == "hodinou"
-        assert self.locale._format_timeframe("hour", 0) == "0 hodín"
+        assert self.locale._format_timeframe("hours", 0) == "0 hodín"
         assert self.locale._format_timeframe("hour", 1) == "hodinu"
         assert self.locale._format_timeframe("hours", 2) == "2 hodiny"
         assert self.locale._format_timeframe("hours", 5) == "5 hodín"
@@ -721,7 +721,7 @@ class TestSlovakLocale:
         assert self.locale._format_timeframe("days", -5) == "5 dňami"
         assert self.locale._format_timeframe("days", -2) == "2 dňami"
         assert self.locale._format_timeframe("day", -1) == "dňom"
-        assert self.locale._format_timeframe("day", 0) == "0 dní"
+        assert self.locale._format_timeframe("days", 0) == "0 dní"
         assert self.locale._format_timeframe("day", 1) == "deň"
         assert self.locale._format_timeframe("days", 2) == "2 dni"
         assert self.locale._format_timeframe("days", 5) == "5 dní"
@@ -729,7 +729,7 @@ class TestSlovakLocale:
         assert self.locale._format_timeframe("weeks", -5) == "5 týždňami"
         assert self.locale._format_timeframe("weeks", -2) == "2 týždňami"
         assert self.locale._format_timeframe("week", -1) == "týždňom"
-        assert self.locale._format_timeframe("week", 0) == "0 týždňov"
+        assert self.locale._format_timeframe("weeks", 0) == "0 týždňov"
         assert self.locale._format_timeframe("week", 1) == "týždeň"
         assert self.locale._format_timeframe("weeks", 2) == "2 týždne"
         assert self.locale._format_timeframe("weeks", 5) == "5 týždňov"
@@ -737,7 +737,7 @@ class TestSlovakLocale:
         assert self.locale._format_timeframe("months", -5) == "5 mesiacmi"
         assert self.locale._format_timeframe("months", -2) == "2 mesiacmi"
         assert self.locale._format_timeframe("month", -1) == "mesiacom"
-        assert self.locale._format_timeframe("month", 0) == "0 mesiacov"
+        assert self.locale._format_timeframe("months", 0) == "0 mesiacov"
         assert self.locale._format_timeframe("month", 1) == "mesiac"
         assert self.locale._format_timeframe("months", 2) == "2 mesiace"
         assert self.locale._format_timeframe("months", 5) == "5 mesiacov"
@@ -745,7 +745,7 @@ class TestSlovakLocale:
         assert self.locale._format_timeframe("years", -5) == "5 rokmi"
         assert self.locale._format_timeframe("years", -2) == "2 rokmi"
         assert self.locale._format_timeframe("year", -1) == "rokom"
-        assert self.locale._format_timeframe("year", 0) == "0 rokov"
+        assert self.locale._format_timeframe("years", 0) == "0 rokov"
         assert self.locale._format_timeframe("year", 1) == "rok"
         assert self.locale._format_timeframe("years", 2) == "2 roky"
         assert self.locale._format_timeframe("years", 5) == "5 rokov"