Repository: crsmithdev/arrow
Pull request #791 (2020-05-14 15:29:49)

Create built-in format strings for common formats
## Pull Request Checklist

Thank you for taking the time to improve Arrow! Before submitting your pull request, please check all *appropriate* boxes:

<!-- Check boxes by placing an x in the brackets: [x] -->
- [x] 🧪 Added **tests** for changed code.
- [x] 🛠️ All tests **pass** when run locally (run `tox` or `make test` to find out!).
- [x] 🧹 All linting checks **pass** when run locally (run `tox -e lint` or `make lint` to find out!).
- [ ] 📚 Updated **documentation** for changed code.
- [ ] ⏩ Code is **up-to-date** with the `master` branch.

If you have *any* questions about your code changes or any of the points above, please submit your questions along with the pull request and we will try our best to help!

## Description of Changes

replaces #770 
ref #762 



Patch:
diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml
index d299c16..24e7a9f 100644
--- a/.github/workflows/continuous_integration.yml
+++ b/.github/workflows/continuous_integration.yml
@@ -15,15 +15,13 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-      - uses: actions/checkout@v1
+      - uses: actions/checkout@v2
       - name: Set up Python 3.8
-        uses: actions/setup-python@v1
+        uses: actions/setup-python@v2
         with:
-          python-version: 3.8
+          python-version: "3.8"
       - name: Install dependencies
-        run: |
-          pip install -U pip
-          pip install tox
+        run: pip install -U tox
       - name: Linting code
         run: tox -e lint
       - name: Linting docs
@@ -33,70 +31,61 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: [2.7, 3.5, 3.6, 3.7, 3.8]
+        python-version: ["pypy3", "2.7", "3.5", "3.6", "3.7", "3.8"]
 
     steps:
-      - uses: actions/checkout@v1
+      - uses: actions/checkout@v2
       - name: Set up Python ${{ matrix.python-version }}
-        uses: actions/setup-python@v1
+        uses: actions/setup-python@v2
         with:
           python-version: ${{ matrix.python-version }}
       - name: Install dependencies
-        run: |
-          pip install -U pip
-          pip install tox tox-gh-actions
+        run: pip install -U tox tox-gh-actions
       - name: Test with tox
         run: tox
       - name: Upload coverage to Codecov
         uses: codecov/codecov-action@v1
         with:
-          token: ${{ secrets.CODECOV_TOKEN }}
           file: .coverage
 
   MacOS:
     runs-on: macos-latest
     strategy:
       matrix:
-        python-version: [2.7, 3.5, 3.6, 3.7, 3.8]
+        python-version: ["pypy3", "2.7", "3.5", "3.6", "3.7", "3.8"]
 
     steps:
-      - uses: actions/checkout@v1
+      - uses: actions/checkout@v2
       - name: Set up Python ${{ matrix.python-version }}
-        uses: actions/setup-python@v1
+        uses: actions/setup-python@v2
         with:
           python-version: ${{ matrix.python-version }}
       - name: Install dependencies
-        run: |
-          pip install -U pip
-          pip install tox tox-gh-actions
+        run: pip install -U tox tox-gh-actions
       - name: Test with tox
         run: tox
       - name: Upload coverage to Codecov
         uses: codecov/codecov-action@v1
         with:
-          token: ${{ secrets.CODECOV_TOKEN }}
           file: .coverage
 
   Windows:
     runs-on: windows-latest
     strategy:
       matrix:
-        python-version: [2.7, 3.5, 3.6, 3.7, 3.8]
+        python-version: ["pypy3", "2.7", "3.5", "3.6", "3.7", "3.8"]
 
     steps:
-      - uses: actions/checkout@v1
+      - uses: actions/checkout@v2
       - name: Set up Python ${{ matrix.python-version }}
-        uses: actions/setup-python@v1
+        uses: actions/setup-python@v2
         with:
           python-version: ${{ matrix.python-version }}
       - name: Install dependencies
-        run: |
-          pip install -U pip
-          pip install tox tox-gh-actions
+        run: pip install -U tox tox-gh-actions
       - name: Test with tox
         run: tox
       - name: Upload coverage to Codecov
         uses: codecov/codecov-action@v1
         with:
-          token: ${{ secrets.CODECOV_TOKEN }}
           file: .coverage
diff --git a/.travis.yml b/.travis.yml
index 688c7ec..cfa2895 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -26,7 +26,7 @@ matrix:
       os: windows
       language: shell # 'language: python' is an error on Travis CI Windows
       before_install:
