Repository: crsmithdev/arrow
Pull request #640 (2019-08-17 20:20:09)

Fix pytz and tzinfo string bugs in Arrow factory
Closes: https://github.com/crsmithdev/arrow/issues/625
Closes: https://github.com/crsmithdev/arrow/issues/626

I needed to add a few other tests to get the coverage back up to 100% since I refactored some one-line ternary statements into multi-line conditionals for readability.

codecov-io: # [Codecov](https://codecov.io/gh/crsmithdev/arrow/pull/640?src=pr&el=h1) Report
> Merging [#640](https://codecov.io/gh/crsmithdev/arrow/pull/640?src=pr&el=desc) into [master](https://codecov.io/gh/crsmithdev/arrow/commit/11f5751e6dd6b1854829ab325ff208d79c9bee2d?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/crsmithdev/arrow/pull/640/graphs/tree.svg?width=650&token=4EkGnFsIGn&height=150&src=pr)](https://codecov.io/gh/crsmithdev/arrow/pull/640?src=pr&el=tree)

```diff
@@          Coverage Diff          @@
##           master   #640   +/-   ##
=====================================
  Coverage     100%   100%           
=====================================
  Files           8      8           
  Lines        1519   1531   +12     
  Branches      226    233    +7     
=====================================
+ Hits         1519   1531   +12
```


| [Impacted Files](https://codecov.io/gh/crsmithdev/arrow/pull/640?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [arrow/factory.py](https://codecov.io/gh/crsmithdev/arrow/pull/640/diff?src=pr&el=tree#diff-YXJyb3cvZmFjdG9yeS5weQ==) | `100% <100%> (ø)` | :arrow_up: |
| [arrow/arrow.py](https://codecov.io/gh/crsmithdev/arrow/pull/640/diff?src=pr&el=tree#diff-YXJyb3cvYXJyb3cucHk=) | `100% <100%> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/crsmithdev/arrow/pull/640?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/crsmithdev/arrow/pull/640?src=pr&el=footer). Last update [11f5751...cd1efae](https://codecov.io/gh/crsmithdev/arrow/pull/640?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 825d456..22aa587 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -2,17 +2,21 @@ default_language_version:
     python: python3
 repos:
     - repo: https://github.com/pre-commit/pre-commit-hooks
-      rev: v2.2.3
+      rev: v2.3.0
       hooks:
           - id: trailing-whitespace
           - id: end-of-file-fixer
           - id: fix-encoding-pragma
             exclude: ^arrow/_version.py
+          - id: requirements-txt-fixer
+          - id: check-ast
           - id: check-yaml
+          - id: check-case-conflict
+          - id: check-docstring-first
+          - id: check-merge-conflict
           - id: debug-statements
-          - id: requirements-txt-fixer
     - repo: https://github.com/asottile/seed-isort-config
-      rev: v1.9.1
+      rev: v1.9.2
       hooks:
           - id: seed-isort-config
     - repo: https://github.com/pre-commit/mirrors-isort
@@ -20,13 +24,14 @@ repos:
       hooks:
           - id: isort
     - repo: https://github.com/asottile/pyupgrade
-      rev: v1.20.1
+      rev: v1.22.1
       hooks:
           - id: pyupgrade
     - repo: https://github.com/pre-commit/pygrep-hooks
-      rev: v1.4.0
+      rev: v1.4.1
       hooks:
           - id: python-no-eval
+          - id: python-check-blanket-noqa
           - id: rst-backticks
     - repo: https://github.com/python/black
       rev: 19.3b0
diff --git a/README.rst b/README.rst
index b9a16be..84eaa29 100644
--- a/README.rst
+++ b/README.rst
@@ -32,7 +32,7 @@ What?
 
 Arrow is a Python library that offers a sensible, human-friendly approach to creating, manipulating, formatting and converting dates, times, and timestamps.  It implements and updates the datetime type, plugging gaps in functionality, and provides an intelligent module API that supports many common creation scenarios.  Simply put, it helps you work with dates and times with fewer imports and a lot less code.
 
-Arrow is heavily inspired by `moment.js <https://github.com/timrwood/moment>`_ and `requests <https://github.com/kennethreitz/requests>`_.
+Arrow is heavily inspired by `moment.js <https://github.com/moment/moment>`_ and `requests <https://github.com/psf/requests>`_.
 
 Why?
 ----
@@ -52,7 +52,7 @@ Features
 - Supports Python 2.7, 3.5, 3.6, 3.7 and 3.8
 - Timezone-aware & UTC by default
 - Provides super-simple creation options for many common input scenarios
-- Updated :code:`replace` method with support for relative offsets, including weeks
+- :code:`shift` method with support for relative offsets, including weeks
 - Formats and parses strings automatically
 - Partial support for ISO-8601
 - Timezone conversion
@@ -119,4 +119,4 @@ For full documentation, please visit `arrow.readthedocs.io <https://arrow.readth
 Contributing
 ------------
 
-Contributions are welcome for both code and localization. To get started, find an issue or feature to tackle on `the issue tracker <https://github.com/crsmithdev/arrow/issues>`_ and then fork `the repository <https://github.com/crsmithdev/arrow>`_ on GitHub to begin making changes. If you would like to help with localization, please see `locales.py <https://github.com/crsmithdev/arrow/blob/master/arrow/locales.py>`_ for what locales are currently supported. If you are helping with code, make sure to add tests to ensure that a bug was fixed or the feature works as intended.
+Contributions are welcome for both code and localizations. To get started, find an issue or feature to tackle on `the issue tracker <https://github.com/crsmithdev/arrow/issues>`_ and then fork `this repository <https://github.com/crsmithdev/arrow>`_ on GitHub to begin making changes. If you would like to help with localizations, please see `locales.py <https://github.com/crsmithdev/arrow/blob/master/arrow/locales.py>`_ to see what locales are currently supported. If you are helping with code, make sure to add a few tests to ensure that the bug was fixed or the feature works as intended.
diff --git a/README.rst.new b/README.rst.new
new file mode 100644
index 0000000..e69de29
diff --git a/arrow/arrow.py b/arrow/arrow.py
index 08b9b57..3a6f2ef 100644
--- a/arrow/arrow.py
+++ b/arrow/arrow.py
@@ -9,7 +9,8 @@ from __future__ import absolute_import
 
 import calendar
 import sys
-from datetime import datetime, timedelta, tzinfo
+from datetime import datetime, timedelta
+from datetime import tzinfo as dt_tzinfo
 from math import trunc
 
 from dateutil import tz as dateutil_tz
@@ -59,10 +60,17 @@ class Arrow(object):
     def __init__(
         self, year, month, day, hour=0, minute=0, second=0, microsecond=0, tzinfo=None
     ):
-
-        if util.isstr(tzinfo):
+        if tzinfo is None:
+            tzinfo = dateutil_tz.tzutc()
+        # detect that tzinfo is a pytz object (issue #626)
+        elif (
+            isinstance(tzinfo, dt_tzinfo)
+            and hasattr(tzinfo, "localize")
+            and tzinfo.zone
+        ):
+            tzinfo = parser.TzinfoParser.parse(tzinfo.zone)
+        elif util.isstr(tzinfo):
             tzinfo = parser.TzinfoParser.parse(tzinfo)
-        tzinfo = tzinfo if tzinfo is not None else dateutil_tz.tzutc()
 
         self._datetime = datetime(
             year, month, day, hour, minute, second, microsecond, tzinfo
@@ -84,7 +92,8 @@ class Arrow(object):
 
         """
 
-        tzinfo = tzinfo if tzinfo is not None else dateutil_tz.tzlocal()
+        if tzinfo is None:
+            tzinfo = dateutil_tz.tzlocal()
         dt = datetime.now(tzinfo)
 
         return cls(
@@ -138,7 +147,8 @@ class Arrow(object):
 
         """
 
-        tzinfo = tzinfo if tzinfo is not None else dateutil_tz.tzlocal()
+        if tzinfo is None:
+            tzinfo = dateutil_tz.tzlocal()
         timestamp = cls._get_timestamp_from_input(timestamp)
         dt = datetime.fromtimestamp(timestamp, tzinfo)
 
@@ -219,7 +229,8 @@ class Arrow(object):
         :param tzinfo: (optional) A :ref:`timezone expression <tz-expr>`.  Defaults to UTC.
         """
 
-        tzinfo = tzinfo if tzinfo is not None else dateutil_tz.tzutc()
+        if tzinfo is None:
+            tzinfo = dateutil_tz.tzutc()
 
         return cls(date.year, date.month, date.day, tzinfo=tzinfo)
 
@@ -241,7 +252,8 @@ class Arrow(object):
         """
 
         dt = datetime.strptime(date_str, fmt)
-        tzinfo = tzinfo if tzinfo is not None else dt.tzinfo
+        if tzinfo is None:
+            tzinfo = dt.tzinfo
 
         return cls(
             dt.year,
@@ -677,7 +689,7 @@ class Arrow(object):
 
         """
 
-        if not isinstance(tz, tzinfo):
+        if not isinstance(tz, dt_tzinfo):
             tz = parser.TzinfoParser.parse(tz)
 
         dt = self._datetime.astimezone(tz)
@@ -1286,7 +1298,7 @@ class Arrow(object):
 
         if tz_expr is None:
             return dateutil_tz.tzutc()
-        if isinstance(tz_expr, tzinfo):
+        if isinstance(tz_expr, dt_tzinfo):
             return tz_expr
         else:
             try:
diff --git a/arrow/factory.py b/arrow/factory.py
index 8cca6ac..aed4815 100644
--- a/arrow/factory.py
+++ b/arrow/factory.py
@@ -10,7 +10,8 @@ from __future__ import absolute_import
 
 import calendar
 import warnings
-from datetime import date, datetime, tzinfo
+from datetime import date, datetime
+from datetime import tzinfo as dt_tzinfo
 from time import struct_time
 
 from dateutil import tz as dateutil_tz
@@ -163,8 +164,13 @@ class ArrowFactory(object):
 
         # () -> now, @ utc.
         if arg_count == 0:
-            if isinstance(tz, tzinfo):
+            if isstr(tz):
+                tz = parser.TzinfoParser.parse(tz)
                 return self.type.now(tz)
+
+            if isinstance(tz, dt_tzinfo):
+                return self.type.now(tz)
+
             return self.type.utcnow()
 
         if arg_count == 1:
@@ -191,7 +197,7 @@ class ArrowFactory(object):
                 return self.type.fromdate(arg)
 
             # (tzinfo) -> now, @ tzinfo.
-            elif isinstance(arg, tzinfo):
+            elif isinstance(arg, dt_tzinfo):
                 return self.type.now(arg)
 
             # (str) -> parse.
@@ -220,7 +226,7 @@ class ArrowFactory(object):
             if isinstance(arg_1, datetime):
 
                 # (datetime, tzinfo/str) -> fromdatetime replace tzinfo.
-                if isinstance(arg_2, tzinfo) or isstr(arg_2):
+                if isinstance(arg_2, dt_tzinfo) or isstr(arg_2):
                     return self.type.fromdatetime(arg_1, arg_2)
                 else:
                     raise TypeError(
@@ -232,7 +238,7 @@ class ArrowFactory(object):
             elif isinstance(arg_1, date):
 
                 # (date, tzinfo/str) -> fromdate replace tzinfo.
-                if isinstance(arg_2, tzinfo) or isstr(arg_2):
+                if isinstance(arg_2, dt_tzinfo) or isstr(arg_2):
                     return self.type.fromdate(arg_1, tzinfo=arg_2)
                 else:
                     raise TypeError(
@@ -298,7 +304,7 @@ class ArrowFactory(object):
 
         if tz is None:
             tz = dateutil_tz.tzlocal()
-        elif not isinstance(tz, tzinfo):
+        elif not isinstance(tz, dt_tzinfo):
             tz = parser.TzinfoParser.parse(tz)
 
         return self.type.now(tz)
diff --git a/arrow/locales.py b/arrow/locales.py
index 81a7bad..4e540f0 100644
--- a/arrow/locales.py
+++ b/arrow/locales.py
@@ -1433,6 +1433,82 @@ class UkrainianLocale(SlavicBaseLocale):
     day_abbreviations = ["", "пн", "вт", "ср", "чт", "пт", "сб", "нд"]
 
 
+class MacedonianLocale(SlavicBaseLocale):
+    names = ["mk", "mk_mk"]
+
+    past = "пред {0}"
+    future = "за {0}"
+
+    timeframes = {
+        "now": "сега",
+        "seconds": "секунди",
+        "minute": "една минута",
+        "minutes": ["{0} минута", "{0} минути", "{0} минути"],
+        "hour": "еден саат",
+        "hours": ["{0} саат", "{0} саати", "{0} саати"],
+        "day": "еден ден",
+        "days": ["{0} ден", "{0} дена", "{0} дена"],
+        "month": "еден месец",
+        "months": ["{0} месец", "{0} месеци", "{0} месеци"],
+        "year": "една година",
+        "years": ["{0} година", "{0} години", "{0} години"],
+    }
+
+    meridians = {"am": "дп", "pm": "пп", "AM": "претпладне", "PM": "попладне"}
+
+    month_names = [
+        "",
+        "Јануари",
+        "Февруари",
+        "Март",
+        "Април",
+        "Мај",
+        "Јуни",
+        "Јули",
+        "Август",
+        "Септември",
+        "Октомври",
+        "Ноември",
+        "Декември",
+    ]
+    month_abbreviations = [
+        "",
+        "Јан.",
+        " Фев.",
+        " Мар.",
+        " Апр.",
+        " Мај",
+        " Јун.",
+        " Јул.",
+        " Авг.",
+        " Септ.",
+        " Окт.",
+        " Ноем.",
+        " Декем.",
+    ]
+
+    day_names = [
+        "",
+        "Понеделник",
+        " Вторник",
+        " Среда",
+        " Четврток",
+        " Петок",
+        " Сабота",
+        " Недела",
+    ]
+    day_abbreviations = [
+        "",
+        "Пон.",
+        " Вт.",
+        " Сре.",
+        " Чет.",
+        " Пет.",
+        " Саб.",
+        " Нед.",
+    ]
+
+
 class _DeutschLocaleCommonMixin(object):
 
     past = "vor {0}"
@@ -2843,82 +2919,6 @@ class FarsiLocale(Locale):
     day_abbreviations = ["", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
 
 
-class MacedonianLocale(Locale):
-    names = ["mk", "mk_mk"]
-
-    past = "пред {0}"
-    future = "за {0}"
-
-    timeframes = {
-        "now": "сега",
-        "seconds": "секунди",
-        "minute": "една минута",
-        "minutes": "{0} минути",
-        "hour": "еден саат",
-        "hours": "{0} саати",
-        "day": "еден ден",
-        "days": "{0} дена",
-        "month": "еден месец",
-        "months": "{0} месеци",
-        "year": "една година",
-        "years": "{0} години",
-    }
-
-    meridians = {"am": "дп", "pm": "пп", "AM": "претпладне", "PM": "попладне"}
-
-    month_names = [
-        "",
-        "Јануари",
-        "Февруари",
-        "Март",
-        "Април",
-        "Мај",
-        "Јуни",
-        "Јули",
-        "Август",
-        "Септември",
-        "Октомври",
-        "Ноември",
-        "Декември",
-    ]
-    month_abbreviations = [
-        "",
-        "Јан.",
-        " Фев.",
-        " Мар.",
-        " Апр.",
-        " Мај",
-        " Јун.",
-        " Јул.",
-        " Авг.",
-        " Септ.",
-        " Окт.",
-        " Ноем.",
-        " Декем.",
-    ]
-
-    day_names = [
-        "",
-        "Понеделник",
-        " Вторник",
-        " Среда",
-        " Четврток",
-        " Петок",
-        " Сабота",
-        " Недела",
-    ]
-    day_abbreviations = [
-        "",
-        "Пон.",
-        " Вт.",
-        " Сре.",
-        " Чет.",
-        " Пет.",
-        " Саб.",
-        " Нед.",
-    ]
-
-
 class HebrewLocale(Locale):
 
     names = ["he", "he_IL"]
diff --git a/requirements.txt b/requirements.txt
index ac54277..f5aa6da 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,9 +2,9 @@ backports.functools_lru_cache==1.5.0
 chai==1.1.2
 nose==1.3.7
 nose-cov==1.6
-pre-commit==1.17.0
-python-dateutil==2.8.0
-pytz==2019.1
-simplejson==3.16.0
-sphinx==1.8.5; python_version == '2.7'
-sphinx==2.1.2; python_version >= '3.5'
+pre-commit==1.18.*
+python-dateutil==2.8.*
+pytz==2019.*
+simplejson==3.16.*
+sphinx==1.8.*; python_version == '2.7'
+sphinx==2.1.*; python_version >= '3.5'


Test patch:
diff --git a/tests/arrow_tests.py b/tests/arrow_tests.py
index cd35786..87cfe6a 100644
--- a/tests/arrow_tests.py
+++ b/tests/arrow_tests.py
@@ -8,6 +8,7 @@ import time
 import warnings
 from datetime import date, datetime, timedelta
 
+import pytz
 import simplejson as json
 from chai import Chai
 from dateutil import tz
@@ -22,12 +23,58 @@ def assertDtEqual(dt1, dt2, within=10):
 
 
 class ArrowInitTests(Chai):
+    def test_init_bad_input(self):
+
+        with self.assertRaises(TypeError):
+            arrow.Arrow(2013)
+
+        with self.assertRaises(TypeError):
+            arrow.Arrow(2013, 2)
+
+        with self.assertRaises(ValueError):
+            arrow.Arrow(2013, 2, 2, 12, 30, 45, 9999999)
+
     def test_init(self):
 
+        result = arrow.Arrow(2013, 2, 2)
+        self.expected = datetime(2013, 2, 2, tzinfo=tz.tzutc())
+        self.assertEqual(result._datetime, self.expected)
+
+        result = arrow.Arrow(2013, 2, 2, 12)
+        self.expected = datetime(2013, 2, 2, 12, tzinfo=tz.tzutc())
+        self.assertEqual(result._datetime, self.expected)
+
+        result = arrow.Arrow(2013, 2, 2, 12, 30)
+        self.expected = datetime(2013, 2, 2, 12, 30, tzinfo=tz.tzutc())
+        self.assertEqual(result._datetime, self.expected)
+
+        result = arrow.Arrow(2013, 2, 2, 12, 30, 45)
+        self.expected = datetime(2013, 2, 2, 12, 30, 45, tzinfo=tz.tzutc())
+        self.assertEqual(result._datetime, self.expected)
+
         result = arrow.Arrow(2013, 2, 2, 12, 30, 45, 999999)
         self.expected = datetime(2013, 2, 2, 12, 30, 45, 999999, tzinfo=tz.tzutc())
+        self.assertEqual(result._datetime, self.expected)
 
+        result = arrow.Arrow(
+            2013, 2, 2, 12, 30, 45, 999999, tzinfo=tz.gettz("Europe/Paris")
+        )
+        self.expected = datetime(
+            2013, 2, 2, 12, 30, 45, 999999, tzinfo=tz.gettz("Europe/Paris")
+        )
+        self.assertEqual(result._datetime, self.expected)
+
+    # regression tests for issue #626
+    def test_init_pytz_timezone(self):
+
+        result = arrow.Arrow(
+            2013, 2, 2, 12, 30, 45, 999999, tzinfo=pytz.timezone("Europe/Paris")
+        )
+        self.expected = datetime(
+            2013, 2, 2, 12, 30, 45, 999999, tzinfo=tz.gettz("Europe/Paris")
+        )
         self.assertEqual(result._datetime, self.expected)
+        assertDtEqual(result._datetime, self.expected, 1)
 
 
 class ArrowFactoryTests(Chai):
@@ -48,9 +95,14 @@ class ArrowFactoryTests(Chai):
         timestamp = time.time()
 
         result = arrow.Arrow.fromtimestamp(timestamp)
-
         assertDtEqual(result._datetime, datetime.now().replace(tzinfo=tz.tzlocal()))
 
+        result = arrow.Arrow.fromtimestamp(timestamp, tzinfo=tz.gettz("Europe/Paris"))
+        assertDtEqual(
+            result._datetime,
+            datetime.fromtimestamp(timestamp, tz.gettz("Europe/Paris")),
+        )
+
     def test_fromdatetime(self):
 
         dt = datetime(2013, 2, 3, 12, 30, 45, 1)
@@ -90,11 +142,18 @@ class ArrowFactoryTests(Chai):
         formatted = datetime(2013, 2, 3, 12, 30, 45).strftime("%Y-%m-%d %H:%M:%S")
 
         result = arrow.Arrow.strptime(formatted, "%Y-%m-%d %H:%M:%S")
-
         self.assertEqual(
             result._datetime, datetime(2013, 2, 3, 12, 30, 45, tzinfo=tz.tzutc())
         )
 
+        result = arrow.Arrow.strptime(
+            formatted, "%Y-%m-%d %H:%M:%S", tzinfo=tz.gettz("Europe/Paris")
+        )
+        self.assertEqual(
+            result._datetime,
+            datetime(2013, 2, 3, 12, 30, 45, tzinfo=tz.gettz("Europe/Paris")),
+        )
+
 
 class ArrowRepresentationTests(Chai):
     def setUp(self):
diff --git a/tests/factory_tests.py b/tests/factory_tests.py
index acd4c36..3d75ffd 100644
--- a/tests/factory_tests.py
+++ b/tests/factory_tests.py
@@ -30,7 +30,7 @@ class GetTests(Chai):
 
         self.assertEqual(no_arg, one_arg)
 
-    def test_one_arg_non(self):
+    def test_one_arg_none(self):
 
         assertDtEqual(
             self.factory.get(None), datetime.utcnow().replace(tzinfo=tz.tzutc())
@@ -103,6 +103,16 @@ class GetTests(Chai):
 
         assertDtEqual(self.factory.get(tzinfo=tz.gettz("US/Pacific")), self.expected)
 
+    def test_kwarg_tzinfo_string(self):
+
+        self.expected = (
+            datetime.utcnow()
+            .replace(tzinfo=tz.tzutc())
+            .astimezone(tz.gettz("US/Pacific"))
+        )
+
+        assertDtEqual(self.factory.get(tzinfo="US/Pacific"), self.expected)
+
     def test_one_arg_iso_str(self):
 
         dt = datetime.utcnow()
diff --git a/tests/locales_tests.py b/tests/locales_tests.py
index c1a8912..0abf208 100644
--- a/tests/locales_tests.py
+++ b/tests/locales_tests.py
@@ -323,6 +323,35 @@ class BulgarianLocaleTests(Chai):
         self.assertEqual(locale._format_timeframe("minutes", 25), "25 минути")
 
 
+class MacedonianLocaleTests(Chai):
+    def test_plurals_mk(self):
+
+        locale = locales.MacedonianLocale()
+
+        # time
+        self.assertEqual(locale._format_relative("сега", "now", 0), "сега")
+
+        # Hours
+        self.assertEqual(locale._format_timeframe("hours", 0), "0 саати")
+        self.assertEqual(locale._format_timeframe("hours", 1), "1 саат")
+        self.assertEqual(locale._format_timeframe("hours", 2), "2 саати")
+        self.assertEqual(locale._format_timeframe("hours", 4), "4 саати")
+        self.assertEqual(locale._format_timeframe("hours", 5), "5 саати")
+        self.assertEqual(locale._format_timeframe("hours", 21), "21 саат")
+        self.assertEqual(locale._format_timeframe("hours", 22), "22 саати")
+        self.assertEqual(locale._format_timeframe("hours", 25), "25 саати")
+
+        # Minutes
+        self.assertEqual(locale._format_timeframe("minutes", 0), "0 минути")
+        self.assertEqual(locale._format_timeframe("minutes", 1), "1 минута")
+        self.assertEqual(locale._format_timeframe("minutes", 2), "2 минути")
+        self.assertEqual(locale._format_timeframe("minutes", 4), "4 минути")
+        self.assertEqual(locale._format_timeframe("minutes", 5), "5 минути")
+        self.assertEqual(locale._format_timeframe("minutes", 21), "21 минута")
+        self.assertEqual(locale._format_timeframe("minutes", 22), "22 минути")
+        self.assertEqual(locale._format_timeframe("minutes", 25), "25 минути")
+
+
 class HebrewLocaleTests(Chai):
     def test_couple_of_timeframe(self):
         locale = locales.HebrewLocale()