Repository: crsmithdev/arrow
Pull request #748 (2020-01-02 22:23:41)

Correct error in humanize() granularities for single seconds
## 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] 📚 Updated **documentation** for changed code.
- [x] ⏩ 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: https://github.com/crsmithdev/arrow/pull/726
Closes: https://github.com/crsmithdev/arrow/issues/727
Closes: https://github.com/crsmithdev/arrow/issues/697
<!--
Replace with description of code changes.

If your PR has an associated issue, insert the issue number (e.g. #703) or directly link to the GitHub issue (e.g. https://github.com/crsmithdev/arrow/issues/703).

For example, doing the following will automatically close issue #703 when this PR is merged:

Closes: #703
-->



Patch:
diff --git a/arrow/arrow.py b/arrow/arrow.py
index aff3508..1bb2192 100644
--- a/arrow/arrow.py
+++ b/arrow/arrow.py
@@ -904,7 +904,12 @@ class Arrow(object):
                 dt = other.astimezone(self._datetime.tzinfo)
 
         else:
-            raise TypeError()
+            raise TypeError(
+                "Invalid 'other' argument of type '{}'. "
+                "Argument must be of type None, Arrow, or datetime.".format(
+                    type(other).__name__
+                )
+            )
 
         if isinstance(granularity, list) and len(granularity) == 1:
             granularity = granularity[0]
@@ -939,7 +944,8 @@ class Arrow(object):
                     hours = sign * int(max(delta / 3600, 2))
                     return locale.describe("hours", hours, only_distance=only_distance)
 
-                elif diff < 129600:
+                # anything less than 48 hours should be 1 day
+                elif diff < 172800:
                     return locale.describe("day", sign, only_distance=only_distance)
                 elif diff < 554400:
                     days = sign * int(max(delta / 86400, 2))
@@ -1033,17 +1039,20 @@ class Arrow(object):
 
                 if len(timeframes) < len(granularity):
                     raise AttributeError(
-                        "Invalid level of granularity. Please select between 'second', 'minute', 'hour', 'day', 'week', 'month' or 'year'"
+                        "Invalid level of granularity. "
+                        "Please select between 'second', 'minute', 'hour', 'day', 'week', 'month' or 'year'."
                     )
 
-                for index, (_, delta) in enumerate(timeframes):
-                    if trunc(abs(delta)) != 1:
-                        timeframes[index][0] += "s"
+                for tf in timeframes:
+                    # Make granularity plural if the delta is not equal to 1
+                    if trunc(abs(tf[1])) != 1:
+                        tf[0] += "s"
                 return locale.describe_multi(timeframes, only_distance=only_distance)
 
         except KeyError as e:
             raise ValueError(
-                "Humanization of the {} granularity is not currently translated in the '{}' locale. Please consider making a contribution to this locale.".format(
+                "Humanization of the {} granularity is not currently translated in the '{}' locale. "
+                "Please consider making a contribution to this locale.".format(
                     e, locale_name
                 )
             )
diff --git a/arrow/locales.py b/arrow/locales.py
index 2c12cd9..a42947f 100644
--- a/arrow/locales.py
+++ b/arrow/locales.py
@@ -32,6 +32,7 @@ class Locale(object):
 
     timeframes = {
         "now": "",
+        "second": "",
         "seconds": "",
         "minute": "",
         "minutes": "",
@@ -83,7 +84,7 @@ class Locale(object):
         """ Describes a delta within multiple timeframes in plain language.
 
         :param timeframes: a list of string, quantity pairs each representing a timeframe and delta.
-        :param only_distance: return only distance eg: "2 hours 11 seconds" without "in" or "ago" keywords
+        :param only_distance: return only distance eg: "2 hours and 11 seconds" without "in" or "ago" keywords
         """
 
         humanized = ""
@@ -225,7 +226,8 @@ class EnglishLocale(Locale):
 
     timeframes = {
         "now": "just now",
-        "seconds": "seconds",
+        "second": "a second",
+        "seconds": "{0} seconds",
         "minute": "a minute",
         "minutes": "{0} minutes",
         "hour": "an hour",
@@ -321,7 +323,8 @@ class ItalianLocale(Locale):
 
     timeframes = {
         "now": "adesso",
-        "seconds": "qualche secondo",
+        "second": "un secondo",
+        "seconds": "{0} qualche secondo",
         "minute": "un minuto",
         "minutes": "{0} minuti",
         "hour": "un'ora",
@@ -391,7 +394,8 @@ class SpanishLocale(Locale):
 
     timeframes = {
         "now": "ahora",
-        "seconds": "segundos",
+        "second": "un segundo",
+        "seconds": "{0} segundos",
         "minute": "un minuto",
         "minutes": "{0} minutos",
         "hour": "una hora",
@@ -465,7 +469,8 @@ class FrenchLocale(Locale):
 
     timeframes = {
         "now": "maintenant",
-        "seconds": "quelques secondes",
+        "second": "une seconde",
+        "seconds": "{0} quelques secondes",
         "minute": "une minute",
         "minutes": "{0} minutes",
         "hour": "une heure",
@@ -543,7 +548,8 @@ class GreekLocale(Locale):
 
     timeframes = {
         "now": "τώρα",
-        "seconds": "δευτερόλεπτα",
+        "second": "ένα δεύτερο",
+        "seconds": "{0} δευτερόλεπτα",
         "minute": "ένα λεπτό",
         "minutes": "{0} λεπτά",
         "hour": "μία ώρα",
@@ -609,7 +615,8 @@ class JapaneseLocale(Locale):
 
     timeframes = {
         "now": "現在",
-        "seconds": "数秒",
+        "second": "二番目の",
+        "seconds": "{0}数秒",
         "minute": "1分",
         "minutes": "{0}分",
         "hour": "1時間",
@@ -669,7 +676,8 @@ class SwedishLocale(Locale):
 
     timeframes = {
         "now": "just nu",
-        "seconds": "några sekunder",
+        "second": "en sekund",
+        "seconds": "{0} några sekunder",
         "minute": "en minut",
         "minutes": "{0} minuter",
         "hour": "en timme",
@@ -738,7 +746,8 @@ class FinnishLocale(Locale):
 
     timeframes = {
         "now": ["juuri nyt", "juuri nyt"],
-        "seconds": ["muutama sekunti", "muutaman sekunnin"],
+        "second": ["sekunti", "sekunti"],
+        "seconds": ["{0} muutama sekunti", "{0} muutaman sekunnin"],
         "minute": ["minuutti", "minuutin"],
         "minutes": ["{0} minuuttia", "{0} minuutin"],
         "hour": ["tunti", "tunnin"],
@@ -825,7 +834,8 @@ class ChineseCNLocale(Locale):
 
     timeframes = {
         "now": "刚才",
-        "seconds": "几秒",
+        "second": "一秒",
+        "seconds": "{0}几秒",
         "minute": "1分钟",
         "minutes": "{0}分钟",
         "hour": "1小时",
@@ -884,7 +894,8 @@ class ChineseTWLocale(Locale):
 
     timeframes = {
         "now": "剛才",
-        "seconds": "幾秒",
+        "second": "一秒",
+        "seconds": "{0}幾秒",
         "minute": "1分鐘",
         "minutes": "{0}分鐘",
         "hour": "1小時",
@@ -1001,7 +1012,8 @@ class KoreanLocale(Locale):
 
     timeframes = {
         "now": "지금",
-        "seconds": "몇 초",
+        "second": "두 번째",
+        "seconds": "{0}몇 초",
         "minute": "1분",
         "minutes": "{0}분",
         "hour": "1시간",
@@ -1059,7 +1071,8 @@ class DutchLocale(Locale):
 
     timeframes = {
         "now": "nu",
-        "seconds": "seconden",
+        "second": "een seconde",
+        "seconds": "{0} seconden",
         "minute": "een minuut",
         "minutes": "{0} minuten",
         "hour": "een uur",
@@ -1147,7 +1160,8 @@ class BelarusianLocale(SlavicBaseLocale):
 
     timeframes = {
         "now": "зараз",
-        "seconds": "некалькі секунд",
+        "second": "секунду",
+        "seconds": "{0} некалькі секунд",
         "minute": "хвіліну",
         "minutes": ["{0} хвіліну", "{0} хвіліны", "{0} хвілін"],
         "hour": "гадзіну",
@@ -1213,7 +1227,8 @@ class PolishLocale(SlavicBaseLocale):
 
     timeframes = {
         "now": "teraz",
-        "seconds": "kilka sekund",
+        "second": "sekunda",
+        "seconds": "{0} kilka sekund",
         "minute": "minutę",
         "minutes": ["{0} minut", "{0} minuty", "{0} minut"],
         "hour": "godzina",
@@ -1279,7 +1294,8 @@ class RussianLocale(SlavicBaseLocale):
 
     timeframes = {
         "now": "сейчас",
-        "seconds": "несколько секунд",
+        "second": "Второй",
+        "seconds": "{0} несколько секунд",
         "minute": "минуту",
         "minutes": ["{0} минуту", "{0} минуты", "{0} минут"],
         "hour": "час",
@@ -1347,7 +1363,8 @@ class AfrikaansLocale(Locale):
 
     timeframes = {
         "now": "nou",
-        "seconds": "sekondes",
+        "second": "n sekonde",
+        "seconds": "{0} sekondes",
         "minute": "minuut",
         "minutes": "{0} minute",
         "hour": "uur",
@@ -1413,7 +1430,8 @@ class BulgarianLocale(SlavicBaseLocale):
 
     timeframes = {
         "now": "сега",
-        "seconds": "няколко секунди",
+        "second": "секунда",
+        "seconds": "{0} няколко секунди",
         "minute": "минута",
         "minutes": ["{0} минута", "{0} минути", "{0} минути"],
         "hour": "час",
@@ -1479,7 +1497,8 @@ class UkrainianLocale(SlavicBaseLocale):
 
     timeframes = {
         "now": "зараз",
-        "seconds": "кілька секунд",
+        "second": "секунда",
+        "seconds": "{0} кілька секунд",
         "minute": "хвилину",
         "minutes": ["{0} хвилину", "{0} хвилини", "{0} хвилин"],
         "hour": "годину",
@@ -1544,7 +1563,8 @@ class MacedonianLocale(SlavicBaseLocale):
 
     timeframes = {
         "now": "сега",
-        "seconds": "секунди",
+        "second": "секунда",
+        "seconds": "{0} секунди",
         "minute": "една минута",
         "minutes": ["{0} минута", "{0} минути", "{0} минути"],
         "hour": "еден саат",
@@ -1620,7 +1640,8 @@ class DeutschBaseLocale(Locale):
 
     timeframes = {
         "now": "gerade eben",
-        "seconds": "Sekunden",
+        "second": "eine Sekunde",
+        "seconds": "{0} Sekunden",
         "minute": "einer Minute",
         "minutes": "{0} Minuten",
         "hour": "einer Stunde",
@@ -1740,7 +1761,8 @@ class NorwegianLocale(Locale):
 
     timeframes = {
         "now": "nå nettopp",
-        "seconds": "noen sekunder",
+        "second": "et sekund",
+        "seconds": "{0} noen sekunder",
         "minute": "ett minutt",
         "minutes": "{0} minutter",
         "hour": "en time",
@@ -1806,7 +1828,8 @@ class NewNorwegianLocale(Locale):
 
     timeframes = {
         "now": "no nettopp",
-        "seconds": "nokre sekund",
+        "second": "et sekund",
+        "seconds": "{0} nokre sekund",
         "minute": "ett minutt",
         "minutes": "{0} minutt",
         "hour": "ein time",
@@ -2010,7 +2033,8 @@ class TagalogLocale(Locale):
 
     timeframes = {
         "now": "ngayon lang",
-        "seconds": "segundo",
+        "second": "isang segundo",
+        "seconds": "{0} segundo",
         "minute": "isang minuto",
         "minutes": "{0} minuto",
         "hour": "isang oras",
@@ -2079,7 +2103,8 @@ class VietnameseLocale(Locale):
 
     timeframes = {
         "now": "hiện tại",
-        "seconds": "giây",
+        "second": "một giây",
+        "seconds": "{0} giây",
         "minute": "một phút",
         "minutes": "{0} phút",
         "hour": "một giờ",
@@ -2147,7 +2172,8 @@ class TurkishLocale(Locale):
 
     timeframes = {
         "now": "şimdi",
-        "seconds": "saniye",
+        "second": "bir saniye",
+        "seconds": "{0} saniye",
         "minute": "bir dakika",
         "minutes": "{0} dakika",
         "hour": "bir saat",
@@ -2213,7 +2239,8 @@ class AzerbaijaniLocale(Locale):
 
     timeframes = {
         "now": "indi",
-        "seconds": "saniyə",
+        "second": "saniyə",
+        "seconds": "{0} saniyə",
         "minute": "bir dəqiqə",
         "minutes": "{0} dəqiqə",
         "hour": "bir saat",
@@ -2297,6 +2324,7 @@ class ArabicLocale(Locale):
 
     timeframes = {
         "now": "الآن",
+        "second": "ثانية",
         "seconds": {"double": "ثانيتين", "ten": "{0} ثوان", "higher": "{0} ثانية"},
         "minute": "دقيقة",
         "minutes": {"double": "دقيقتين", "ten": "{0} دقائق", "higher": "{0} دقيقة"},
@@ -2521,7 +2549,8 @@ class IcelandicLocale(Locale):
 
     timeframes = {
         "now": "rétt í þessu",
-        "seconds": ("nokkrum sekúndum", "nokkrar sekúndur"),
+        "second": ("sekúndu", "sekúndu"),
+        "seconds": ("{0} nokkrum sekúndum", "nokkrar sekúndur"),
         "minute": ("einni mínútu", "eina mínútu"),
         "minutes": ("{0} mínútum", "{0} mínútur"),
         "hour": ("einum tíma", "einn tíma"),
@@ -2590,7 +2619,8 @@ class DanishLocale(Locale):
 
     timeframes = {
         "now": "lige nu",
-        "seconds": "et par sekunder",
+        "second": "et sekund",
+        "seconds": "{0} et par sekunder",
         "minute": "et minut",
         "minutes": "{0} minutter",
         "hour": "en time",
@@ -2656,7 +2686,8 @@ class MalayalamLocale(Locale):
 
     timeframes = {
         "now": "ഇപ്പോൾ",
-        "seconds": "സെക്കന്റ്‌",
+        "second": "ഒരു നിമിഷം",
+        "seconds": "{0} സെക്കന്റ്‌",
         "minute": "ഒരു മിനിറ്റ്",
         "minutes": "{0} മിനിറ്റ്",
         "hour": "ഒരു മണിക്കൂർ",
@@ -2729,7 +2760,8 @@ class HindiLocale(Locale):
 
     timeframes = {
         "now": "अभी",
-        "seconds": "सेकंड्",
+        "second": "एक पल",
+        "seconds": "{0} सेकंड्",
         "minute": "एक मिनट ",
         "minutes": "{0} मिनट ",
         "hour": "एक घंटा",
@@ -2793,6 +2825,7 @@ class CzechLocale(Locale):
 
     timeframes = {
         "now": "Teď",
+        "second": {"past": "vteřina", "future": "vteřina", "zero": "vteřina"},
         "seconds": {"past": "{0} sekundami", "future": ["{0} sekundy", "{0} sekund"]},
         "minute": {"past": "minutou", "future": "minutu", "zero": "{0} minut"},
         "minutes": {"past": "{0} minutami", "future": ["{0} minuty", "{0} minut"]},
@@ -2879,6 +2912,7 @@ class SlovakLocale(Locale):
 
     timeframes = {
         "now": "Teraz",
+        "second": {"past": "druhý", "future": "druhý", "zero": "druhý"},
         "seconds": {"past": "pár 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"]},
@@ -2970,7 +3004,8 @@ class FarsiLocale(Locale):
 
     timeframes = {
         "now": "اکنون",
-        "seconds": "ثانیه",
+        "second": "یک لحظه",
+        "seconds": "{0} ثانیه",
         "minute": "یک دقیقه",
         "minutes": "{0} دقیقه",
         "hour": "یک ساعت",
@@ -3043,7 +3078,8 @@ class HebrewLocale(Locale):
 
     timeframes = {
         "now": "הרגע",
-        "seconds": "שניות",
+        "second": "שנייה",
+        "seconds": "{0} שניות",
         "minute": "דקה",
         "minutes": "{0} דקות",
         "hour": "שעה",
@@ -3119,7 +3155,8 @@ class MarathiLocale(Locale):
 
     timeframes = {
         "now": "सद्य",
-        "seconds": "सेकंद",
+        "second": "एक सेकंद",
+        "seconds": "{0} सेकंद",
         "minute": "एक मिनिट ",
         "minutes": "{0} मिनिट ",
         "hour": "एक तास",
@@ -3198,7 +3235,8 @@ class CatalanLocale(Locale):
 
     timeframes = {
         "now": "Ara mateix",
-        "seconds": "segons",
+        "second": "un segon",
+        "seconds": "{0} segons",
         "minute": "1 minut",
         "minutes": "{0} minuts",
         "hour": "una hora",
@@ -3270,7 +3308,8 @@ class BasqueLocale(Locale):
 
     timeframes = {
         "now": "Orain",
-        "seconds": "segundu",
+        "second": "segundo bat",
+        "seconds": "{0} segundu",
         "minute": "minutu bat",
         "minutes": "{0} minutu",
         "hour": "ordu bat",
@@ -3335,7 +3374,8 @@ class HungarianLocale(Locale):
 
     timeframes = {
         "now": "éppen most",
-        "seconds": {"past": "másodpercekkel", "future": "pár másodperc"},
+        "second": {"past": "egy második", "future": "egy második"},
+        "seconds": {"past": "{0} másodpercekkel", "future": "{0} pár másodperc"},
         "minute": {"past": "egy perccel", "future": "egy perc"},
         "minutes": {"past": "{0} perccel", "future": "{0} perc"},
         "hour": {"past": "egy órával", "future": "egy óra"},
@@ -3412,7 +3452,8 @@ class EsperantoLocale(Locale):
 
     timeframes = {
         "now": "nun",
-        "seconds": "kelkaj sekundoj",
+        "second": "sekundo",
+        "seconds": "{0} kelkaj sekundoj",
         "minute": "unu minuto",
         "minutes": "{0} minutoj",
         "hour": "un horo",
@@ -3485,7 +3526,8 @@ class ThaiLocale(Locale):
 
     timeframes = {
         "now": "ขณะนี้",
-        "seconds": "ไม่กี่วินาที",
+        "second": "วินาที",
+        "seconds": "{0} ไม่กี่วินาที",
         "minute": "1 นาที",
         "minutes": "{0} นาที",
         "hour": "1 ชั่วโมง",
@@ -3565,7 +3607,8 @@ class BengaliLocale(Locale):
 
     timeframes = {
         "now": "এখন",
-        "seconds": "সেকেন্ড",
+        "second": "একটি দ্বিতীয়",
+        "seconds": "{0} সেকেন্ড",
         "minute": "এক মিনিট",
         "minutes": "{0} মিনিট",
         "hour": "এক ঘণ্টা",
@@ -3645,7 +3688,8 @@ class RomanshLocale(Locale):
 
     timeframes = {
         "now": "en quest mument",
-        "seconds": "secundas",
+        "second": "in secunda",
+        "seconds": "{0} secundas",
         "minute": "ina minuta",
         "minutes": "{0} minutas",
         "hour": "in'ura",
@@ -3713,7 +3757,8 @@ class SwissLocale(Locale):
 
     timeframes = {
         "now": "gerade eben",
-        "seconds": "Sekunden",
+        "second": "eine Sekunde",
+        "seconds": "{0} Sekunden",
         "minute": "einer Minute",
         "minutes": "{0} Minuten",
         "hour": "einer Stunde",
@@ -3783,7 +3828,8 @@ class RomanianLocale(Locale):
 
     timeframes = {
         "now": "acum",
-        "seconds": "câteva secunde",
+        "second": "o secunda",
+        "seconds": "{0} câteva secunde",
         "minute": "un minut",
         "minutes": "{0} minute",
         "hour": "o oră",
@@ -3849,7 +3895,8 @@ class SlovenianLocale(Locale):
 
     timeframes = {
         "now": "zdaj",
-        "seconds": "sekund",
+        "second": "sekundo",
+        "seconds": "{0} sekund",
         "minute": "minuta",
         "minutes": "{0} minutami",
         "hour": "uro",
@@ -3920,7 +3967,8 @@ class IndonesianLocale(Locale):
 
     timeframes = {
         "now": "baru saja",
-        "seconds": "detik",
+        "second": "1 sebentar",
+        "seconds": "{0} detik",
         "minute": "1 menit",
         "minutes": "{0} menit",
         "hour": "1 jam",
@@ -3989,7 +4037,8 @@ class NepaliLocale(Locale):
 
     timeframes = {
         "now": "अहिले",
-        "seconds": "सेकण्ड",
+        "second": "एक सेकेन्ड",
+        "seconds": "{0} सेकण्ड",
         "minute": "मिनेट",
         "minutes": "{0} मिनेट",
         "hour": "एक घण्टा",
diff --git a/docs/index.rst b/docs/index.rst
index 2122bce..c310f05 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -163,7 +163,7 @@ Format
 Convert
 ~~~~~~~
 
-Convert to timezones by name or tzinfo:
+Convert from UTC to other timezones by name or tzinfo:
 
 .. code-block:: python
 


Test patch:
diff --git a/tests/arrow_tests.py b/tests/arrow_tests.py
index 6214ada..9d68276 100644
--- a/tests/arrow_tests.py
+++ b/tests/arrow_tests.py
@@ -512,6 +512,58 @@ class ArrowDatetimeInterfaceTests(Chai):
         self.assertEqual(result, self.arrow._datetime.strftime("%Y"))
 
 
+class ArrowFalsePositiveDstTests(Chai):
+    """These tests relate to issues #376 and #551.
+    The key points in both issues are that arrow will assign a UTC timezone if none is provided and
+    .to() will change other attributes to be correct whereas .replace() only changes the specified attribute.
+
+    Issue 376
+    >>> arrow.get('2016-11-06').to('America/New_York').ceil('day')
+    < Arrow [2016-11-05T23:59:59.999999-04:00] >
+
+    Issue 551
+    >>> just_before = arrow.get('2018-11-04T01:59:59.999999')
+    >>> just_before
+    2018-11-04T01:59:59.999999+00:00
+    >>> just_after = just_before.shift(microseconds=1)
+    >>> just_after
+    2018-11-04T02:00:00+00:00
+    >>> just_before_eastern = just_before.replace(tzinfo='US/Eastern')
+    >>> just_before_eastern
+    2018-11-04T01:59:59.999999-04:00
+    >>> just_after_eastern = just_after.replace(tzinfo='US/Eastern')
+    >>> just_after_eastern
+    2018-11-04T02:00:00-05:00
+    """
+
+    def setUp(self):
+
+        super(ArrowFalsePositiveDstTests, self).setUp()
+        self.before_1 = arrow.Arrow(
+            2016, 11, 6, 3, 59, tzinfo=tz.gettz("America/New_York")
+        )
+        self.before_2 = arrow.Arrow(2016, 11, 6, tzinfo=tz.gettz("America/New_York"))
+        self.after_1 = arrow.Arrow(2016, 11, 6, 4, tzinfo=tz.gettz("America/New_York"))
+        self.after_2 = arrow.Arrow(
+            2016, 11, 6, 23, 59, tzinfo=tz.gettz("America/New_York")
+        )
+        self.before_3 = arrow.Arrow(
+            2018, 11, 4, 3, 59, tzinfo=tz.gettz("America/New_York")
+        )
+        self.before_4 = arrow.Arrow(2018, 11, 4, tzinfo=tz.gettz("America/New_York"))
+        self.after_3 = arrow.Arrow(2018, 11, 4, 4, tzinfo=tz.gettz("America/New_York"))
+        self.after_4 = arrow.Arrow(
+            2018, 11, 4, 23, 59, tzinfo=tz.gettz("America/New_York")
+        )
+
+    def test_dst(self):
+
+        self.assertEqual(self.before_1.day, self.before_2.day)
+        self.assertEqual(self.after_1.day, self.after_2.day)
+        self.assertEqual(self.before_3.day, self.before_4.day)
+        self.assertEqual(self.after_3.day, self.after_4.day)
+
+
 class ArrowConversionTests(Chai):
     def test_to(self):
 
@@ -1492,10 +1544,10 @@ class ArrowHumanizeTests(Chai):
 
         later100 = self.now.shift(seconds=100)
         self.assertEqual(
-            self.now.humanize(later100, granularity="second"), "seconds ago"
+            self.now.humanize(later100, granularity="second"), "100 seconds ago"
         )
         self.assertEqual(
-            later100.humanize(self.now, granularity="second"), "in seconds"
+            later100.humanize(self.now, granularity="second"), "in 100 seconds"
         )
         self.assertEqual(
             self.now.humanize(later100, granularity="minute"), "a minute ago"
@@ -1594,7 +1646,7 @@ class ArrowHumanizeTests(Chai):
         self.assertEqual(self.now.humanize(granularity=["second"]), "just now")
         self.assertEqual(
             self.now.humanize(granularity=["year", "month", "day", "hour", "second"]),
-            "in 0 years 0 months 0 days 0 hours and seconds",
+            "in 0 years 0 months 0 days 0 hours and 0 seconds",
         )
 
         later4000 = self.now.shift(seconds=4000)
@@ -1646,18 +1698,31 @@ class ArrowHumanizeTests(Chai):
             self.now.humanize(
                 later108onlydistance, only_distance=True, granularity=["year", "second"]
             ),
-            "3 years and seconds",
+            "3 years and 5327200 seconds",
+        )
+
+        one_min_one_sec_ago = self.now.shift(minutes=-1, seconds=-1)
+        self.assertEqual(
+            one_min_one_sec_ago.humanize(self.now, granularity=["minute", "second"]),
+            "a minute and a second ago",
+        )
+
+        one_min_two_secs_ago = self.now.shift(minutes=-1, seconds=-2)
+        self.assertEqual(
+            one_min_two_secs_ago.humanize(self.now, granularity=["minute", "second"]),
+            "a minute and 2 seconds ago",
         )
 
     def test_seconds(self):
 
         later = self.now.shift(seconds=10)
 
-        self.assertEqual(self.now.humanize(later), "seconds ago")
-        self.assertEqual(later.humanize(self.now), "in seconds")
+        # regression test for issue #727
+        self.assertEqual(self.now.humanize(later), "10 seconds ago")
+        self.assertEqual(later.humanize(self.now), "in 10 seconds")
 
-        self.assertEqual(self.now.humanize(later, only_distance=True), "seconds")
-        self.assertEqual(later.humanize(self.now, only_distance=True), "seconds")
+        self.assertEqual(self.now.humanize(later, only_distance=True), "10 seconds")
+        self.assertEqual(later.humanize(self.now, only_distance=True), "10 seconds")
 
     def test_minute(self):
 
@@ -1706,6 +1771,23 @@ class ArrowHumanizeTests(Chai):
         self.assertEqual(self.now.humanize(later), "a day ago")
         self.assertEqual(later.humanize(self.now), "in a day")
 
+        # regression test for issue #697
+        less_than_48_hours = self.now.shift(
+            days=1, hours=23, seconds=59, microseconds=999999
+        )
+        self.assertEqual(self.now.humanize(less_than_48_hours), "a day ago")
+        self.assertEqual(less_than_48_hours.humanize(self.now), "in a day")
+
+        less_than_48_hours_date = less_than_48_hours._datetime.date()
+        with self.assertRaises(TypeError):
+            # humanize other argument does not take raw datetime.date objects
+            self.now.humanize(less_than_48_hours_date)
+
+        # convert from date to arrow object
+        less_than_48_hours_date = arrow.Arrow.fromdate(less_than_48_hours_date)
+        self.assertEqual(self.now.humanize(less_than_48_hours_date), "a day ago")
+        self.assertEqual(less_than_48_hours_date.humanize(self.now), "in a day")
+
         self.assertEqual(self.now.humanize(later, only_distance=True), "a day")
         self.assertEqual(later.humanize(self.now, only_distance=True), "a day")
 
@@ -1870,7 +1952,7 @@ class ArrowHumanizeTestsWithLocale(Chai):
 
         result = arw.humanize(self.datetime, locale="ru")
 
-        self.assertEqual(result, "через несколько секунд")
+        self.assertEqual(result, "через 44 несколько секунд")
 
     def test_years(self):
 
diff --git a/tests/locales_tests.py b/tests/locales_tests.py
index 83e5664..af205b4 100644
--- a/tests/locales_tests.py
+++ b/tests/locales_tests.py
@@ -153,9 +153,9 @@ class SpanishLocalesTests(Chai):
     def test_format_timeframe(self):
         locale = locales.SpanishLocale()
         self.assertEqual(locale._format_timeframe("now", 0), "ahora")
-        self.assertEqual(locale._format_timeframe("seconds", 1), "segundos")
-        self.assertEqual(locale._format_timeframe("seconds", 3), "segundos")
-        self.assertEqual(locale._format_timeframe("seconds", 30), "segundos")
+        self.assertEqual(locale._format_timeframe("seconds", 1), "1 segundos")
+        self.assertEqual(locale._format_timeframe("seconds", 3), "3 segundos")
+        self.assertEqual(locale._format_timeframe("seconds", 30), "30 segundos")
         self.assertEqual(locale._format_timeframe("minute", 1), "un minuto")
         self.assertEqual(locale._format_timeframe("minutes", 4), "4 minutos")
         self.assertEqual(locale._format_timeframe("minutes", 40), "40 minutos")
@@ -176,9 +176,9 @@ class SpanishLocalesTests(Chai):
         self.assertEqual(locale._format_timeframe("years", 12), "12 años")
 
         self.assertEqual(locale._format_timeframe("now", 0), "ahora")
-        self.assertEqual(locale._format_timeframe("seconds", -1), "segundos")
-        self.assertEqual(locale._format_timeframe("seconds", -9), "segundos")
-        self.assertEqual(locale._format_timeframe("seconds", -12), "segundos")
+        self.assertEqual(locale._format_timeframe("seconds", -1), "1 segundos")
+        self.assertEqual(locale._format_timeframe("seconds", -9), "9 segundos")
+        self.assertEqual(locale._format_timeframe("seconds", -12), "12 segundos")
         self.assertEqual(locale._format_timeframe("minute", -1), "un minuto")
         self.assertEqual(locale._format_timeframe("minutes", -2), "2 minutos")
         self.assertEqual(locale._format_timeframe("minutes", -10), "10 minutos")
@@ -805,7 +805,7 @@ class TagalogLocaleTests(Chai):
         self.assertEqual(self.locale._format_timeframe("month", 1), "isang buwan")
         self.assertEqual(self.locale._format_timeframe("year", 1), "isang taon")
 
-        self.assertEqual(self.locale._format_timeframe("seconds", 2), "segundo")
+        self.assertEqual(self.locale._format_timeframe("seconds", 2), "2 segundo")
         self.assertEqual(self.locale._format_timeframe("minutes", 3), "3 minuto")
         self.assertEqual(self.locale._format_timeframe("hours", 4), "4 oras")
         self.assertEqual(self.locale._format_timeframe("months", 5), "5 buwan")