-        - choco install python --version 3.7.5
+        - choco install python --version 3.7.7
         - python -m pip install --upgrade pip
       env:
         - PATH=/c/Python37:/c/Python37/Scripts:$PATH
@@ -34,6 +34,9 @@ matrix:
     - name: "Python 3.8"
       python: "3.8"
       env: TOXENV=py38
+    - name: "Python 3.9"
+      python: "3.9-dev"
+      env: TOXENV=py39
     - name: "Linting"
       python: "3.7"
       env: TOXENV=lint,docs
diff --git a/Makefile b/Makefile
index 8c5c9c3..894a6ba 100644
--- a/Makefile
+++ b/Makefile
@@ -7,8 +7,9 @@ build35: PYTHON_VER = python3.5
 build36: PYTHON_VER = python3.6
 build37: PYTHON_VER = python3.7
 build38: PYTHON_VER = python3.8
+build39: PYTHON_VER = python3.9
 
-build27 build35 build36 build37 build38: clean
+build27 build35 build36 build37 build38 build39: clean
 	virtualenv venv --python=$(PYTHON_VER)
 	. venv/bin/activate; \
 	pip install -r requirements.txt; \
diff --git a/README.rst b/README.rst
index 00f3065..e31ce60 100644
--- a/README.rst
+++ b/README.rst
@@ -47,7 +47,7 @@ Features
 --------
 
 - Fully-implemented, drop-in replacement for datetime
-- Supports Python 2.7, 3.5, 3.6, 3.7 and 3.8
+- Supports Python 2.7, 3.5, 3.6, 3.7, 3.8 and 3.9
 - Timezone-aware and UTC by default
 - Provides super-simple creation options for many common input scenarios
 - :code:`shift` method with support for relative offsets, including weeks
diff --git a/arrow/__init__.py b/arrow/__init__.py
index d99a70a..2883527 100644
--- a/arrow/__init__.py
+++ b/arrow/__init__.py
@@ -3,4 +3,16 @@ from ._version import __version__
 from .api import get, now, utcnow
 from .arrow import Arrow
 from .factory import ArrowFactory
+from .formatter import (
+    FORMAT_ATOM,
+    FORMAT_COOKIE,
+    FORMAT_RFC822,
+    FORMAT_RFC850,
+    FORMAT_RFC1036,
+    FORMAT_RFC1123,
+    FORMAT_RFC2822,
+    FORMAT_RFC3339,
+    FORMAT_RSS,
+    FORMAT_W3C,
+)
 from .parser import ParserError
diff --git a/arrow/formatter.py b/arrow/formatter.py
index 325c393..9f9d7a4 100644
--- a/arrow/formatter.py
+++ b/arrow/formatter.py
@@ -8,6 +8,17 @@ from dateutil import tz as dateutil_tz
 
 from arrow import locales, util
 
+FORMAT_ATOM = "YYYY-MM-DD HH:mm:ssZZ"
+FORMAT_COOKIE = "dddd, DD-MMM-YYYY HH:mm:ss ZZZ"
+FORMAT_RFC822 = "ddd, DD MMM YY HH:mm:ss Z"
+FORMAT_RFC850 = "dddd, DD-MMM-YY HH:mm:ss ZZZ"
+FORMAT_RFC1036 = "ddd, DD MMM YY HH:mm:ss Z"
+FORMAT_RFC1123 = "ddd, DD MMM YYYY HH:mm:ss Z"
+FORMAT_RFC2822 = "ddd, DD MMM YYYY HH:mm:ss Z"
+FORMAT_RFC3339 = "YYYY-MM-DD HH:mm:ssZZ"
+FORMAT_RSS = "ddd, DD MMM YYYY HH:mm:ss Z"
+FORMAT_W3C = "YYYY-MM-DD HH:mm:ssZZ"
+
 
 class DateTimeFormatter(object):
 
diff --git a/arrow/locales.py b/arrow/locales.py
index b57e3ba..91a9a55 100644
--- a/arrow/locales.py
+++ b/arrow/locales.py
@@ -478,8 +478,8 @@ class SpanishLocale(Locale):
         return "{}º".format(n)
 
 
-class FrenchLocale(Locale):
-    names = ["fr", "fr_fr"]
+class FrenchBaseLocale(Locale):
+
     past = "il y a {0}"
     future = "dans {0}"
     and_word = "et"
