def humanize(
        self,
        other: Union["Arrow", dt_datetime, None] = None,
        locale: str = DEFAULT_LOCALE,
        only_distance: bool = False,
        granularity: Union[_GRANULARITY, List[_GRANULARITY]] = "auto",
    ) -> str:
        """Returns a localized, humanized representation of a relative difference in time.

        :param other: (optional) an :class:`Arrow <arrow.arrow.Arrow>` or ``datetime`` object.
            Defaults to now in the current :class:`Arrow <arrow.arrow.Arrow>` object's timezone.
        :param locale: (optional) a ``str`` specifying a locale.  Defaults to 'en-us'.
        :param only_distance: (optional) returns only time difference eg: "11 seconds" without "in" or "ago" part.
        :param granularity: (optional) defines the precision of the output. Set it to strings 'second', 'minute',
                           'hour', 'day', 'week', 'month' or 'year' or a list of any combination of these strings

        Usage::

            >>> earlier = arrow.utcnow().shift(hours=-2)
            >>> earlier.humanize()
            '2 hours ago'

            >>> later = earlier.shift(hours=4)
            >>> later.humanize(earlier)
            'in 4 hours'

        """

        locale_name = locale
        locale = locales.get_locale(locale)

        if other is None:
            utc = dt_datetime.now(timezone.utc).replace(tzinfo=dateutil_tz.tzutc())
            dt = utc.astimezone(self._datetime.tzinfo)

        elif isinstance(other, Arrow):
            dt = other._datetime

        elif isinstance(other, dt_datetime):
            if other.tzinfo is None:
                dt = other.replace(tzinfo=self._datetime.tzinfo)
            else:
                dt = other.astimezone(self._datetime.tzinfo)

        else:
            raise TypeError(
                f"Invalid 'other' argument of type {type(other).__name__!r}. "
                "Argument must be of type None, Arrow, or datetime."
            )

        if isinstance(granularity, list) and len(granularity) == 1:
            granularity = granularity[0]

        _delta = int(round((self._datetime - dt).total_seconds()))
        sign = -1 if _delta < 0 else 1
        delta_second = diff = abs(_delta)

        try:
            if granularity == "auto":
                if diff < 10:
                    return locale.describe("now", only_distance=only_distance)

                if diff < self._SECS_PER_MINUTE:
                    seconds = sign * delta_second
                    return locale.describe(
                        "seconds", seconds, only_distance=only_distance
                    )

                elif diff < self._SECS_PER_MINUTE * 2:
                    return locale.describe("minute", sign, only_distance=only_distance)
                elif diff < self._SECS_PER_HOUR:
                    minutes = sign * max(delta_second // self._SECS_PER_MINUTE, 2)
                    return locale.describe(
                        "minutes", minutes, only_distance=only_distance
                    )

                elif diff < self._SECS_PER_HOUR * 2:
                    return locale.describe("hour", sign, only_distance=only_distance)
                elif diff < self._SECS_PER_DAY:
                    hours = sign * max(delta_second // self._SECS_PER_HOUR, 2)
                    return locale.describe("hours", hours, only_distance=only_distance)
                elif diff < self._SECS_PER_DAY * 2:
                    return locale.describe("day", sign, only_distance=only_distance)
                elif diff < self._SECS_PER_WEEK:
                    days = sign * max(delta_second // self._SECS_PER_DAY, 2)
                    return locale.describe("days", days, only_distance=only_distance)

                elif diff < self._SECS_PER_WEEK * 2:
                    return locale.describe("week", sign, only_distance=only_distance)
                elif diff < self._SECS_PER_MONTH:
                    weeks = sign * max(delta_second // self._SECS_PER_WEEK, 2)
                    return locale.describe("weeks", weeks, only_distance=only_distance)

                elif diff < self._SECS_PER_MONTH * 2:
                    return locale.describe("month", sign, only_distance=only_distance)
                elif diff < self._SECS_PER_YEAR:
                    # TODO revisit for humanization during leap years
                    self_months = self._datetime.year * 12 + self._datetime.month
                    other_months = dt.year * 12 + dt.month

                    months = sign * max(abs(other_months - self_months), 2)

                    return locale.describe(
                        "months", months, only_distance=only_distance
                    )

                elif diff < self._SECS_PER_YEAR * 2:
                    return locale.describe("year", sign, only_distance=only_distance)
                else:
                    years = sign * max(delta_second // self._SECS_PER_YEAR, 2)
                    return locale.describe("years", years, only_distance=only_distance)

            elif isinstance(granularity, str):
                granularity = cast(TimeFrameLiteral, granularity)  # type: ignore[assignment]

                if granularity == "second":
                    delta = sign * float(delta_second)
                    if abs(delta) < 2:
                        return locale.describe("now", only_distance=only_distance)
                elif granularity == "minute":
                    delta = sign * delta_second / self._SECS_PER_MINUTE
                elif granularity == "hour":
                    delta = sign * delta_second / self._SECS_PER_HOUR
                elif granularity == "day":
                    delta = sign * delta_second / self._SECS_PER_DAY
                elif granularity == "week":
                    delta = sign * delta_second / self._SECS_PER_WEEK
                elif granularity == "month":
                    delta = sign * delta_second / self._SECS_PER_MONTH
                elif granularity == "quarter":
                    delta = sign * delta_second / self._SECS_PER_QUARTER
                elif granularity == "year":
                    delta = sign * delta_second / self._SECS_PER_YEAR
                else:
                    raise ValueError(
                        "Invalid level of granularity. "
                        "Please select between 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter' or 'year'."
                    )

                if trunc(abs(delta)) != 1:
                    granularity += "s"  # type: ignore[assignment]
                return locale.describe(granularity, delta, only_distance=only_distance)

            else:
                if not granularity:
                    raise ValueError(
                        "Empty granularity list provided. "
                        "Please select one or more from 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter', 'year'."
                    )

                timeframes: List[Tuple[TimeFrameLiteral, float]] = []

                def gather_timeframes(_delta: float, _frame: TimeFrameLiteral) -> float:
                    if _frame in granularity:
                        value = sign * _delta / self._SECS_MAP[_frame]
                        _delta %= self._SECS_MAP[_frame]
                        if trunc(abs(value)) != 1:
                            timeframes.append(
                                (cast(TimeFrameLiteral, _frame + "s"), value)
                            )
                        else:
                            timeframes.append((_frame, value))
                    return _delta

                delta = float(delta_second)
                frames: Tuple[TimeFrameLiteral, ...] = (
                    "year",
                    "quarter",
                    "month",
                    "week",
                    "day",
                    "hour",
                    "minute",
                    "second",
                )
                for frame in frames:
                    delta = gather_timeframes(delta, frame)

                if len(timeframes) < len(granularity):
                    raise ValueError(
                        "Invalid level of granularity. "
                        "Please select between 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter' or 'year'."
                    )

                return locale.describe_multi(timeframes, only_distance=only_distance)

        except KeyError as e:
            raise ValueError(
                f"Humanization of the {e} granularity is not currently translated in the {locale_name!r} locale. "
                "Please consider making a contribution to this locale."
            )
Returns a localized, humanized representation of a relative difference in time.

        :param other: (optional) an :class:`Arrow <arrow.arrow.Arrow>` or ``datetime`` object.
            Defaults to now in the current :class:`Arrow <arrow.arrow.Arrow>` object's timezone.
        :param locale: (optional) a ``str`` specifying a locale.  Defaults to 'en-us'.
        :param only_distance: (optional) returns only time difference eg: "11 seconds" without "in" or "ago" part.
        :param granularity: (optional) defines the precision of the output. Set it to strings 'second', 'minute',
                           'hour', 'day', 'week', 'month' or 'year' or a list of any combination of these strings

        Usage::

            >>> earlier = arrow.utcnow().shift(hours=-2)
            >>> earlier.humanize()
            '2 hours ago'

            >>> later = earlier.shift(hours=4)
            >>> later.humanize(earlier)
            'in 4 hours'

        
arrow/arrow.py