Repository: sdispater/pendulum
Pull request #716 (2023-06-25 02:36:04)

Replace inspect.stack() with traceback.extract_stack() to solve performance bottleneck
Use traceback.extract_stack() instead of inspect.stack() to check upper stack method, they are at same functionality, but the traceback(5μs) is 400x faster than inspect.stack(2ms). This method affects many common operation, such as add() and from_timestamp(tz), 2ms delay typically cause performance issues.
This method was available since early py 2.x

EDIT: the performance diff is not 50x, but 400x

## Performance
### before
```python
%%timeit
inspect.stack()[1][3]
# 2.11 ms ± 14.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%%timeit
pendulum.from_timestamp(1687658855, "Asia/Shanghai")
# 2.21 ms ± 63 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
```
### after
```python
%%timeit
traceback.extract_stack(limit=2)[0].name
# 3.7 µs ± 73.5 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)

%%timeit
pendulum.from_timestamp(1687658855, "Asia/Shanghai")
# 8.45 µs ± 142 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
```

## Calling Result Demo
```python
import inspect
import traceback

def call1(f):
    f()
def call2(f):
    call1(f)

def old():
    print("old return is->", inspect.stack()[1][3])
def new():
    print("new return is->", traceback.extract_stack(limit=2)[0].name)


call2(old)
call2(new)
```
The output is
```
old return is-> call1
new return is-> call1
```

## Pull Request Check List

- [ x ] Added **tests** for changed code. 
   No new tests required
- [ x ] Updated **documentation** for changed code.
   No new document required



Patch:
diff --git a/.flake8 b/.flake8
deleted file mode 100644
index 1d7308c..0000000
--- a/.flake8
+++ /dev/null
@@ -1,29 +0,0 @@
-[flake8]
-max-line-length = 88
-per-file-ignores =
-    # F401:	Module imported but unused
-    # TC001: Move import into type checking block
-    __init__.py:F401, TC001
-    pendulum/date.py:E800
-    # F811:	Redefinition of unused name from line n
-    pendulum/tz/timezone.py:F811, E800
-min_python_version = 3.7.0
-ban-relative-imports = true
-# flake8-use-fstring: https://github.com/MichaelKim0407/flake8-use-fstring#--percent-greedy-and---format-greedy
-format-greedy = 1
-inline-quotes = double
-enable-extensions = TC, TC1
-type-checking-exempt-modules = typing, typing-extensions
-eradicate-whitelist-extend = ^-.*;
-extend-ignore =
-    # E501:	Line too long
-    E501,
-    # E203:	Whitespace before ':'
-    E203,
-    # SIM106: Handle error-cases first
-    SIM106,
-extend-exclude =
-    # External to the project's coding standards:
-    docs/*,
-    # Machine-generated, too many false-positives
-    pendulum/locales/*,
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 2ee2fd4..2974fac 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -3,7 +3,7 @@ ci:
 
 repos:
   - repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v4.3.0
+    rev: v4.4.0
     hooks:
       - id: trailing-whitespace
         exclude: ^tests/.*/fixtures/.*
@@ -11,50 +11,12 @@ repos:
         exclude: ^tests/.*/fixtures/.*
       - id: debug-statements
 
-  - repo: https://github.com/hadialqattan/pycln
-    rev: v2.1.1
-    hooks:
-      - id: pycln
-        args: [ --all ]
-
   - repo: https://github.com/psf/black
-    rev: 22.10.0
+    rev: 23.3.0
     hooks:
       - id: black
 
-  - repo: https://github.com/pycqa/isort
-    rev: 5.12.0
-    hooks:
-      - id: isort
-        args: [ --add-import, from __future__ import annotations, --lines-after-imports, "-1" ]
-
-  - repo: https://github.com/pycqa/flake8
-    rev: 5.0.4
-    hooks:
-      - id: flake8
-        additional_dependencies: &flake8_deps
-          - flake8-broken-line==0.5.0
-          - flake8-bugbear==22.7.1
-          - flake8-comprehensions==3.10.0
-          - flake8-eradicate==1.3.0
-          - flake8-quotes==3.3.1
-          - flake8-simplify==0.19.2
-          - flake8-tidy-imports==4.8.0
-          - flake8-type-checking==2.1.2
-          - flake8-typing-imports==1.12.0
-          - flake8-use-fstring==1.3
-          - pep8-naming==0.13.1
-
-  - repo: https://github.com/asottile/yesqa
-    rev: v1.4.0
-    hooks:
-      - id: yesqa
-        additional_dependencies: *flake8_deps
-
-  - repo: https://github.com/asottile/pyupgrade
-    rev: v3.2.0
+  - repo: https://github.com/charliermarsh/ruff-pre-commit
+    rev: v0.0.270
     hooks:
-      - id: pyupgrade
-        exclude: ^build\.py$
-        args:
-          - --py37-plus
+    -   id: ruff
diff --git a/build.py b/build.py
index 95e63e1..8a72beb 100644
--- a/build.py
+++ b/build.py
@@ -4,7 +4,7 @@ from pathlib import Path
 
 
 def meson(*args):
-    subprocess.call(["meson"] + list(args))
+    subprocess.call(["meson", *list(args)])
 
 
 def _build():
diff --git a/clock b/clock
index 17f35a3..3c21a16 100755
--- a/clock
+++ b/clock
@@ -47,7 +47,6 @@ class _LambdaCompiler(_GettextCompiler):
 
 
 class LocaleCreate(Command):
-
     name = "locale create"
     description = "Creates locale translations."
 
@@ -229,7 +228,6 @@ translations = {{}}
 
 
 class LocaleRecreate(Command):
-
     name = "locale recreate"
     description = "Recreate existing locales."
 
@@ -244,7 +242,6 @@ class LocaleRecreate(Command):
 
 
 class WindowsTzDump(Command):
-
     name = "windows dump-timezones"
     description = "Dumps the mapping of Windows timezones to IANA timezones."
 
diff --git a/pendulum/__init__.py b/pendulum/__init__.py
index 1f89551..9c019c0 100644
--- a/pendulum/__init__.py
+++ b/pendulum/__init__.py
@@ -47,6 +47,7 @@ from pendulum.tz import timezones
 from pendulum.tz.timezone import FixedTimezone
 from pendulum.tz.timezone import Timezone
 
+
 _TEST_NOW: DateTime | None = None
 _LOCALE = "en"
 _WEEK_STARTS_AT = MONDAY