@@ -517,21 +517,6 @@ class FrenchLocale(Locale):
         "novembre",
         "décembre",
     ]
-    month_abbreviations = [
-        "",
-        "janv",
-        "févr",
-        "mars",
-        "avr",
-        "mai",
-        "juin",
-        "juil",
-        "août",
-        "sept",
-        "oct",
-        "nov",
-        "déc",
-    ]
 
     day_names = [
         "",
@@ -555,6 +540,48 @@ class FrenchLocale(Locale):
         return "{}e".format(n)
 
 
+class FrenchLocale(FrenchBaseLocale, Locale):
+
+    names = ["fr", "fr_fr"]
+
+    month_abbreviations = [
+        "",
+        "janv",
+        "févr",
+        "mars",
+        "avr",
+        "mai",
+        "juin",
+        "juil",
+        "août",
+        "sept",
+        "oct",
+        "nov",
+        "déc",
+    ]
+
+
+class FrenchCanadianLocale(FrenchBaseLocale, Locale):
+
+    names = ["fr_ca"]
+
+    month_abbreviations = [
+        "",
+        "janv",
+        "févr",
+        "mars",
+        "avr",
+        "mai",
+        "juin",
+        "juill",
+        "août",
+        "sept",
+        "oct",
+        "nov",
+        "déc",
+    ]
+
+
 class GreekLocale(Locale):
 
     names = ["el", "el_gr"]
@@ -1947,75 +1974,6 @@ class PortugueseLocale(Locale):
         "years": "{0} anos",
     }
 
-    month_names = [
-        "",
-        "janeiro",
-        "fevereiro",
-        "março",
-        "abril",
-        "maio",
-        "junho",
-        "julho",
-        "agosto",
-        "setembro",
-        "outubro",
-        "novembro",
-        "dezembro",
-    ]
-    month_abbreviations = [
-        "",
-        "jan",
-        "fev",
-        "mar",
-        "abr",
-        "maio",
-        "jun",
-        "jul",
-        "ago",
-        "set",
-        "out",
-        "nov",
-        "dez",
-    ]
-
-    day_names = [
-        "",
-        "segunda-feira",
-        "terça-feira",
-        "quarta-feira",
-        "quinta-feira",
-        "sexta-feira",
-        "sábado",
-        "domingo",
-    ]
-    day_abbreviations = ["", "seg", "ter", "qua", "qui", "sex", "sab", "dom"]
-
-
-class BrazilianPortugueseLocale(PortugueseLocale):
-    names = ["pt_br"]
-
-    past = "faz {0}"
-
-    future = "em {0}"
-
-    timeframes = {
-        "now": "agora",
-        "second": "um segundo",
-        "seconds": "{0} segundos",
-        "minute": "um minuto",
-        "minutes": "{0} minutos",
-        "hour": "uma hora",
-        "hours": "{0} horas",
-        "day": "um dia",
-        "days": "{0} dias",
-        "week": "uma semana",
-        "weeks": "{0} semanas",
-        "month": "um mês",
-        "months": "{0} meses",
-        "year": "um ano",
-        "years": "{0} anos",
-    }
-
     month_names = [
         "",
         "Janeiro",
@@ -2060,6 +2018,12 @@ class BrazilianPortugueseLocale(PortugueseLocale):
     day_abbreviations = ["", "Seg", "Ter", "Qua", "Qui", "Sex", "Sab", "Dom"]
 
 
+class BrazilianPortugueseLocale(PortugueseLocale):
+    names = ["pt_br"]
+
+    past = "faz {0}"
+
+
 class TagalogLocale(Locale):
 
     names = ["tl", "tl_ph"]
@@ -2948,14 +2912,16 @@ class SlovakLocale(Locale):
 
     timeframes = {
         "now": "Teraz",
-        "second": {"past": "druhý", "future": "druhý", "zero": "druhý"},
-        "seconds": {"past": "pár sekundami", "future": ["{0} sekundy", "{0} sekúnd"]},
+        "second": {"past": "sekundou", "future": "sekundu", "zero": "{0} sekúnd"},
+        "seconds": {"past": "{0} sekundami", "future": ["{0} sekundy", "{0} sekúnd"]},
         "minute": {"past": "minútou", "future": "minútu", "zero": "{0} minút"},
         "minutes": {"past": "{0} minútami", "future": ["{0} minúty", "{0} minút"]},
         "hour": {"past": "hodinou", "future": "hodinu", "zero": "{0} hodín"},
         "hours": {"past": "{0} hodinami", "future": ["{0} hodiny", "{0} hodín"]},
         "day": {"past": "dňom", "future": "deň", "zero": "{0} dní"},
         "days": {"past": "{0} dňami", "future": ["{0} dni", "{0} dní"]},
+        "week": {"past": "týždňom", "future": "týždeň", "zero": "{0} týždňov"},
+        "weeks": {"past": "{0} týždňami", "future": ["{0} týždne", "{0} týždňov"]},
         "month": {"past": "mesiacom", "future": "mesiac", "zero": "{0} mesiacov"},
         "months": {"past": "{0} mesiacmi", "future": ["{0} mesiace", "{0} mesiacov"]},
         "year": {"past": "rokom", "future": "rok", "zero": "{0} rokov"},
@@ -3111,6 +3077,7 @@ class HebrewLocale(Locale):
 
     past = "לפני {0}"
     future = "בעוד {0}"
+    and_word = "ו"
 
     timeframes = {
         "now": "הרגע",
@@ -3124,6 +3091,9 @@ class HebrewLocale(Locale):
         "day": "יום",
         "days": "{0} ימים",
         "2-days": "יומיים",
+        "week": "שבוע",
+        "weeks": "{0} שבועות",
+        "2-weeks": "שבועיים",
         "month": "חודש",
         "months": "{0} חודשים",
         "2-months": "חודשיים",
@@ -3176,10 +3146,41 @@ class HebrewLocale(Locale):
     def _format_timeframe(self, timeframe, delta):
         """Hebrew couple of <timeframe> aware"""
         couple = "2-{}".format(timeframe)
+        single = timeframe.rstrip("s")
         if abs(delta) == 2 and couple in self.timeframes:
-            return self.timeframes[couple].format(abs(delta))
+            key = couple
+        elif abs(delta) == 1 and single in self.timeframes:
+            key = single
         else:
-            return self.timeframes[timeframe].format(abs(delta))
+            key = timeframe
+
+        return self.timeframes[key].format(trunc(abs(delta)))
+
+    def describe_multi(self, timeframes, only_distance=False):
+        """ Describes a delta within multiple timeframes in plain language.
+        In Hebrew, the and word behaves a bit differently.
+
+        :param timeframes: a list of string, quantity pairs each representing a timeframe and delta.
+        :param only_distance: return only distance eg: "2 hours and 11 seconds" without "in" or "ago" keywords
+        """
+
+        humanized = ""
+        for index, (timeframe, delta) in enumerate(timeframes):
+            last_humanized = self._format_timeframe(timeframe, delta)
+            if index == 0:
+                humanized = last_humanized
+            elif index == len(timeframes) - 1:  # Must have at least 2 items
+                humanized += " " + self.and_word
+                if last_humanized[0].isdecimal():
+                    humanized += "־"
+                humanized += last_humanized
+            else:  # Don't add for the last one
+                humanized += ", " + last_humanized
+
+        if not only_distance:
+            humanized = self._format_relative(humanized, timeframe, delta)
+
+        return humanized
 
 
 class MarathiLocale(Locale):
@@ -4138,4 +4139,89 @@ class EstonianLocale(Locale):
         return form.format(abs(delta))
 
 
+class SwahiliLocale(Locale):
+
+    names = [
+        "sw",
+        "sw_ke",
+        "sw_tz",
+    ]
+
+    past = "{0} iliyopita"
+    future = "muda wa {0}"
+    and_word = "na"
+
+    timeframes = {
+        "now": "sasa hivi",
+        "second": "sekunde",
+        "seconds": "sekunde {0}",
+        "minute": "dakika moja",
+        "minutes": "dakika {0}",
+        "hour": "saa moja",
+        "hours": "saa {0}",
+        "day": "siku moja",
+        "days": "siku {0}",
+        "week": "wiki moja",
+        "weeks": "wiki {0}",
+        "month": "mwezi moja",
+        "months": "miezi {0}",
+        "year": "mwaka moja",
+        "years": "miaka {0}",
+    }
+
+    meridians = {"am": "asu", "pm": "mch", "AM": "ASU", "PM": "MCH"}
+
+    month_names = [
+        "",
+        "Januari",
+        "Februari",
+        "Machi",
+        "Aprili",
+        "Mei",
+        "Juni",
+        "Julai",
+        "Agosti",
+        "Septemba",
+        "Oktoba",
+        "Novemba",
+        "Desemba",
+    ]
+    month_abbreviations = [
+        "",
+        "Jan",
+        "Feb",
+        "Mac",
+        "Apr",
+        "Mei",
+        "Jun",
+        "Jul",
+        "Ago",
+        "Sep",
+        "Okt",
+        "Nov",
+        "Des",
+    ]
+
+    day_names = [
+        "",
+        "Jumatatu",
+        "Jumanne",
+        "Jumatano",
+        "Alhamisi",
+        "Ijumaa",
+        "Jumamosi",
+        "Jumapili",
+    ]
+    day_abbreviations = [
+        "",
+        "Jumatatu",
+        "Jumanne",
+        "Jumatano",
+        "Alhamisi",
+        "Ijumaa",
+        "Jumamosi",
+        "Jumapili",
+    ]
+
+
 _locales = _map_locales()
diff --git a/arrow/parser.py b/arrow/parser.py
index 0f932b5..c9e17b1 100644
--- a/arrow/parser.py
+++ b/arrow/parser.py
@@ -417,9 +417,9 @@ class DateTimeParser(object):
 
             if expanded_timestamp > MAX_TIMESTAMP:
                 if expanded_timestamp < MAX_TIMESTAMP_MS:
-                    expanded_timestamp /= 1000.0
+                    expanded_timestamp /= 1e3
                 elif expanded_timestamp < MAX_TIMESTAMP_US:
-                    expanded_timestamp /= 1000000.0
+                    expanded_timestamp /= 1e6
                 else:
                     raise ValueError(
                         "The specified timestamp '{}' is too large.".format(
diff --git a/docs/index.rst b/docs/index.rst
index 95f659b..6f5c8bc 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -408,6 +408,35 @@ Use the following tokens for parsing and formatting. Note that they are **not**
 .. [#t4] timezone names from `tz database <https://www.iana.org/time-zones>`_ provided via dateutil package
 .. [#t5] this token cannot be used for parsing timestamps out of natural language strings due to compatibility reasons
 
+Built-in Formats
+++++++++++++++++
+
+There are several formatting standards that are provided as built-in tokens.
+
+.. code-block:: python
+
+    >>> arw = arrow.utcnow()
+    >>> arw.format(arrow.FORMAT_ATOM)
+    '2020-05-27 10:30:35+00:00'
+    >>> arw.format(arrow.FORMAT_COOKIE)
+    'Wednesday, 27-May-2020 10:30:35 UTC'
+    >>> arw.format(arrow.FORMAT_RSS)
+    'Wed, 27 May 2020 10:30:35 +0000'
+    >>> arw.format(arrow.FORMAT_RFC822)
+    'Wed, 27 May 20 10:30:35 +0000'
+    >>> arw.format(arrow.FORMAT_RFC850)
+    'Wednesday, 27-May-20 10:30:35 UTC'
+    >>> arw.format(arrow.FORMAT_RFC1036)
+    'Wed, 27 May 20 10:30:35 +0000'
+    >>> arw.format(arrow.FORMAT_RFC1123)
+    'Wed, 27 May 2020 10:30:35 +0000'
+    >>> arw.format(arrow.FORMAT_RFC2822)
+    'Wed, 27 May 2020 10:30:35 +0000'
+     >>> arw.format(arrow.FORMAT_RFC3339)
+    '2020-05-27 10:30:35+00:00'
+     >>> arw.format(arrow.FORMAT_W3C)
+    '2020-05-27 10:30:35+00:00'
+
 Escaping Formats
 ~~~~~~~~~~~~~~~~
 
diff --git a/setup.py b/setup.py
index a60250a..3f6e433 100644
--- a/setup.py
+++ b/setup.py
@@ -39,8 +39,9 @@ setup(
         "Programming Language :: Python :: 3.6",
         "Programming Language :: Python :: 3.7",
         "Programming Language :: Python :: 3.8",
+        "Programming Language :: Python :: 3.9",
     ],
-    keywords="arrow date time datetime timestamp timezone",
+    keywords="arrow date time datetime timestamp timezone humanize",
     project_urls={
         "Repository": "https://github.com/crsmithdev/arrow",
         "Bug Reports": "https://github.com/crsmithdev/arrow/issues",
diff --git a/tox.ini b/tox.ini
index 9b89588..7538e71 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,14 +1,16 @@
 [tox]
-envlist = py{27,35,36,37,38},lint,docs
+envlist = py{py3,27,35,36,37,38,39},lint,docs
 skip_missing_interpreters = true
 
 [gh-actions]
 python =
+    pypy3: pypy3
     2.7: py27
     3.5: py35
     3.6: py36
     3.7: py37
     3.8: py38
+    3.9: py39
 
 # Configure testing environments
 


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 7b53743..5bc8a4a 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -2,6 +2,7 @@
 from datetime import datetime
 
 import pytest
+from dateutil import tz as dateutil_tz
 
 from arrow import arrow, factory, formatter, locales, parser
 
@@ -29,6 +30,14 @@ def time_2013_02_15(request):
     request.cls.arrow = arrow.Arrow.fromdatetime(request.cls.datetime)
 
 
+@pytest.fixture(scope="class")
+def time_1975_12_25(request):
+    request.cls.datetime = datetime(
+        1975, 12, 25, 14, 15, 16, tzinfo=dateutil_tz.gettz("America/New_York")
+    )
+    request.cls.arrow = arrow.Arrow.fromdatetime(request.cls.datetime)
+
+
 @pytest.fixture(scope="class")
 def arrow_formatter(request):
     request.cls.formatter = formatter.DateTimeFormatter()
diff --git a/tests/test_formatter.py b/tests/test_formatter.py
index 9f52318..ee3a6be 100644
--- a/tests/test_formatter.py
+++ b/tests/test_formatter.py
@@ -5,6 +5,19 @@ import pytest
 import pytz
 from dateutil import tz as dateutil_tz
 
+from arrow import (
+    FORMAT_ATOM,
+    FORMAT_COOKIE,
+    FORMAT_RFC822,
+    FORMAT_RFC850,
+    FORMAT_RFC1036,
+    FORMAT_RFC1123,
+    FORMAT_RFC2822,
+    FORMAT_RFC3339,
+    FORMAT_RSS,
+    FORMAT_W3C,
+)
+
 from .utils import make_full_tz_list
 
 
@@ -204,3 +217,66 @@ class TestDateTimeFormatterFormatToken:
 
         # Escaping is atomic: brackets inside brackets are treated literally
         assert self.formatter.format(datetime(1, 1, 1), "[[[ ]]") == "[[ ]"
+
+
+@pytest.mark.usefixtures("arrow_formatter", "time_1975_12_25")
+class TestDateTimeFormatterBuiltinFormats:
+    def test_atom(self):
+        assert (
+            self.formatter.format(self.datetime, FORMAT_ATOM)
+            == "1975-12-25 14:15:16-05:00"
+        )
+
+    def test_cookie(self):
+        assert (
+            self.formatter.format(self.datetime, FORMAT_COOKIE)
+            == "Thursday, 25-Dec-1975 14:15:16 EST"
+        )
+
+    def test_rfc_822(self):
+        assert (
+            self.formatter.format(self.datetime, FORMAT_RFC822)
+            == "Thu, 25 Dec 75 14:15:16 -0500"
+        )
+
+    def test_rfc_850(self):
+        assert (
+            self.formatter.format(self.datetime, FORMAT_RFC850)
+            == "Thursday, 25-Dec-75 14:15:16 EST"
+        )
+
+    def test_rfc_1036(self):
+        assert (
+            self.formatter.format(self.datetime, FORMAT_RFC1036)
+            == "Thu, 25 Dec 75 14:15:16 -0500"
+        )
+
+    def test_rfc_1123(self):
+        assert (
+            self.formatter.format(self.datetime, FORMAT_RFC1123)
+            == "Thu, 25 Dec 1975 14:15:16 -0500"
+        )
+
+    def test_rfc_2822(self):
+        assert (
+            self.formatter.format(self.datetime, FORMAT_RFC2822)
+            == "Thu, 25 Dec 1975 14:15:16 -0500"
+        )
+
+    def test_rfc3339(self):
+        assert (
+            self.formatter.format(self.datetime, FORMAT_RFC3339)
+            == "1975-12-25 14:15:16-05:00"
+        )
+
+    def test_rss(self):
+        assert (
+            self.formatter.format(self.datetime, FORMAT_RSS)
+            == "Thu, 25 Dec 1975 14:15:16 -0500"
+        )
+
+    def test_w3c(self):
+        assert (
+            self.formatter.format(self.datetime, FORMAT_W3C)
+            == "1975-12-25 14:15:16-05:00"
+        )
diff --git a/tests/test_locales.py b/tests/test_locales.py
index 0e749a8..14f5d7d 100644
--- a/tests/test_locales.py
+++ b/tests/test_locales.py
@@ -205,6 +205,15 @@ class TestFrenchLocale:
         assert self.locale.ordinal_number(1) == "1er"
         assert self.locale.ordinal_number(2) == "2e"
 
+    def test_month_abbreviation(self):
+        assert "juil" in self.locale.month_abbreviations
+
+
+@pytest.mark.usefixtures("lang_locale")
+class TestFrenchCanadianLocale:
+    def test_month_abbreviation(self):
+        assert "juill" in self.locale.month_abbreviations
+
 
 @pytest.mark.usefixtures("lang_locale")
 class TestRussianLocale:
@@ -372,11 +381,62 @@ class TestCzechLocale:
 class TestSlovakLocale:
     def test_format_timeframe(self):
 
+        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("second", 1) == "sekundu"
+        assert self.locale._format_timeframe("seconds", 2) == "2 sekundy"
+        assert self.locale._format_timeframe("seconds", 5) == "5 sekúnd"
+
+        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("minute", 1) == "minútu"
+        assert self.locale._format_timeframe("minutes", 2) == "2 minúty"
+        assert self.locale._format_timeframe("minutes", 5) == "5 minút"
+
+        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("hour", 1) == "hodinu"
         assert self.locale._format_timeframe("hours", 2) == "2 hodiny"
         assert self.locale._format_timeframe("hours", 5) == "5 hodín"
-        assert self.locale._format_timeframe("hour", 0) == "0 hodín"
-        assert self.locale._format_timeframe("hours", -2) == "2 hodinami"
-        assert self.locale._format_timeframe("hours", -5) == "5 hodinami"
+
+        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("day", 1) == "deň"
+        assert self.locale._format_timeframe("days", 2) == "2 dni"
+        assert self.locale._format_timeframe("days", 5) == "5 dní"
+
+        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("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"
+
+        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("month", 1) == "mesiac"
+        assert self.locale._format_timeframe("months", 2) == "2 mesiace"
+        assert self.locale._format_timeframe("months", 5) == "5 mesiacov"
+
+        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("year", 1) == "rok"
+        assert self.locale._format_timeframe("years", 2) == "2 roky"
+        assert self.locale._format_timeframe("years", 5) == "5 rokov"
+
         assert self.locale._format_timeframe("now", 0) == "Teraz"
 
     def test_format_relative_now(self):
@@ -445,18 +505,50 @@ class TestMacedonianLocale:
         assert self.locale._format_timeframe("minutes", 25) == "25 минути"
 
 
+@pytest.mark.usefixtures("time_2013_01_01")
 @pytest.mark.usefixtures("lang_locale")
 class TestHebrewLocale:
     def test_couple_of_timeframe(self):
-        assert self.locale._format_timeframe("hours", 2) == "שעתיים"
-        assert self.locale._format_timeframe("months", 2) == "חודשיים"
+        assert self.locale._format_timeframe("days", 1) == "יום"
         assert self.locale._format_timeframe("days", 2) == "יומיים"
-        assert self.locale._format_timeframe("years", 2) == "שנתיים"
+        assert self.locale._format_timeframe("days", 3) == "3 ימים"
+
+        assert self.locale._format_timeframe("hours", 1) == "שעה"
+        assert self.locale._format_timeframe("hours", 2) == "שעתיים"
         assert self.locale._format_timeframe("hours", 3) == "3 שעות"
+
+        assert self.locale._format_timeframe("week", 1) == "שבוע"
+        assert self.locale._format_timeframe("weeks", 2) == "שבועיים"
+        assert self.locale._format_timeframe("weeks", 3) == "3 שבועות"
+
+        assert self.locale._format_timeframe("months", 1) == "חודש"
+        assert self.locale._format_timeframe("months", 2) == "חודשיים"
         assert self.locale._format_timeframe("months", 4) == "4 חודשים"
-        assert self.locale._format_timeframe("days", 3) == "3 ימים"
+
+        assert self.locale._format_timeframe("years", 1) == "שנה"
+        assert self.locale._format_timeframe("years", 2) == "שנתיים"
         assert self.locale._format_timeframe("years", 5) == "5 שנים"
 
+    def test_describe_multi(self):
+        describe = self.locale.describe_multi
+
+        fulltest = [("years", 5), ("weeks", 1), ("hours", 1), ("minutes", 6)]
+        assert describe(fulltest) == "בעוד 5 שנים, שבוע, שעה ו־6 דקות"
+        seconds4000_0days = [("days", 0), ("hours", 1), ("minutes", 6)]
+        assert describe(seconds4000_0days) == "בעוד 0 ימים, שעה ו־6 דקות"
+        seconds4000 = [("hours", 1), ("minutes", 6)]
+        assert describe(seconds4000) == "בעוד שעה ו־6 דקות"
+        assert describe(seconds4000, only_distance=True) == "שעה ו־6 דקות"
+        seconds3700 = [("hours", 1), ("minutes", 1)]
+        assert describe(seconds3700) == "בעוד שעה ודקה"
+        seconds300_0hours = [("hours", 0), ("minutes", 5)]
+        assert describe(seconds300_0hours) == "בעוד 0 שעות ו־5 דקות"
+        seconds300 = [("minutes", 5)]
+        assert describe(seconds300) == "בעוד 5 דקות"
+        seconds60 = [("minutes", 1)]
+        assert describe(seconds60) == "בעוד דקה"
+        assert describe(seconds60, only_distance=True) == "דקה"
+
 
 @pytest.mark.usefixtures("lang_locale")
 class TestMarathiLocale:
@@ -822,6 +914,7 @@ class TestBrazilianPortugueseLocale:
         assert self.locale._format_timeframe("months", 11) == "11 meses"
         assert self.locale._format_timeframe("year", 1) == "um ano"
         assert self.locale._format_timeframe("years", 12) == "12 anos"
+        assert self.locale._format_relative("uma hora", "hour", -1) == "faz uma hora"
 
 
 @pytest.mark.usefixtures("lang_locale")
@@ -862,3 +955,41 @@ class TestChineseTWLocale:
         assert self.locale._format_timeframe("months", 11) == "11個月"
         assert self.locale._format_timeframe("year", 1) == "1年"
         assert self.locale._format_timeframe("years", 12) == "12年"
+
+
+@pytest.mark.usefixtures("lang_locale")
+class TestSwahiliLocale:
+    def test_format_timeframe(self):
+        assert self.locale._format_timeframe("now", 0) == "sasa hivi"
+        assert self.locale._format_timeframe("second", 1) == "sekunde"
+        assert self.locale._format_timeframe("seconds", 3) == "sekunde 3"
+        assert self.locale._format_timeframe("seconds", 30) == "sekunde 30"
+        assert self.locale._format_timeframe("minute", 1) == "dakika moja"
+        assert self.locale._format_timeframe("minutes", 4) == "dakika 4"
+        assert self.locale._format_timeframe("minutes", 40) == "dakika 40"
+        assert self.locale._format_timeframe("hour", 1) == "saa moja"
+        assert self.locale._format_timeframe("hours", 5) == "saa 5"
+        assert self.locale._format_timeframe("hours", 23) == "saa 23"
+        assert self.locale._format_timeframe("day", 1) == "siku moja"
+        assert self.locale._format_timeframe("days", 6) == "siku 6"
+        assert self.locale._format_timeframe("days", 12) == "siku 12"
+        assert self.locale._format_timeframe("month", 1) == "mwezi moja"
+        assert self.locale._format_timeframe("months", 7) == "miezi 7"
+        assert self.locale._format_timeframe("week", 1) == "wiki moja"
+        assert self.locale._format_timeframe("weeks", 2) == "wiki 2"
+        assert self.locale._format_timeframe("months", 11) == "miezi 11"
+        assert self.locale._format_timeframe("year", 1) == "mwaka moja"
+        assert self.locale._format_timeframe("years", 8) == "miaka 8"
+        assert self.locale._format_timeframe("years", 12) == "miaka 12"
+
+    def test_format_relative_now(self):
+        result = self.locale._format_relative("sasa hivi", "now", 0)
+        assert result == "sasa hivi"
+
+    def test_format_relative_past(self):
+        result = self.locale._format_relative("saa moja", "hour", 1)
+        assert result == "muda wa saa moja"
+
+    def test_format_relative_future(self):
+        result = self.locale._format_relative("saa moja", "hour", -1)
+        assert result == "saa moja iliyopita"