@@ -105,7 +106,7 @@ def _safe_timezone(
             obj = obj.key
         # pytz
         elif hasattr(obj, "localize"):
-            obj = obj.zone  # type: ignore
+            obj = obj.zone  # type: ignore[attr-defined]
         elif obj.tzname(None) == "UTC":
             return UTC
         else:
diff --git a/pendulum/__version__.py b/pendulum/__version__.py
index 29e86a8..0f8ddaa 100644
--- a/pendulum/__version__.py
+++ b/pendulum/__version__.py
@@ -1,1 +1,4 @@
+from __future__ import annotations
+
+
 __version__ = "3.0.0a"
diff --git a/pendulum/_extensions/helpers.py b/pendulum/_extensions/helpers.py
index e0b16a3..4dd4c72 100644
--- a/pendulum/_extensions/helpers.py
+++ b/pendulum/_extensions/helpers.py
@@ -354,6 +354,6 @@ def _get_tzinfo_name(tzinfo: datetime.tzinfo | None) -> str | None:
         return cast(Timezone, tzinfo).name
     elif hasattr(tzinfo, "zone"):
         # pytz timezone
-        return tzinfo.zone  # type: ignore
+        return tzinfo.zone  # type: ignore[no-any-return]
 
     return None
diff --git a/pendulum/constants.py b/pendulum/constants.py
index a3d2a18..c7b3876 100644
--- a/pendulum/constants.py
+++ b/pendulum/constants.py
@@ -1,6 +1,7 @@
 # The day constants
 from __future__ import annotations
 
+
 SUNDAY = 0
 MONDAY = 1
 TUESDAY = 2
diff --git a/pendulum/date.py b/pendulum/date.py
index 0d8382a..0fe776c 100644
--- a/pendulum/date.py
+++ b/pendulum/date.py
@@ -30,6 +30,7 @@ from pendulum.helpers import add_duration
 from pendulum.interval import Interval
 from pendulum.mixins.default import FormattableMixin
 
+
 if TYPE_CHECKING:
     from typing_extensions import Self
     from typing_extensions import SupportsIndex
@@ -187,8 +188,9 @@ class Date(FormattableMixin, date):
         return (self.month, self.day) == (instance.month, instance.day)
 
     # the additional method for checking if today is the anniversary day
-    # the alias is provided to start using a new name and keep the backward compatibility
-    # the old name can be completely replaced with the new in one of the future versions
+    # the alias is provided to start using a new name and keep the backward
+    # compatibility the old name can be completely replaced with the new in
+    # one of the future versions
     is_birthday = is_anniversary
 
     # ADDITIONS AND SUBTRACTIONS
@@ -516,7 +518,8 @@ class Date(FormattableMixin, date):
         Returns an instance set to the first occurrence
         of a given day of the week in the current unit.
         If no day_of_week is provided, modify to the first day of the unit.
-        Use the supplied consts to indicate the desired day_of_week, ex. pendulum.MONDAY.
+        Use the supplied consts to indicate the desired day_of_week,
+        ex. pendulum.MONDAY.
 
         Supported units are month, quarter and year.
 
@@ -533,7 +536,8 @@ class Date(FormattableMixin, date):
         Returns an instance set to the last occurrence
         of a given day of the week in the current unit.
         If no day_of_week is provided, modify to the last day of the unit.
-        Use the supplied consts to indicate the desired day_of_week, ex. pendulum.MONDAY.
+        Use the supplied consts to indicate the desired day_of_week,
+        ex. pendulum.MONDAY.
 
         Supported units are month, quarter and year.
 
diff --git a/pendulum/datetime.py b/pendulum/datetime.py
index d265aec..67c1e92 100644
--- a/pendulum/datetime.py
+++ b/pendulum/datetime.py
@@ -3,6 +3,7 @@ from __future__ import annotations
 import calendar
 import datetime
 import sys
+import traceback
 
 from typing import TYPE_CHECKING
 from typing import Any
@@ -41,6 +42,7 @@ from pendulum.tz import local_timezone
 from pendulum.tz.timezone import FixedTimezone
 from pendulum.tz.timezone import Timezone
 
+
 if TYPE_CHECKING:
     from typing_extensions import Literal
     from typing_extensions import Self
@@ -913,10 +915,7 @@ class DateTime(datetime.datetime, Date):
         if day_of_week < SUNDAY or day_of_week > SATURDAY:
             raise ValueError("Invalid day of week")
 
-        if keep_time:
-            dt = self
-        else:
-            dt = self.start_of("day")
+        dt = self if keep_time else self.start_of("day")
 
         dt = dt.add(days=1)
         while dt.day_of_week != day_of_week:
@@ -937,10 +936,7 @@ class DateTime(datetime.datetime, Date):
         if day_of_week < SUNDAY or day_of_week > SATURDAY:
             raise ValueError("Invalid day of week")
 
-        if keep_time:
-            dt = self
-        else:
-            dt = self.start_of("day")
+        dt = self if keep_time else self.start_of("day")
 
         dt = dt.subtract(days=1)
         while dt.day_of_week != day_of_week:
@@ -953,7 +949,8 @@ class DateTime(datetime.datetime, Date):
         Returns an instance set to the first occurrence
         of a given day of the week in the current unit.
         If no day_of_week is provided, modify to the first day of the unit.
-        Use the supplied consts to indicate the desired day_of_week, ex. DateTime.MONDAY.
+        Use the supplied consts to indicate the desired day_of_week,
+        ex. DateTime.MONDAY.
 
         Supported units are month, quarter and year.
         """
@@ -967,7 +964,8 @@ class DateTime(datetime.datetime, Date):
         Returns an instance set to the last occurrence
         of a given day of the week in the current unit.
         If no day_of_week is provided, modify to the last day of the unit.
-        Use the supplied consts to indicate the desired day_of_week, ex. DateTime.MONDAY.
+        Use the supplied consts to indicate the desired day_of_week,
+        ex. DateTime.MONDAY.
 
         Supported units are month, quarter and year.
         """
@@ -1221,9 +1219,7 @@ class DateTime(datetime.datetime, Date):
             # This is a workaround for Python 3.8+
             # since calling astimezone() will call this method
             # instead of the base datetime class one.
-            import inspect
-
-            caller = inspect.stack()[1][3]
+            caller = traceback.extract_stack(limit=2)[0].name
             if caller == "astimezone":
                 return super().__add__(other)
 
diff --git a/pendulum/duration.py b/pendulum/duration.py
index 2e9daf2..a07c9c7 100644
--- a/pendulum/duration.py
+++ b/pendulum/duration.py
@@ -13,6 +13,7 @@ from pendulum.constants import SECONDS_PER_MINUTE
 from pendulum.constants import US_PER_SECOND
 from pendulum.utils._compat import PYPY
 
+
 if TYPE_CHECKING:
     from typing_extensions import Self
 
@@ -376,7 +377,9 @@ class Duration(timedelta):
 
         usec = self._to_microseconds()
         if isinstance(other, timedelta):
-            return cast(int, usec // other._to_microseconds())  # type: ignore[attr-defined]
+            return cast(
+                int, usec // other._to_microseconds()  # type: ignore[attr-defined]
+            )
 
         if isinstance(other, int):
             return self.__class__(
@@ -401,7 +404,9 @@ class Duration(timedelta):
 
         usec = self._to_microseconds()
         if isinstance(other, timedelta):
-            return cast(float, usec / other._to_microseconds())  # type: ignore[attr-defined]
+            return cast(
+                float, usec / other._to_microseconds()  # type: ignore[attr-defined]
+            )
 
         if isinstance(other, int):
             return self.__class__(
@@ -427,7 +432,7 @@ class Duration(timedelta):
 
     def __mod__(self, other: timedelta) -> Self:
         if isinstance(other, timedelta):
-            r = self._to_microseconds() % other._to_microseconds()  # type: ignore[attr-defined]
+            r = self._to_microseconds() % other._to_microseconds()  # type: ignore[attr-defined] # noqa: E501
 
             return self.__class__(0, 0, r)
 
@@ -435,7 +440,7 @@ class Duration(timedelta):
 
     def __divmod__(self, other: timedelta) -> tuple[int, Duration]:
         if isinstance(other, timedelta):
-            q, r = divmod(self._to_microseconds(), other._to_microseconds())  # type: ignore[attr-defined]
+            q, r = divmod(self._to_microseconds(), other._to_microseconds())  # type: ignore[attr-defined] # noqa: E501
 
             return q, self.__class__(0, 0, r)
 
diff --git a/pendulum/exceptions.py b/pendulum/exceptions.py
index 3ab4db9..1687211 100644
--- a/pendulum/exceptions.py
+++ b/pendulum/exceptions.py
@@ -1,8 +1,13 @@
 from __future__ import annotations
 
-from .parsing.exceptions import ParserError  # noqa
+from pendulum.parsing.exceptions import ParserError
 
 
 class PendulumException(Exception):
-
     pass
+
+
+__all__ = [
+    "ParserError",
+    "PendulumException",
+]
diff --git a/pendulum/formatting/__init__.py b/pendulum/formatting/__init__.py
index 975c409..0c6e725 100644
--- a/pendulum/formatting/__init__.py
+++ b/pendulum/formatting/__init__.py
@@ -2,4 +2,5 @@ from __future__ import annotations
 
 from pendulum.formatting.formatter import Formatter
 
+
 __all__ = ["Formatter"]
diff --git a/pendulum/formatting/difference_formatter.py b/pendulum/formatting/difference_formatter.py
index dad219d..588c072 100644
--- a/pendulum/formatting/difference_formatter.py
+++ b/pendulum/formatting/difference_formatter.py
@@ -4,6 +4,7 @@ import typing as t
 
 from pendulum.locales.locale import Locale
 
+
 if t.TYPE_CHECKING:
     from pendulum import Duration
 
@@ -31,10 +32,7 @@ class DifferenceFormatter:
         :param absolute: Whether it's an absolute difference or not
         :param locale: The locale to use
         """
-        if locale is None:
-            locale = self._locale
-        else:
-            locale = Locale.load(locale)
+        locale = self._locale if locale is None else Locale.load(locale)
 
         if diff.years > 0:
             unit = "year"
diff --git a/pendulum/formatting/formatter.py b/pendulum/formatting/formatter.py
index 032daaa..d1bbab7 100644
--- a/pendulum/formatting/formatter.py
+++ b/pendulum/formatting/formatter.py
@@ -14,6 +14,7 @@ import pendulum
 
 from pendulum.locales.locale import Locale
 
+
 if TYPE_CHECKING:
     from pendulum import Timezone
 
@@ -287,10 +288,7 @@ class Formatter:
             offset = dt.utcoffset() or datetime.timedelta()
             minutes = offset.total_seconds() / 60
 
-            if minutes >= 0:
-                sign = "+"
-            else:
-                sign = "-"
+            sign = "+" if minutes >= 0 else "-"
 
             hour, minute = divmod(abs(int(minutes)), 60)
 
@@ -395,9 +393,8 @@ class Formatter:
         if not re.search("^" + pattern + "$", time):
             raise ValueError(f"String does not match format {fmt}")
 
-        _get_parsed_values: Callable[
-            [Match[str]], Any
-        ] = lambda m: self._get_parsed_values(m, parsed, loaded_locale, now)
+        def _get_parsed_values(m: Match[str]) -> Any:
+            return self._get_parsed_values(m, parsed, loaded_locale, now)
 
         re.sub(pattern, _get_parsed_values, time)
 
@@ -502,9 +499,9 @@ class Formatter:
                 raise ValueError("Invalid date")
 
             pm = parsed["meridiem"] == "pm"
-            validated["hour"] %= 12  # type: ignore
+            validated["hour"] %= 12  # type: ignore[operator]
             if pm:
-                validated["hour"] += 12  # type: ignore
+                validated["hour"] += 12  # type: ignore[operator]
 
         if validated["month"] is None:
             if parsed["year"] is not None:
diff --git a/pendulum/helpers.py b/pendulum/helpers.py
index 72c527d..6b62fe7 100644
--- a/pendulum/helpers.py
+++ b/pendulum/helpers.py
@@ -17,6 +17,7 @@ from pendulum.constants import DAYS_PER_MONTHS
 from pendulum.formatting.difference_formatter import DifferenceFormatter
 from pendulum.locales.locale import Locale
 
+
 if TYPE_CHECKING:
     # Prevent import cycles
     from pendulum.duration import Duration
@@ -27,7 +28,6 @@ _DT = TypeVar("_DT", bound=datetime)
 _D = TypeVar("_D", bound=date)
 
 try:
-    # nopycln: file # noqa: E800
     if not with_extensions or struct.calcsize("P") == 4:
         raise ImportError()
 
diff --git a/pendulum/interval.py b/pendulum/interval.py
index ed2cfe6..0c77305 100644
--- a/pendulum/interval.py
+++ b/pendulum/interval.py
@@ -17,12 +17,13 @@ from pendulum.constants import MONTHS_PER_YEAR
 from pendulum.duration import Duration
 from pendulum.helpers import precise_diff
 
+
 if TYPE_CHECKING:
     from typing_extensions import Self
     from typing_extensions import SupportsIndex
 
     from pendulum.helpers import PreciseDiff
-    from pendulum.locales.locale import Locale  # noqa
+    from pendulum.locales.locale import Locale
 
 
 class Interval(Duration):
@@ -264,7 +265,7 @@ class Interval(Duration):
         :param locale: The locale to use. Defaults to current locale.
         :param separator: The separator to use between each unit
         """
-        from pendulum.locales.locale import Locale  # noqa
+        from pendulum.locales.locale import Locale
 
         periods = [
             ("year", self.years),
diff --git a/pendulum/locales/tr/__init__.py b/pendulum/locales/tr/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/pendulum/locales/tr/custom.py b/pendulum/locales/tr/custom.py
new file mode 100644
index 0000000..f644f85
--- /dev/null
+++ b/pendulum/locales/tr/custom.py
@@ -0,0 +1,22 @@
+"""
+tr custom locale file.
+"""
+
+translations = {
+    # Relative time
+    "ago": "{} önce",
+    "from_now": "{} içinde",
+    "after": "{0} sonra",
+    "before": "{0} önce",
+    # Ordinals
+    "ordinal": {"one": ".", "two": ".", "few": ".", "other": "."},
+    # Date formats
+    "date_formats": {
+        "LTS": "h:mm:ss A",
+        "LT": "h:mm A",
+        "L": "MM/DD/YYYY",
+        "LL": "MMMM D, YYYY",
+        "LLL": "MMMM D, YYYY h:mm A",
+        "LLLL": "dddd, MMMM D, YYYY h:mm A",
+    },
+}
diff --git a/pendulum/locales/tr/locale.py b/pendulum/locales/tr/locale.py
new file mode 100644
index 0000000..1806df1
--- /dev/null
+++ b/pendulum/locales/tr/locale.py
@@ -0,0 +1,217 @@
+from .custom import translations as custom_translations
+
+
+"""
+tr locale file.
+
+It has been generated automatically and must not be modified directly.
+"""
+
+
+locale = {
+    "plural": lambda n: "one" if (n == n and (n == 1)) else "other",
+    "ordinal": lambda n: "other",
+    "translations": {
+        "days": {
+            "abbreviated": {
+                0: "Paz",
+                1: "Pzt",
+                2: "Sal",
+                3: "Çar",
+                4: "Per",
+                5: "Cum",
+                6: "Cmt",
+            },
+            "narrow": {
+                0: "P",
+                1: "P",
+                2: "S",
+                3: "Ç",
+                4: "P",
+                5: "C",
+                6: "C",
+            },
+            "short": {
+                0: "Pa",
+                1: "Pt",
+                2: "Sa",
+                3: "Ça",
+                4: "Pe",
+                5: "Cu",
+                6: "Ct",
+            },
+            "wide": {
+                0: "Pazar",
+                1: "Pazartesi",
+                2: "Salı",
+                3: "Çarşamba",
+                4: "Perşembe",
+                5: "Cuma",
+                6: "Cumartesi",
+            },
+        },
+        "months": {
+            "abbreviated": {
+                1: "Oca",
+                2: "Şub",
+                3: "Mar",
+                4: "Nis",
+                5: "May",
+                6: "Haz",
+                7: "Tem",
+                8: "Ağu",
+                9: "Eyl",
+                10: "Eki",
+                11: "Kas",
+                12: "Ara",
+            },
+            "narrow": {
+                1: "O",
+                2: "Ş",
+                3: "M",
+                4: "N",
+                5: "M",
+                6: "H",
+                7: "T",
+                8: "A",
+                9: "E",
+                10: "E",
+                11: "K",
+                12: "A",
+            },
+            "wide": {
+                1: "Ocak",
+                2: "Şubat",
+                3: "Mart",
+                4: "Nisan",
+                5: "Mayıs",
+                6: "Haziran",
+                7: "Temmuz",
+                8: "Ağustos",
+                9: "Eylül",
+                10: "Ekim",
+                11: "Kasım",
+                12: "Aralık",
+            },
+        },
+        "units": {
+            "year": {
+                "one": "{0} yıl",
+                "other": "{0} yıl",
+            },
+            "month": {
+                "one": "{0} ay",
+                "other": "{0} ay",
+            },
+            "week": {
+                "one": "{0} hafta",
+                "other": "{0} hafta",
+            },
+            "day": {
+                "one": "{0} gün",
+                "other": "{0} gün",
+            },
+            "hour": {
+                "one": "{0} saat",
+                "other": "{0} saat",
+            },
+            "minute": {
+                "one": "{0} dakika",
+                "other": "{0} dakika",
+            },
+            "second": {
+                "one": "{0} saniye",
+                "other": "{0} saniye",
+            },
+            "microsecond": {
+                "one": "{0} mikrosaniye",
+                "other": "{0} mikrosaniye",
+            },
+        },
+        "relative": {
+            "year": {
+                "future": {
+                    "other": "{0} yıl sonra",
+                    "one": "{0} yıl sonra",
+                },
+                "past": {
+                    "other": "{0} yıl önce",
+                    "one": "{0} yıl önce",
+                },
+            },
+            "month": {
+                "future": {
+                    "other": "{0} ay sonra",
+                    "one": "{0} ay sonra",
+                },
+                "past": {
+                    "other": "{0} ay önce",
+                    "one": "{0} ay önce",
+                },
+            },
+            "week": {
+                "future": {
+                    "other": "{0} hafta sonra",
+                    "one": "{0} hafta sonra",
+                },
+                "past": {
+                    "other": "{0} hafta önce",
+                    "one": "{0} hafta önce",
+                },
+            },
+            "day": {
+                "future": {
+                    "other": "{0} gün sonra",
+                    "one": "{0} gün sonra",
+                },
+                "past": {
+                    "other": "{0} gün önce",
+                    "one": "{0} gün önce",
+                },
+            },
+            "hour": {
+                "future": {
+                    "other": "{0} saat sonra",
+                    "one": "{0} saat sonra",
+                },
+                "past": {
+                    "other": "{0} saat önce",
+                    "one": "{0} saat önce",
+                },
+            },
+            "minute": {
+                "future": {
+                    "other": "{0} dakika sonra",
+                    "one": "{0} dakika sonra",
+                },
+                "past": {
+                    "other": "{0} dakika önce",
+                    "one": "{0} dakika önce",
+                },
+            },
+            "second": {
+                "future": {
+                    "other": "{0} saniye sonra",
+                    "one": "{0} saniye sonra",
+                },
+                "past": {
+                    "other": "{0} saniye önce",
+                    "one": "{0} saniye önce",
+                },
+            },
+        },
+        "day_periods": {
+            "midnight": "gece yarısı",
+            "am": "ÖÖ",
+            "noon": "öğle",
+            "pm": "ÖS",
+            "morning1": "sabah",
+            "morning2": "öğleden önce",
+            "afternoon1": "öğleden sonra",
+            "afternoon2": "akşamüstü",
+            "evening1": "akşam",
+            "night1": "gece",
+        },
+    },
+    "custom": custom_translations,
+}
diff --git a/pendulum/mixins/default.py b/pendulum/mixins/default.py
index 59f985e..6444b2a 100644
--- a/pendulum/mixins/default.py
+++ b/pendulum/mixins/default.py
@@ -2,6 +2,7 @@ from __future__ import annotations
 
 from pendulum.formatting import Formatter
 
+
 _formatter = Formatter()
 
 
diff --git a/pendulum/parser.py b/pendulum/parser.py
index 1b05062..d39842f 100644
--- a/pendulum/parser.py
+++ b/pendulum/parser.py
@@ -9,6 +9,7 @@ from pendulum.parsing import _Interval
 from pendulum.parsing import parse as base_parse
 from pendulum.tz.timezone import UTC
 
+
 if t.TYPE_CHECKING:
     from pendulum.date import Date
     from pendulum.datetime import DateTime
diff --git a/pendulum/parsing/__init__.py b/pendulum/parsing/__init__.py
index ca46592..3b64994 100644
--- a/pendulum/parsing/__init__.py
+++ b/pendulum/parsing/__init__.py
@@ -17,6 +17,7 @@ from dateutil import parser
 
 from pendulum.parsing.exceptions import ParserError
 
+
 with_extensions = os.getenv("PENDULUM_EXTENSIONS", "1") == "1"
 
 try:
@@ -30,7 +31,7 @@ except ImportError:
     from pendulum.parsing.iso8601 import parse_iso8601  # type: ignore[assignment]
 
 COMMON = re.compile(
-    # Date (optional)  # noqa: E800
+    # Date (optional)  # noqa: ERA001
     "^"
     "(?P<date>"
     "    (?P<classic>"  # Classic date (YYYY-MM-DD)
@@ -41,10 +42,10 @@ COMMON = re.compile(
     "        )?"
     "    )"
     ")?"
-    # Time (optional)  # noqa: E800
-    "(?P<time>"
-    r"    (?P<timesep>\ )?"  # Separator (space)
-    r"    (?P<hour>\d{1,2}):(?P<minute>\d{1,2})?(?::(?P<second>\d{1,2}))?"  # HH:mm:ss (optional mm and ss)
+    # Time (optional)  # noqa: ERA001
+    "(?P<time>" r"    (?P<timesep>\ )?"  # Separator (space)
+    # HH:mm:ss (optional mm and ss)
+    r"    (?P<hour>\d{1,2}):(?P<minute>\d{1,2})?(?::(?P<second>\d{1,2}))?"
     # Subsecond part (optional)
     "    (?P<subsecondsection>"
     "        (?:[.|,])"  # Subsecond separator (optional)
@@ -173,10 +174,7 @@ def _parse_common(text: str, **options: Any) -> datetime | date | time:
 
     minute = int(m.group("minute"))
 
-    if m.group("second"):
-        second = int(m.group("second"))
-    else:
-        second = 0
+    second = int(m.group("second")) if m.group("second") else 0
 
     # Grabbing subseconds, if any
     microsecond = 0
diff --git a/pendulum/parsing/exceptions/__init__.py b/pendulum/parsing/exceptions/__init__.py
index 05195b5..9f2d809 100644
--- a/pendulum/parsing/exceptions/__init__.py
+++ b/pendulum/parsing/exceptions/__init__.py
@@ -2,5 +2,4 @@ from __future__ import annotations
 
 
 class ParserError(ValueError):
-
     pass
diff --git a/pendulum/parsing/iso8601.py b/pendulum/parsing/iso8601.py
index ba2bba3..4f99175 100644
--- a/pendulum/parsing/iso8601.py
+++ b/pendulum/parsing/iso8601.py
@@ -19,8 +19,9 @@ from pendulum.tz.timezone import UTC
 from pendulum.tz.timezone import FixedTimezone
 from pendulum.tz.timezone import Timezone
 
+
 ISO8601_DT = re.compile(
-    # Date (optional)  # noqa: E800
+    # Date (optional)  # noqa: ERA001
     "^"
     "(?P<date>"
     "    (?P<classic>"  # Classic date (YYYY-MM-DD) or ordinal (YYYY-DDD)
@@ -40,10 +41,10 @@ ISO8601_DT = re.compile(
     r"        (?P<isoweekday>\d)?"  # Weekday (optional)
     "    )"
     ")?"
-    # Time (optional)  # noqa: E800
-    "(?P<time>"
-    r"    (?P<timesep>[T\ ])?"  # Separator (T or space)
-    r"    (?P<hour>\d{1,2})(?P<minsep>:)?(?P<minute>\d{1,2})?(?P<secsep>:)?(?P<second>\d{1,2})?"  # HH:mm:ss (optional mm and ss)
+    # Time (optional)  # noqa: ERA001
+    "(?P<time>" r"    (?P<timesep>[T\ ])?"  # Separator (T or space)
+    # HH:mm:ss (optional mm and ss)
+    r"    (?P<hour>\d{1,2})(?P<minsep>:)?(?P<minute>\d{1,2})?(?P<secsep>:)?(?P<second>\d{1,2})?"  # noqa: E501
     # Subsecond part (optional)
     "    (?P<subsecondsection>"
     "        (?:[.,])"  # Subsecond separator (optional)
@@ -60,7 +61,7 @@ ISO8601_DT = re.compile(
 
 ISO8601_DURATION = re.compile(
     "^P"  # Duration P indicator
-    # Years, months and days (optional)  # noqa: E800
+    # Years, months and days (optional)  # noqa: ERA001
     "(?P<w>"
     r"    (?P<weeks>\d+(?:[.,]\d+)?W)"
     ")?"
@@ -184,7 +185,7 @@ def parse_iso8601(
         if ambiguous_date:
             # We can "safely" assume that the ambiguous date
             # was actually a time in the form hhmmss
-            hhmmss = f"{str(year)}{str(month):0>2}"
+            hhmmss = f"{year!s}{str(month):0>2}"
 
             return datetime.time(int(hhmmss[:2]), int(hhmmss[2:4]), int(hhmmss[4:]))
 
@@ -419,10 +420,7 @@ def _parse_iso8601_duration(text: str, **options: str) -> Duration | None:
 def _get_iso_8601_week(
     year: int | str, week: int | str, weekday: int | str
 ) -> dict[str, int]:
-    if not weekday:
-        weekday = 1
-    else:
-        weekday = int(weekday)
+    weekday = 1 if not weekday else int(weekday)
 
     year = int(year)
     week = int(week)
diff --git a/pendulum/time.py b/pendulum/time.py
index 6c0c304..f1edb10 100644
--- a/pendulum/time.py
+++ b/pendulum/time.py
@@ -18,6 +18,7 @@ from pendulum.duration import AbsoluteDuration
 from pendulum.duration import Duration
 from pendulum.mixins.default import FormattableMixin
 
+
 if TYPE_CHECKING:
     from typing_extensions import Literal
     from typing_extensions import Self
@@ -37,7 +38,7 @@ class Time(FormattableMixin, time):
 
         tzinfo = ""
         if self.tzinfo:
-            tzinfo = f", tzinfo={repr(self.tzinfo)}"
+            tzinfo = f", tzinfo={self.tzinfo!r}"
 
         return (
             f"{self.__class__.__name__}"
diff --git a/pendulum/tz/__init__.py b/pendulum/tz/__init__.py
index 0ca1b32..36c2c69 100644
--- a/pendulum/tz/__init__.py
+++ b/pendulum/tz/__init__.py
@@ -11,6 +11,7 @@ from pendulum.tz.timezone import FixedTimezone
 from pendulum.tz.timezone import Timezone
 from pendulum.utils._compat import resources
 
+
 PRE_TRANSITION = "pre"
 POST_TRANSITION = "post"
 TRANSITION_ERROR = "error"
diff --git a/pendulum/tz/data/windows.py b/pendulum/tz/data/windows.py
index 65aa6c3..e76e6bb 100644
--- a/pendulum/tz/data/windows.py
+++ b/pendulum/tz/data/windows.py
@@ -1,5 +1,6 @@
 from __future__ import annotations
 
+
 windows_timezones = {
     "AUS Central Standard Time": "Australia/Darwin",
     "AUS Eastern Standard Time": "Australia/Sydney",
diff --git a/pendulum/tz/exceptions.py b/pendulum/tz/exceptions.py
index b8833ac..d06c133 100644
--- a/pendulum/tz/exceptions.py
+++ b/pendulum/tz/exceptions.py
@@ -2,6 +2,7 @@ from __future__ import annotations
 
 from typing import TYPE_CHECKING
 
+
 if TYPE_CHECKING:
     from datetime import datetime
 
diff --git a/pendulum/tz/local_timezone.py b/pendulum/tz/local_timezone.py
index 0965768..dacf234 100644
--- a/pendulum/tz/local_timezone.py
+++ b/pendulum/tz/local_timezone.py
@@ -13,6 +13,7 @@ from pendulum.tz.exceptions import InvalidTimezone
 from pendulum.tz.timezone import FixedTimezone
 from pendulum.tz.timezone import Timezone
 
+
 if sys.platform == "win32":
     import winreg
 
diff --git a/pendulum/tz/timezone.py b/pendulum/tz/timezone.py
index 9befc00..0c6eb8d 100644
--- a/pendulum/tz/timezone.py
+++ b/pendulum/tz/timezone.py
@@ -14,6 +14,7 @@ from pendulum.tz.exceptions import InvalidTimezone
 from pendulum.tz.exceptions import NonExistingTime
 from pendulum.utils._compat import zoneinfo
 
+
 if TYPE_CHECKING:
     from typing_extensions import Self
 
diff --git a/pendulum/utils/_compat.py b/pendulum/utils/_compat.py
index 5ac51bd..e4dfedf 100644
--- a/pendulum/utils/_compat.py
+++ b/pendulum/utils/_compat.py
@@ -4,6 +4,7 @@ import sys
 
 from pendulum.utils import _zoneinfo as zoneinfo
 
+
 PYPY = hasattr(sys, "pypy_version_info")
 
 if sys.version_info < (3, 9):
diff --git a/pendulum/utils/_zoneinfo.py b/pendulum/utils/_zoneinfo.py
index 8f87b79..e9f0251 100644
--- a/pendulum/utils/_zoneinfo.py
+++ b/pendulum/utils/_zoneinfo.py
@@ -4,6 +4,7 @@ import sys
 
 from typing import TYPE_CHECKING
 
+
 if sys.version_info < (3, 9):
     # Works around https://github.com/pganssle/zoneinfo/issues/125
     from backports.zoneinfo import TZPATH
diff --git a/poetry.lock b/poetry.lock
index 2334018..6f6e448 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -69,53 +69,6 @@ files = [
 [package.extras]
 tzdata = ["tzdata"]
 
-[[package]]
-name = "black"
-version = "22.6.0"
-description = "The uncompromising code formatter."
-optional = false
-python-versions = ">=3.6.2"
-files = [
-    {file = "black-22.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f586c26118bc6e714ec58c09df0157fe2d9ee195c764f630eb0d8e7ccce72e69"},
-    {file = "black-22.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b270a168d69edb8b7ed32c193ef10fd27844e5c60852039599f9184460ce0807"},
-    {file = "black-22.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6797f58943fceb1c461fb572edbe828d811e719c24e03375fd25170ada53825e"},
-    {file = "black-22.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c85928b9d5f83b23cee7d0efcb310172412fbf7cb9d9ce963bd67fd141781def"},
-    {file = "black-22.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:f6fe02afde060bbeef044af7996f335fbe90b039ccf3f5eb8f16df8b20f77666"},
-    {file = "black-22.6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cfaf3895a9634e882bf9d2363fed5af8888802d670f58b279b0bece00e9a872d"},
-    {file = "black-22.6.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94783f636bca89f11eb5d50437e8e17fbc6a929a628d82304c80fa9cd945f256"},
-    {file = "black-22.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2ea29072e954a4d55a2ff58971b83365eba5d3d357352a07a7a4df0d95f51c78"},
-    {file = "black-22.6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e439798f819d49ba1c0bd9664427a05aab79bfba777a6db94fd4e56fae0cb849"},
-    {file = "black-22.6.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:187d96c5e713f441a5829e77120c269b6514418f4513a390b0499b0987f2ff1c"},
-    {file = "black-22.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:074458dc2f6e0d3dab7928d4417bb6957bb834434516f21514138437accdbe90"},
-    {file = "black-22.6.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a218d7e5856f91d20f04e931b6f16d15356db1c846ee55f01bac297a705ca24f"},
-    {file = "black-22.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:568ac3c465b1c8b34b61cd7a4e349e93f91abf0f9371eda1cf87194663ab684e"},
-    {file = "black-22.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6c1734ab264b8f7929cef8ae5f900b85d579e6cbfde09d7387da8f04771b51c6"},
-    {file = "black-22.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9a3ac16efe9ec7d7381ddebcc022119794872abce99475345c5a61aa18c45ad"},
-    {file = "black-22.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:b9fd45787ba8aa3f5e0a0a98920c1012c884622c6c920dbe98dbd05bc7c70fbf"},
-    {file = "black-22.6.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7ba9be198ecca5031cd78745780d65a3f75a34b2ff9be5837045dce55db83d1c"},
-    {file = "black-22.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3db5b6409b96d9bd543323b23ef32a1a2b06416d525d27e0f67e74f1446c8f2"},
-    {file = "black-22.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:560558527e52ce8afba936fcce93a7411ab40c7d5fe8c2463e279e843c0328ee"},
-    {file = "black-22.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b154e6bbde1e79ea3260c4b40c0b7b3109ffcdf7bc4ebf8859169a6af72cd70b"},
-    {file = "black-22.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:4af5bc0e1f96be5ae9bd7aaec219c901a94d6caa2484c21983d043371c733fc4"},
-    {file = "black-22.6.0-py3-none-any.whl", hash = "sha256:ac609cf8ef5e7115ddd07d85d988d074ed00e10fbc3445aee393e70164a2219c"},
-    {file = "black-22.6.0.tar.gz", hash = "sha256:6c6d39e28aed379aec40da1c65434c77d75e65bb59a1e1c283de545fb4e7c6c9"},
-]
-
-[package.dependencies]
-click = ">=8.0.0"
-mypy-extensions = ">=0.4.3"
-pathspec = ">=0.9.0"
-platformdirs = ">=2"
-tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""}
-typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""}
-typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
-
-[package.extras]
-colorama = ["colorama (>=0.4.3)"]
-d = ["aiohttp (>=3.7.4)"]
-jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
-uvloop = ["uvloop (>=0.15.2)"]
-
 [[package]]
 name = "cffi"
 version = "1.15.1"
@@ -361,23 +314,6 @@ files = [
     {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
 ]
 
-[[package]]
-name = "isort"
-version = "5.10.1"
-description = "A Python utility / library to sort Python imports."
-optional = false
-python-versions = ">=3.6.1,<4.0"
-files = [
-    {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
-    {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
-]
-
-[package.extras]
-colors = ["colorama (>=0.4.3,<0.5.0)"]
-pipfile-deprecated-finder = ["pipreqs", "requirementslib"]
-plugins = ["setuptools"]
-requirements-deprecated-finder = ["pip-api", "pipreqs"]
-
 [[package]]
 name = "jinja2"
 version = "3.1.2"
@@ -639,17 +575,6 @@ files = [
 [package.dependencies]
 pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
 
-[[package]]
-name = "pathspec"
-version = "0.9.0"
-description = "Utility library for gitignore style pattern matching of file paths."
-optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
-files = [
-    {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
-    {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
-]
-
 [[package]]
 name = "platformdirs"
 version = "2.5.2"
@@ -1309,4 +1234,4 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=
 [metadata]
 lock-version = "2.0"
 python-versions = "^3.7"
-content-hash = "bd4dfa1f9eb000d1c21df3eef7542ddefb1edcd222b9f9fd829ee0279fa12717"
+content-hash = "7a4cee3a29551de99a31bcf181c7a90cdf9c2979fb3cfbb3a3981d6a0b1c7488"
diff --git a/pyproject.toml b/pyproject.toml
index 561e050..91bd700 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -46,8 +46,6 @@ pygments = "^2.2"
 markdown-include = "^0.5.1"
 
 [tool.poetry.group.lint.dependencies]
-black = { version = "^22.6.0", markers = "implementation_name != 'pypy'" }
-isort = "^5.10.1"
 pre-commit = "^2.20.0"
 
 [tool.poetry.group.typing.dependencies]
@@ -74,32 +72,68 @@ pytest-codspeed = "^1.2.2"
 generate-setup-file = false
 script = "build.py"
 
-[tool.isort]
-profile = "black"
-force_single_line = true
-atomic = true
-lines_after_imports = -1
-lines_between_types = 1
-skip_glob = [
-    "pendulum/locales/**",
-    "build.py",
-    "pendulum/__version__.py",
+
+[tool.ruff]
+fix = true
+unfixable = [
+    "ERA", # do not autoremove commented out code
+]
+target-version = "py37"
+line-length = 88
+extend-select = [
+    "B",   # flake8-bugbear
+    "C4",  # flake8-comprehensions
+    "ERA", # flake8-eradicate/eradicate
+    "I",   # isort
+    "N",   # pep8-naming
+    "PIE", # flake8-pie
+    "PGH", # pygrep
+    "RUF", # ruff checks
+    "SIM", # flake8-simplify
+    "TCH", # flake8-type-checking
+    "TID", # flake8-tidy-imports
+    "UP",  # pyupgrade
+]
+ignore = [
+    "B904", # use 'raise ... from err'
+    "B905", # use explicit 'strict=' parameter with 'zip()'
+    "N818", #  Exception name should be named with an Error suffix
+]
+extend-exclude = [
+    # External to the project's coding standards:
+    "docs/*",
+    # Machine-generated, too many false-positives
+    "pendulum/locales/*",
+    # ruff disagrees with black when it comes to formatting
+    "*.pyi",
 ]
-filter_files = true
-known_first_party = "pendulum"
-known_third_party = [
+
+[tool.ruff.flake8-tidy-imports]
+ban-relative-imports = "all"
+
+[tool.ruff.isort]
+force-single-line = true
+lines-between-types = 1
+lines-after-imports = 2
+known-first-party = ["pendulum"]
+known-third-party = [
     "babel",
     "cleo",
     "dateutil",
     "time_machine",
     "pytzdata",
 ]
+required-imports = ["from __future__ import annotations"]
+
+[tool.ruff.extend-per-file-ignores]
+"build.py" = ["I002"]
 
 [tool.mypy]
 strict = true
 files = "pendulum, tests"
 show_error_codes = true
 pretty = true
+warn_unused_ignores = true
 exclude = [
     "^build\\.py$"
 ]


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 7659ec4..ddbb21a 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -19,7 +19,7 @@ jobs:
     strategy:
       matrix:
         os: [Ubuntu, MacOS, Windows]
-        python-version: [3.7, 3.8, 3.9, "3.10"]
+        python-version: [3.7, 3.8, 3.9, "3.10", "3.11"]
     defaults:
       run:
         shell: bash
diff --git a/pendulum/testing/traveller.py b/pendulum/testing/traveller.py
index 8e96c5e..d1be3bb 100644
--- a/pendulum/testing/traveller.py
+++ b/pendulum/testing/traveller.py
@@ -6,6 +6,7 @@ from typing import cast
 from pendulum.datetime import DateTime
 from pendulum.utils._compat import PYPY
 
+
 if TYPE_CHECKING:
     from types import TracebackType
 
@@ -148,5 +149,4 @@ if not PYPY:
 else:
 
     class Traveller(BaseTraveller):  # type: ignore[no-redef]
-
         ...
diff --git a/tests/benchmarks/test_format.py b/tests/benchmarks/test_format.py
index 41e6782..92dd3fa 100644
--- a/tests/benchmarks/test_format.py
+++ b/tests/benchmarks/test_format.py
@@ -2,6 +2,7 @@ from __future__ import annotations
 
 from typing import TYPE_CHECKING
 
+
 if TYPE_CHECKING:
     from pytest_benchmark.fixture import BenchmarkFixture  # type: ignore[import]
 
diff --git a/tests/conftest.py b/tests/conftest.py
index 73b66db..8f0e6b9 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -6,6 +6,7 @@ import pytest
 
 import pendulum
 
+
 if TYPE_CHECKING:
     from collections.abc import Iterator
 
diff --git a/tests/date/test_comparison.py b/tests/date/test_comparison.py
index 94bf224..9fd999b 100644
--- a/tests/date/test_comparison.py
+++ b/tests/date/test_comparison.py
@@ -46,7 +46,7 @@ def test_not_equal_to_false():
 def test_not_equal_to_none():
     d1 = pendulum.Date(2000, 1, 1)
 
-    assert d1 != None  # noqa
+    assert d1 is not None
 
 
 def test_greater_than_true():
diff --git a/tests/datetime/test_comparison.py b/tests/datetime/test_comparison.py
index ad81e73..c3cb064 100644
--- a/tests/datetime/test_comparison.py
+++ b/tests/datetime/test_comparison.py
@@ -75,7 +75,7 @@ def test_not_equal_with_timezone_true():
 def test_not_equal_to_none():
     d1 = pendulum.datetime(2000, 1, 1, 1, 2, 3)
 
-    assert d1 != None  # noqa
+    assert d1 is not None
 
 
 def test_greater_than_true():
diff --git a/tests/datetime/test_construct.py b/tests/datetime/test_construct.py
index 9435413..d0e659d 100644
--- a/tests/datetime/test_construct.py
+++ b/tests/datetime/test_construct.py
@@ -16,6 +16,7 @@ from pendulum import timezone
 from pendulum.utils._compat import PYPY
 from tests.conftest import assert_datetime
 
+
 if not PYPY:
     import time_machine
 else:
diff --git a/tests/datetime/test_from_format.py b/tests/datetime/test_from_format.py
index 10c4a23..7b498b3 100644
--- a/tests/datetime/test_from_format.py
+++ b/tests/datetime/test_from_format.py
@@ -150,10 +150,7 @@ def test_from_format_with_invalid_padded_day():
     ],
 )
 def test_from_format(text, fmt, expected, now):
-    if now is None:
-        now = pendulum.datetime(2015, 11, 12)
-    else:
-        now = pendulum.parse(now)
+    now = pendulum.datetime(2015, 11, 12) if now is None else pendulum.parse(now)
 
     with pendulum.travel_to(now, freeze=True):
         assert pendulum.from_format(text, fmt).isoformat() == expected
diff --git a/tests/datetime/test_getters.py b/tests/datetime/test_getters.py
index 9adfc18..1911cb1 100644
--- a/tests/datetime/test_getters.py
+++ b/tests/datetime/test_getters.py
@@ -126,7 +126,8 @@ def test_utc():
     assert pendulum.datetime(2012, 1, 1, tz="UTC").is_utc()
     assert pendulum.datetime(2012, 1, 1, tz=0).is_utc()
     assert not pendulum.datetime(2012, 1, 1, tz=5).is_utc()
-    # There is no time difference between Greenwich Mean Time and Coordinated Universal Time
+    # There is no time difference between Greenwich Mean Time
+    # and Coordinated Universal Time
     assert pendulum.datetime(2012, 1, 1, tz="GMT").is_utc()
 
 
diff --git a/tests/datetime/test_strings.py b/tests/datetime/test_strings.py
index 0de340d..87321d8 100644
--- a/tests/datetime/test_strings.py
+++ b/tests/datetime/test_strings.py
@@ -109,11 +109,11 @@ def test_to_string_invalid():
 
 def test_repr():
     d = pendulum.datetime(1975, 12, 25, 14, 15, 16, tz="local")
-    expected = f"DateTime(1975, 12, 25, 14, 15, 16, tzinfo={repr(d.tzinfo)})"
+    expected = f"DateTime(1975, 12, 25, 14, 15, 16, tzinfo={d.tzinfo!r})"
     assert repr(d) == expected
 
     d = pendulum.datetime(1975, 12, 25, 14, 15, 16, 123456, tz="local")
-    expected = f"DateTime(1975, 12, 25, 14, 15, 16, 123456, tzinfo={repr(d.tzinfo)})"
+    expected = f"DateTime(1975, 12, 25, 14, 15, 16, 123456, tzinfo={d.tzinfo!r})"
     assert repr(d) == expected
 
 
diff --git a/tests/interval/test_range.py b/tests/interval/test_range.py
index 28fe1ff..0b883e5 100644
--- a/tests/interval/test_range.py
+++ b/tests/interval/test_range.py
@@ -47,7 +47,7 @@ def test_iter():
     dt2 = pendulum.datetime(2000, 1, 31, 12, 45, 37)
 
     p = Interval(dt1, dt2)
-    i = 0  # noqa: SIM113 (suggests use of enumerate)
+    i = 0
     for dt in p:
         assert isinstance(dt, pendulum.DateTime)
         i += 1
diff --git a/tests/localization/test_cs.py b/tests/localization/test_cs.py
index 71b8340..c3c2d1c 100644
--- a/tests/localization/test_cs.py
+++ b/tests/localization/test_cs.py
@@ -2,6 +2,7 @@ from __future__ import annotations
 
 import pendulum
 
+
 locale = "cs"
 
 
diff --git a/tests/localization/test_da.py b/tests/localization/test_da.py
index b08adfe..34c69b1 100644
--- a/tests/localization/test_da.py
+++ b/tests/localization/test_da.py
@@ -2,6 +2,7 @@ from __future__ import annotations
 
 import pendulum
 
+
 locale = "da"
 
 
diff --git a/tests/localization/test_de.py b/tests/localization/test_de.py
index 9c72b79..4abc3f5 100644
--- a/tests/localization/test_de.py
+++ b/tests/localization/test_de.py
@@ -2,6 +2,7 @@ from __future__ import annotations
 
 import pendulum
 
+
 locale = "de"
 
 
diff --git a/tests/localization/test_es.py b/tests/localization/test_es.py
index 747ec6f..64d5dbb 100644
--- a/tests/localization/test_es.py
+++ b/tests/localization/test_es.py
@@ -2,6 +2,7 @@ from __future__ import annotations
 
 import pendulum
 
+
 locale = "es"
 
 
diff --git a/tests/localization/test_fa.py b/tests/localization/test_fa.py
index 39d2e4a..5bb25ad 100644
--- a/tests/localization/test_fa.py
+++ b/tests/localization/test_fa.py
@@ -2,6 +2,7 @@ from __future__ import annotations
 
 import pendulum
 
+
 locale = "fa"
 
 
diff --git a/tests/localization/test_fo.py b/tests/localization/test_fo.py
index f451553..a2b72de 100644
--- a/tests/localization/test_fo.py
+++ b/tests/localization/test_fo.py
@@ -2,6 +2,7 @@ from __future__ import annotations
 
 import pendulum
 
+
 locale = "fo"
 
 
diff --git a/tests/localization/test_fr.py b/tests/localization/test_fr.py
index 1cfef5b..e689a19 100644
--- a/tests/localization/test_fr.py
+++ b/tests/localization/test_fr.py
@@ -2,6 +2,7 @@ from __future__ import annotations
 
 import pendulum
 
+
 locale = "fr"
 
 
diff --git a/tests/localization/test_he.py b/tests/localization/test_he.py
index 6186ef2..0695a64 100644
--- a/tests/localization/test_he.py
+++ b/tests/localization/test_he.py
@@ -2,6 +2,7 @@ from __future__ import annotations
 
 import pendulum
 
+
 locale = "he"
 
 
diff --git a/tests/localization/test_id.py b/tests/localization/test_id.py
index 3dd316c..3ae668d 100644
--- a/tests/localization/test_id.py
+++ b/tests/localization/test_id.py
@@ -2,6 +2,7 @@ from __future__ import annotations
 
 import pendulum
 
+
 locale = "id"
 
 
diff --git a/tests/localization/test_it.py b/tests/localization/test_it.py
index 1918a2b..1f79c6e 100644
--- a/tests/localization/test_it.py
+++ b/tests/localization/test_it.py
@@ -2,6 +2,7 @@ from __future__ import annotations
 
 import pendulum
 
+
 locale = "it"
 
 
diff --git a/tests/localization/test_ja.py b/tests/localization/test_ja.py
index 82457fd..6f38d05 100644
--- a/tests/localization/test_ja.py
+++ b/tests/localization/test_ja.py
@@ -2,6 +2,7 @@ from __future__ import annotations
 
 import pendulum
 
+
 locale = "ja"
 
 
diff --git a/tests/localization/test_ko.py b/tests/localization/test_ko.py
index e33ca25..c03a372 100644
--- a/tests/localization/test_ko.py
+++ b/tests/localization/test_ko.py
@@ -2,6 +2,7 @@ from __future__ import annotations
 
 import pendulum
 
+
 locale = "ko"
 
 
diff --git a/tests/localization/test_lt.py b/tests/localization/test_lt.py
index 71de1ac..4b75394 100644
--- a/tests/localization/test_lt.py
+++ b/tests/localization/test_lt.py
@@ -2,6 +2,7 @@ from __future__ import annotations
 
 import pendulum
 
+
 locale = "lt"
 
 
diff --git a/tests/localization/test_nb.py b/tests/localization/test_nb.py
index 3f696e5..19b7d1c 100644
--- a/tests/localization/test_nb.py
+++ b/tests/localization/test_nb.py
@@ -2,6 +2,7 @@ from __future__ import annotations
 
 import pendulum
 
+
 locale = "nb"
 
 
diff --git a/tests/localization/test_nl.py b/tests/localization/test_nl.py
index 68227ec..fde63b4 100644
--- a/tests/localization/test_nl.py
+++ b/tests/localization/test_nl.py
@@ -2,6 +2,7 @@ from __future__ import annotations
 
 import pendulum
 
+
 locale = "nl"
 
 
diff --git a/tests/localization/test_nn.py b/tests/localization/test_nn.py
index d4b8099..7751858 100644
--- a/tests/localization/test_nn.py
+++ b/tests/localization/test_nn.py
@@ -2,6 +2,7 @@ from __future__ import annotations
 
 import pendulum
 
+
 locale = "nn"
 
 
diff --git a/tests/localization/test_pl.py b/tests/localization/test_pl.py
index 2b6e707..7253096 100644
--- a/tests/localization/test_pl.py
+++ b/tests/localization/test_pl.py
@@ -2,6 +2,7 @@ from __future__ import annotations
 
 import pendulum
 
+
 locale = "pl"
 
 
diff --git a/tests/localization/test_ru.py b/tests/localization/test_ru.py
index be0e645..b0a7712 100644
--- a/tests/localization/test_ru.py
+++ b/tests/localization/test_ru.py
@@ -2,6 +2,7 @@ from __future__ import annotations
 
 import pendulum
 
+
 locale = "ru"
 
 
diff --git a/tests/localization/test_sk.py b/tests/localization/test_sk.py
index 5553e7f..2c18f93 100644
--- a/tests/localization/test_sk.py
+++ b/tests/localization/test_sk.py
@@ -2,6 +2,7 @@ from __future__ import annotations
 
 import pendulum
 
+
 locale = "sk"
 
 
diff --git a/tests/localization/test_sv.py b/tests/localization/test_sv.py
index e0e4e65..e4b7051 100644
--- a/tests/localization/test_sv.py
+++ b/tests/localization/test_sv.py
@@ -2,6 +2,7 @@ from __future__ import annotations
 
 import pendulum
 
+
 locale = "sv"
 
 
diff --git a/tests/localization/test_tr.py b/tests/localization/test_tr.py
new file mode 100644
index 0000000..5ec00ee
--- /dev/null
+++ b/tests/localization/test_tr.py
@@ -0,0 +1,66 @@
+from __future__ import annotations
+
+import pendulum
+
+
+locale = "tr"
+
+
+def test_diff_for_humans():
+    with pendulum.travel_to(pendulum.datetime(2016, 8, 29), freeze=True):
+        diff_for_humans()
+
+
+def diff_for_humans():
+    d = pendulum.now().subtract(seconds=1)
+    assert d.diff_for_humans(locale=locale) == "1 saniye önce"
+
+    d = pendulum.now().subtract(seconds=2)
+    assert d.diff_for_humans(locale=locale) == "2 saniye önce"
+
+    d = pendulum.now().subtract(minutes=1)
+    assert d.diff_for_humans(locale=locale) == "1 dakika önce"
+
+    d = pendulum.now().subtract(minutes=2)
+    assert d.diff_for_humans(locale=locale) == "2 dakika önce"
+
+    d = pendulum.now().subtract(hours=1)
+    assert d.diff_for_humans(locale=locale) == "1 saat önce"
+
+    d = pendulum.now().subtract(hours=2)
+    assert d.diff_for_humans(locale=locale) == "2 saat önce"
+
+    d = pendulum.now().subtract(days=1)
+    assert d.diff_for_humans(locale=locale) == "1 gün önce"
+
+    d = pendulum.now().subtract(days=2)
+    assert d.diff_for_humans(locale=locale) == "2 gün önce"
+
+    d = pendulum.now().subtract(weeks=1)
+    assert d.diff_for_humans(locale=locale) == "1 hafta önce"
+
+    d = pendulum.now().subtract(weeks=2)
+    assert d.diff_for_humans(locale=locale) == "2 hafta önce"
+
+    d = pendulum.now().subtract(months=1)
+    assert d.diff_for_humans(locale=locale) == "1 ay önce"
+
+    d = pendulum.now().subtract(months=2)
+    assert d.diff_for_humans(locale=locale) == "2 ay önce"
+
+    d = pendulum.now().subtract(years=1)
+    assert d.diff_for_humans(locale=locale) == "1 yıl önce"
+
+    d = pendulum.now().subtract(years=2)
+    assert d.diff_for_humans(locale=locale) == "2 yıl önce"
+
+    d = pendulum.now().add(seconds=1)
+    assert d.diff_for_humans(locale=locale) == "1 saniye sonra"
+
+    d = pendulum.now().add(seconds=1)
+    d2 = pendulum.now()
+    assert d.diff_for_humans(d2, locale=locale) == "1 saniye sonra"
+    assert d2.diff_for_humans(d, locale=locale) == "1 saniye önce"
+
+    assert d.diff_for_humans(d2, True, locale=locale) == "1 saniye"
+    assert d2.diff_for_humans(d.add(seconds=1), True, locale=locale) == "2 saniye"
diff --git a/tests/parsing/test_parse_iso8601.py b/tests/parsing/test_parse_iso8601.py
index 0047791..83f2881 100644
--- a/tests/parsing/test_parse_iso8601.py
+++ b/tests/parsing/test_parse_iso8601.py
@@ -8,6 +8,7 @@ import pytest
 
 from pendulum.parsing import parse_iso8601
 
+
 try:
     from pendulum.parsing._extension import TZFixedOffset as FixedTimezone
 except ImportError:
diff --git a/tests/testing/test_time_travel.py b/tests/testing/test_time_travel.py
index dd496e3..5e7a49b 100644
--- a/tests/testing/test_time_travel.py
+++ b/tests/testing/test_time_travel.py
@@ -9,6 +9,7 @@ import pendulum
 
 from pendulum.utils._compat import PYPY
 
+
 if TYPE_CHECKING:
     from typing import Generator
 
diff --git a/tests/time/test_comparison.py b/tests/time/test_comparison.py
index f1ef275..23dd176 100644
--- a/tests/time/test_comparison.py
+++ b/tests/time/test_comparison.py
@@ -28,7 +28,7 @@ def test_equal_to_false():
 def test_not_equal_to_none():
     t1 = pendulum.time(1, 2, 3)
 
-    assert t1 != None  # noqa
+    assert t1 is not None
 
 
 def test_greater_than_true():
diff --git a/tests/tz/test_timezone.py b/tests/tz/test_timezone.py
index 655267d..792beb0 100644
--- a/tests/tz/test_timezone.py
+++ b/tests/tz/test_timezone.py
@@ -274,7 +274,10 @@ def test_after_last_transition():
 
 
 @pytest.mark.skip(
-    reason="zoneinfo does not currently support POSIX transition rules to go beyond the last fixed transition."
+    reason=(
+        "zoneinfo does not currently support POSIX transition"
+        " rules to go beyond the last fixed transition."
+    )
 )
 def test_on_last_transition():
     tz = pendulum.timezone("Europe/Paris")
@@ -406,7 +409,10 @@ def test_just_before_last_transition():
 
 
 @pytest.mark.skip(
-    reason="zoneinfo does not currently support POSIX transition rules to go beyond the last fixed transition."
+    reason=(
+        "zoneinfo does not currently support POSIX transition"
+        " rules to go beyond the last fixed transition."
+    )
 )
 def test_timezones_are_extended():
     tz = pendulum.timezone("Europe/Paris")