summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2023-12-17 14:32:20 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2023-12-17 14:32:20 +0000
commitdb51f7f103bbbd6c91c8f47d75b3482ef8939691 (patch)
treeab59b1147bd0cd39f31a48073cff236ede4ec1df
parentAdding upstream version 3.0.0~a1. (diff)
downloadpendulum-upstream.tar.xz
pendulum-upstream.zip
Adding upstream version 3.0.0.upstream/3.0.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--.flake828
-rw-r--r--.github/workflows/codspeed.yml52
-rw-r--r--.github/workflows/release.yml98
-rw-r--r--.github/workflows/tests.yml31
-rw-r--r--.gitignore2
-rw-r--r--.pre-commit-config.yaml62
-rw-r--r--CHANGELOG.md39
-rw-r--r--Makefile47
-rw-r--r--README.rst22
-rwxr-xr-xbuild-wheels.sh27
-rw-r--r--build.py33
-rwxr-xr-xclock12
-rw-r--r--codecov.yml7
-rw-r--r--docs/docs/difference.md2
-rw-r--r--docs/docs/index.md2
-rw-r--r--docs/docs/installation.md20
-rw-r--r--docs/docs/interval.md (renamed from docs/docs/period.md)72
-rw-r--r--docs/docs/introduction.md2
-rw-r--r--docs/docs/limitations.md4
-rw-r--r--docs/docs/testing.md94
-rw-r--r--docs/docs/timezones.md2
-rw-r--r--meson.build20
-rw-r--r--pendulum/__version__.py1
-rw-r--r--pendulum/_extensions/_helpers.c931
-rw-r--r--pendulum/exceptions.py8
-rw-r--r--pendulum/parsing/_iso8601.c1361
-rw-r--r--pendulum/parsing/_iso8601.pyi22
-rw-r--r--pendulum/testing/traveller.py139
-rw-r--r--pendulum/utils/_compat.py13
-rw-r--r--poetry.lock1662
-rw-r--r--pyproject.toml176
-rw-r--r--rust/.cargo/config.toml15
-rw-r--r--rust/Cargo.lock318
-rw-r--r--rust/Cargo.toml22
-rw-r--r--rust/src/constants.rs56
-rw-r--r--rust/src/helpers.rs122
-rw-r--r--rust/src/lib.rs12
-rw-r--r--rust/src/parsing.rs905
-rw-r--r--rust/src/python/helpers.rs388
-rw-r--r--rust/src/python/mod.rs27
-rw-r--r--rust/src/python/parsing.rs117
-rw-r--r--rust/src/python/types/duration.rs59
-rw-r--r--rust/src/python/types/interval.rs46
-rw-r--r--rust/src/python/types/mod.rs7
-rw-r--r--rust/src/python/types/precise_diff.rs53
-rw-r--r--rust/src/python/types/timezone.rs52
-rw-r--r--src/pendulum/__init__.py (renamed from pendulum/__init__.py)121
-rw-r--r--src/pendulum/__version__.py4
-rw-r--r--src/pendulum/_helpers.py (renamed from pendulum/_extensions/helpers.py)43
-rw-r--r--src/pendulum/_pendulum.pyi (renamed from pendulum/_extensions/_helpers.pyi)37
-rw-r--r--src/pendulum/constants.py (renamed from pendulum/constants.py)7
-rw-r--r--src/pendulum/date.py (renamed from pendulum/date.py)180
-rw-r--r--src/pendulum/datetime.py (renamed from pendulum/datetime.py)339
-rw-r--r--src/pendulum/day.py13
-rw-r--r--src/pendulum/duration.py (renamed from pendulum/duration.py)79
-rw-r--r--src/pendulum/exceptions.py13
-rw-r--r--src/pendulum/formatting/__init__.py (renamed from pendulum/formatting/__init__.py)1
-rw-r--r--src/pendulum/formatting/difference_formatter.py (renamed from pendulum/formatting/difference_formatter.py)6
-rw-r--r--src/pendulum/formatting/formatter.py (renamed from pendulum/formatting/formatter.py)65
-rw-r--r--src/pendulum/helpers.py (renamed from pendulum/helpers.py)54
-rw-r--r--src/pendulum/interval.py (renamed from pendulum/interval.py)87
-rw-r--r--src/pendulum/locales/__init__.py (renamed from pendulum/_extensions/__init__.py)0
-rw-r--r--src/pendulum/locales/cs/__init__.py (renamed from pendulum/locales/__init__.py)0
-rw-r--r--src/pendulum/locales/cs/custom.py (renamed from pendulum/locales/cs/custom.py)2
-rw-r--r--src/pendulum/locales/cs/locale.py (renamed from pendulum/locales/cs/locale.py)66
-rw-r--r--src/pendulum/locales/da/__init__.py (renamed from pendulum/locales/cs/__init__.py)0
-rw-r--r--src/pendulum/locales/da/custom.py (renamed from pendulum/locales/da/custom.py)2
-rw-r--r--src/pendulum/locales/da/locale.py (renamed from pendulum/locales/da/locale.py)42
-rw-r--r--src/pendulum/locales/de/__init__.py (renamed from pendulum/locales/da/__init__.py)0
-rw-r--r--src/pendulum/locales/de/custom.py (renamed from pendulum/locales/de/custom.py)2
-rw-r--r--src/pendulum/locales/de/locale.py (renamed from pendulum/locales/de/locale.py)54
-rw-r--r--src/pendulum/locales/en/__init__.py (renamed from pendulum/locales/de/__init__.py)0
-rw-r--r--src/pendulum/locales/en/custom.py (renamed from pendulum/locales/en/custom.py)2
-rw-r--r--src/pendulum/locales/en/locale.py (renamed from pendulum/locales/en/locale.py)42
-rw-r--r--src/pendulum/locales/en_gb/__init__.py (renamed from pendulum/locales/en/__init__.py)0
-rw-r--r--src/pendulum/locales/en_gb/custom.py25
-rw-r--r--src/pendulum/locales/en_gb/locale.py240
-rw-r--r--src/pendulum/locales/en_us/__init__.py (renamed from pendulum/locales/es/__init__.py)0
-rw-r--r--src/pendulum/locales/en_us/custom.py25
-rw-r--r--src/pendulum/locales/en_us/locale.py240
-rw-r--r--src/pendulum/locales/es/__init__.py (renamed from pendulum/locales/fa/__init__.py)0
-rw-r--r--src/pendulum/locales/es/custom.py (renamed from pendulum/locales/es/custom.py)2
-rw-r--r--src/pendulum/locales/es/locale.py (renamed from pendulum/locales/es/locale.py)42
-rw-r--r--src/pendulum/locales/fa/__init__.py (renamed from pendulum/locales/fo/__init__.py)0
-rw-r--r--src/pendulum/locales/fa/custom.py (renamed from pendulum/locales/fa/custom.py)2
-rw-r--r--src/pendulum/locales/fa/locale.py (renamed from pendulum/locales/fa/locale.py)42
-rw-r--r--src/pendulum/locales/fo/__init__.py (renamed from pendulum/locales/fr/__init__.py)0
-rw-r--r--src/pendulum/locales/fo/custom.py (renamed from pendulum/locales/fo/custom.py)2
-rw-r--r--src/pendulum/locales/fo/locale.py (renamed from pendulum/locales/fo/locale.py)54
-rw-r--r--src/pendulum/locales/fr/__init__.py (renamed from pendulum/locales/he/__init__.py)0
-rw-r--r--src/pendulum/locales/fr/custom.py (renamed from pendulum/locales/fr/custom.py)2
-rw-r--r--src/pendulum/locales/fr/locale.py (renamed from pendulum/locales/fr/locale.py)42
-rw-r--r--src/pendulum/locales/he/__init__.py (renamed from pendulum/locales/id/__init__.py)0
-rw-r--r--src/pendulum/locales/he/custom.py (renamed from pendulum/locales/he/custom.py)2
-rw-r--r--src/pendulum/locales/he/locale.py (renamed from pendulum/locales/he/locale.py)66
-rw-r--r--src/pendulum/locales/id/__init__.py (renamed from pendulum/locales/it/__init__.py)0
-rw-r--r--src/pendulum/locales/id/custom.py (renamed from pendulum/locales/id/custom.py)2
-rw-r--r--src/pendulum/locales/id/locale.py (renamed from pendulum/locales/id/locale.py)54
-rw-r--r--src/pendulum/locales/it/__init__.py (renamed from pendulum/locales/ja/__init__.py)0
-rw-r--r--src/pendulum/locales/it/custom.py (renamed from pendulum/locales/it/custom.py)1
-rw-r--r--src/pendulum/locales/it/locale.py (renamed from pendulum/locales/it/locale.py)54
-rw-r--r--src/pendulum/locales/ja/__init__.py (renamed from pendulum/locales/ko/__init__.py)0
-rw-r--r--src/pendulum/locales/ja/custom.py (renamed from pendulum/locales/ja/custom.py)2
-rw-r--r--src/pendulum/locales/ja/locale.py (renamed from pendulum/locales/ja/locale.py)66
-rw-r--r--src/pendulum/locales/ko/__init__.py (renamed from pendulum/locales/lt/__init__.py)0
-rw-r--r--src/pendulum/locales/ko/custom.py (renamed from pendulum/locales/ko/custom.py)2
-rw-r--r--src/pendulum/locales/ko/locale.py (renamed from pendulum/locales/ko/locale.py)30
-rw-r--r--src/pendulum/locales/locale.py (renamed from pendulum/locales/locale.py)16
-rw-r--r--src/pendulum/locales/lt/__init__.py (renamed from pendulum/locales/nb/__init__.py)0
-rw-r--r--src/pendulum/locales/lt/custom.py (renamed from pendulum/locales/lt/custom.py)2
-rw-r--r--src/pendulum/locales/lt/locale.py (renamed from pendulum/locales/lt/locale.py)44
-rw-r--r--src/pendulum/locales/nb/__init__.py (renamed from pendulum/locales/nl/__init__.py)0
-rw-r--r--src/pendulum/locales/nb/custom.py (renamed from pendulum/locales/nb/custom.py)2
-rw-r--r--src/pendulum/locales/nb/locale.py (renamed from pendulum/locales/nb/locale.py)54
-rw-r--r--src/pendulum/locales/nl/__init__.py (renamed from pendulum/locales/nn/__init__.py)0
-rw-r--r--src/pendulum/locales/nl/custom.py (renamed from pendulum/locales/nl/custom.py)2
-rw-r--r--src/pendulum/locales/nl/locale.py (renamed from pendulum/locales/nl/locale.py)42
-rw-r--r--src/pendulum/locales/nn/__init__.py (renamed from pendulum/locales/pl/__init__.py)0
-rw-r--r--src/pendulum/locales/nn/custom.py (renamed from pendulum/locales/nn/custom.py)2
-rw-r--r--src/pendulum/locales/nn/locale.py (renamed from pendulum/locales/nn/locale.py)54
-rw-r--r--src/pendulum/locales/pl/__init__.py (renamed from pendulum/locales/pt_br/__init__.py)0
-rw-r--r--src/pendulum/locales/pl/custom.py (renamed from pendulum/locales/pl/custom.py)2
-rw-r--r--src/pendulum/locales/pl/locale.py (renamed from pendulum/locales/pl/locale.py)54
-rw-r--r--src/pendulum/locales/pt_br/__init__.py (renamed from pendulum/locales/ru/__init__.py)0
-rw-r--r--src/pendulum/locales/pt_br/custom.py (renamed from pendulum/locales/pt_br/custom.py)2
-rw-r--r--src/pendulum/locales/pt_br/locale.py (renamed from pendulum/locales/pt_br/locale.py)54
-rw-r--r--src/pendulum/locales/ru/__init__.py (renamed from pendulum/locales/sk/__init__.py)0
-rw-r--r--src/pendulum/locales/ru/custom.py (renamed from pendulum/locales/ru/custom.py)2
-rw-r--r--src/pendulum/locales/ru/locale.py (renamed from pendulum/locales/ru/locale.py)42
-rw-r--r--src/pendulum/locales/sk/__init__.py (renamed from pendulum/locales/sv/__init__.py)0
-rw-r--r--src/pendulum/locales/sk/custom.py (renamed from pendulum/locales/sk/custom.py)2
-rw-r--r--src/pendulum/locales/sk/locale.py (renamed from pendulum/locales/sk/locale.py)66
-rw-r--r--src/pendulum/locales/sv/__init__.py (renamed from pendulum/locales/zh/__init__.py)0
-rw-r--r--src/pendulum/locales/sv/custom.py (renamed from pendulum/locales/sv/custom.py)2
-rw-r--r--src/pendulum/locales/sv/locale.py (renamed from pendulum/locales/sv/locale.py)66
-rw-r--r--src/pendulum/locales/tr/__init__.py (renamed from pendulum/mixins/__init__.py)0
-rw-r--r--src/pendulum/locales/tr/custom.py24
-rw-r--r--src/pendulum/locales/tr/locale.py225
-rw-r--r--src/pendulum/locales/zh/__init__.py (renamed from pendulum/testing/__init__.py)0
-rw-r--r--src/pendulum/locales/zh/custom.py (renamed from pendulum/locales/zh/custom.py)2
-rw-r--r--src/pendulum/locales/zh/locale.py (renamed from pendulum/locales/zh/locale.py)42
-rw-r--r--src/pendulum/mixins/__init__.py (renamed from pendulum/tz/data/__init__.py)0
-rw-r--r--src/pendulum/mixins/default.py (renamed from pendulum/mixins/default.py)3
-rw-r--r--src/pendulum/parser.py (renamed from pendulum/parser.py)14
-rw-r--r--src/pendulum/parsing/__init__.py (renamed from pendulum/parsing/__init__.py)28
-rw-r--r--src/pendulum/parsing/exceptions/__init__.py (renamed from pendulum/parsing/exceptions/__init__.py)1
-rw-r--r--src/pendulum/parsing/iso8601.py (renamed from pendulum/parsing/iso8601.py)25
-rw-r--r--src/pendulum/py.typed (renamed from pendulum/py.typed)0
-rw-r--r--src/pendulum/testing/__init__.py (renamed from pendulum/utils/__init__.py)0
-rw-r--r--src/pendulum/testing/traveller.py172
-rw-r--r--src/pendulum/time.py (renamed from pendulum/time.py)42
-rw-r--r--src/pendulum/tz/__init__.py (renamed from pendulum/tz/__init__.py)24
-rw-r--r--src/pendulum/tz/data/__init__.py0
-rw-r--r--src/pendulum/tz/data/windows.py (renamed from pendulum/tz/data/windows.py)1
-rw-r--r--src/pendulum/tz/exceptions.py (renamed from pendulum/tz/exceptions.py)1
-rw-r--r--src/pendulum/tz/local_timezone.py (renamed from pendulum/tz/local_timezone.py)20
-rw-r--r--src/pendulum/tz/timezone.py (renamed from pendulum/tz/timezone.py)96
-rw-r--r--src/pendulum/utils/__init__.py0
-rw-r--r--src/pendulum/utils/_compat.py15
-rw-r--r--src/pendulum/utils/_zoneinfo.py80
-rw-r--r--tests/benchmarks/__init__.py0
-rw-r--r--tests/benchmarks/test_parse_8601.py51
-rw-r--r--tests/conftest.py51
-rw-r--r--tests/date/test_comparison.py2
-rw-r--r--tests/date/test_day_of_week_modifiers.py18
-rw-r--r--tests/date/test_diff.py2
-rw-r--r--tests/datetime/test_add.py20
-rw-r--r--tests/datetime/test_behavior.py8
-rw-r--r--tests/datetime/test_comparison.py2
-rw-r--r--tests/datetime/test_construct.py27
-rw-r--r--tests/datetime/test_day_of_week_modifiers.py18
-rw-r--r--tests/datetime/test_from_format.py35
-rw-r--r--tests/datetime/test_getters.py51
-rw-r--r--tests/datetime/test_start_end_of.py40
-rw-r--r--tests/datetime/test_strings.py10
-rw-r--r--tests/duration/test_behavior.py15
-rw-r--r--tests/formatting/test_formatter.py14
-rw-r--r--tests/interval/test_add_subtract.py20
-rw-r--r--tests/interval/test_behavior.py21
-rw-r--r--tests/interval/test_construct.py28
-rw-r--r--tests/interval/test_hashing.py20
-rw-r--r--tests/interval/test_in_words.py38
-rw-r--r--tests/interval/test_range.py2
-rw-r--r--tests/localization/test_cs.py1
-rw-r--r--tests/localization/test_da.py1
-rw-r--r--tests/localization/test_de.py1
-rw-r--r--tests/localization/test_es.py1
-rw-r--r--tests/localization/test_fa.py1
-rw-r--r--tests/localization/test_fo.py1
-rw-r--r--tests/localization/test_fr.py1
-rw-r--r--tests/localization/test_he.py1
-rw-r--r--tests/localization/test_id.py1
-rw-r--r--tests/localization/test_it.py1
-rw-r--r--tests/localization/test_ja.py1
-rw-r--r--tests/localization/test_ko.py1
-rw-r--r--tests/localization/test_lt.py1
-rw-r--r--tests/localization/test_nb.py1
-rw-r--r--tests/localization/test_nl.py1
-rw-r--r--tests/localization/test_nn.py1
-rw-r--r--tests/localization/test_pl.py1
-rw-r--r--tests/localization/test_ru.py1
-rw-r--r--tests/localization/test_sk.py1
-rw-r--r--tests/localization/test_sv.py1
-rw-r--r--tests/localization/test_tr.py66
-rw-r--r--tests/parsing/test_parse_iso8601.py485
-rw-r--r--tests/parsing/test_parsing.py15
-rw-r--r--tests/test_helpers.py57
-rw-r--r--tests/test_main.py52
-rw-r--r--tests/test_parsing.py53
-rw-r--r--tests/testing/test_time_travel.py1
-rw-r--r--tests/time/test_comparison.py2
-rw-r--r--tests/tz/test_helpers.py2
-rw-r--r--tests/tz/test_timezone.py10
213 files changed, 6723 insertions, 5306 deletions
diff --git a/.flake8 b/.flake8
deleted file mode 100644
index d571285..0000000
--- a/.flake8
+++ /dev/null
@@ -1,28 +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
- # F811: Redefinition of unused name from line n
- pendulum/tz/timezone.py:F811
-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/.github/workflows/codspeed.yml b/.github/workflows/codspeed.yml
new file mode 100644
index 0000000..fcc7ed3
--- /dev/null
+++ b/.github/workflows/codspeed.yml
@@ -0,0 +1,52 @@
+name: codspeed
+
+on:
+ push:
+ branches:
+ - "master"
+ pull_request:
+ # `workflow_dispatch` allows CodSpeed to trigger backtest
+ # performance analysis in order to generate initial data.
+ workflow_dispatch:
+
+jobs:
+ benchmarks:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-python@v3
+ with:
+ python-version: "3.9"
+
+ - name: Get full Python version
+ id: full-python-version
+ run: |
+ echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))")
+
+ - name: Install poetry
+ run: |
+ curl -fsS https://install.python-poetry.org | python - -y
+
+ - name: Update PATH
+ if: ${{ matrix.os != 'Windows' }}
+ run: echo "$HOME/.local/bin" >> $GITHUB_PATH
+
+ - name: Configure poetry
+ run: poetry config virtualenvs.create false
+
+ - name: Install dependencies
+ run: poetry install --only test --only benchmark --only build -vvv --no-root
+
+ - name: Install project
+ run: poetry install --only test --only benchmark --only build -vvv --no-root
+
+ - name: Install pendulum and check extensions
+ run: |
+ poetry run pip install -e . -vvv
+ python -c 'import pendulum._pendulum'
+
+ - name: Run benchmarks
+ uses: CodSpeedHQ/action@v1
+ with:
+ token: ${{ secrets.CODSPEED_TOKEN }}
+ run: pytest tests/ --codspeed
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 59062ae..5821407 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -4,34 +4,103 @@ on:
push:
tags:
- '*.*.*'
+ workflow_dispatch:
jobs:
-
build:
- name: Build wheels on ${{ matrix.os }}
- runs-on: ${{ matrix.os }}-latest
+ name: Build on ${{ matrix.platform || matrix.os }} (${{ matrix.target }} - ${{ matrix.manylinux || 'auto' }})
strategy:
+ fail-fast: false
matrix:
- os: [ ubuntu, windows, macos ]
+ os: [ubuntu, macos, windows]
+ target: [x86_64, aarch64]
+ manylinux: [auto]
+ include:
+ - os: ubuntu
+ platform: linux
+ - os: windows
+ ls: dir
+ interpreter: 3.7 3.8 3.9 3.10 3.11 3.12 pypy3.8 pypy3.9 pypy3.10
+ - os: windows
+ ls: dir
+ target: aarch64
+ interpreter: 3.11 3.12
+ - os: macos
+ target: aarch64
+ interpreter: 3.7 3.8 3.9 3.10 3.11 3.12 pypy3.8 pypy3.9 pypy3.10
+ - os: ubuntu
+ platform: linux
+ target: aarch64
+ # mimalloc not supported on manylinux2014 cross-compile container
+ extra-build-args: --no-default-features
+ # musllinux
+ - os: ubuntu
+ platform: linux
+ target: x86_64
+ manylinux: musllinux_1_1
+ - os: ubuntu
+ platform: linux
+ target: aarch64
+ manylinux: musllinux_1_1
+ - os: ubuntu
+ platform: linux
+ target: ppc64le
+ interpreter: 3.7 3.8 3.9 3.10 3.11 3.12
+ # mimalloc not supported on manylinux2014 cross-compile container
+ extra-build-args: --no-default-features
+ - os: ubuntu
+ platform: linux
+ target: s390x
+ interpreter: 3.7 3.8 3.9 3.10 3.11 3.12
+ # mimalloc not supported on manylinux2014 cross-compile container
+ extra-build-args: --no-default-features
+ runs-on: ${{ matrix.os }}-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- - name: Build wheels
- uses: pypa/cibuildwheel@v2.10.1
- env:
- CIBW_PROJECT_REQUIRES_PYTHON: ">=3.7"
+ - name: set up python
+ uses: actions/setup-python@v4
with:
- package-dir: .
- output-dir: dist
+ python-version: '3.11'
+ architecture: ${{ matrix.python-architecture || 'x64' }}
+
+ - name: build wheels
+ uses: PyO3/maturin-action@v1
+ with:
+ target: ${{ matrix.target }}
+ manylinux: ${{ matrix.manylinux || 'auto' }}
+ container: ${{ matrix.container }}
+ args: --release --out dist --interpreter ${{ matrix.interpreter || '3.7 3.8 3.9 3.10 3.11 3.12 pypy3.7 pypy3.8 pypy3.9 pypy3.10' }} ${{ matrix.extra-build-args }}
+ rust-toolchain: stable
+ docker-options: -e CI
+
+ - run: ${{ matrix.ls || 'ls -lh' }} dist/
- uses: actions/upload-artifact@v3
with:
name: dist
- path: ./dist/*
+ path: dist
+
+ build_sdist:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Build sdist
+ uses: PyO3/maturin-action@v1
+ with:
+ command: sdist
+ args: --out dist
+ - name: Upload sdist
+ uses: actions/upload-artifact@v3
+ with:
+ name: dist
+ path: dist
+
Release:
- needs: [ build ]
+ needs: [ build, build_sdist ]
+ if: success() && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
steps:
@@ -51,9 +120,6 @@ jobs:
- name: Update PATH
run: echo "$HOME/.local/bin" >> $GITHUB_PATH
- - name: Build sdist
- run: poetry build --format sdist
-
- name: Check distributions
run: |
ls -la dist
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 341859e..077a9c7 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -13,13 +13,27 @@ on:
- '**'
jobs:
+ Linting:
+ name: Linting
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-python@v4
+ with:
+ python-version: "3.11"
+ - name: "Install pre-commit"
+ run: pip install pre-commit
+ - name: "Install Rust toolchain"
+ run: rustup component add rustfmt clippy
+ - run: pre-commit run --all-files
+
Tests:
name: ${{ matrix.os }} / ${{ matrix.python-version }}
runs-on: ${{ matrix.os }}-latest
strategy:
matrix:
os: [Ubuntu, MacOS, Windows]
- python-version: [3.7, 3.8, 3.9, "3.10"]
+ python-version: [3.8, 3.9, "3.10", "3.11", "3.12"]
defaults:
run:
shell: bash
@@ -31,6 +45,7 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
+ allow-prereleases: true
- name: Get full Python version
id: full-python-version
@@ -64,8 +79,18 @@ jobs:
if: steps.cache.outputs.cache-hit == 'true' && matrix.os != 'MacOS'
run: timeout 10s poetry run pip --version || rm -rf .venv
- - name: Install dependencies
- run: poetry install --only main --only test -vvv
+ - name: Install runtime, testing, and typing dependencies
+ run: poetry install --only main --only test --only typing --only build --no-root -vvv
+
+ - name: Install project
+ run: poetry run maturin develop
+
+ - name: Run type checking
+ run: poetry run mypy
+
+ - name: Uninstall typing dependencies
+ # This ensures pendulum runs without typing_extensions installed
+ run: poetry install --only main --only test --only build --sync --no-root -vvv
- name: Test Pure Python
run: |
diff --git a/.gitignore b/.gitignore
index bb25f8b..dd3696f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,3 +35,5 @@ setup.py
# editor
.vscode
+/target
+/rust/target
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 0e8d094..3ec3be2 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,61 +11,21 @@ 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.7.0
hooks:
- id: black
- - repo: https://github.com/pycqa/isort
- rev: 5.10.1
- 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/astral-sh/ruff-pre-commit
+ rev: v0.0.291
hooks:
- - id: pyupgrade
- exclude: ^build\.py$
- args:
- - --py37-plus
+ - id: ruff
- - repo: https://github.com/pre-commit/mirrors-mypy
- rev: v0.982
+ - repo: local
hooks:
- - id: mypy
+ - id: lint-rust
+ name: Lint Rust
+ entry: make lint-rust
+ types: [rust]
+ language: rust
pass_filenames: false
- exclude: ^build\.py$
- additional_dependencies:
- - pytest>=7.1.2
- - types-backports
- - types-python-dateutil
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ae71991..4c90eff 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,40 @@
# Change Log
+## [3.0.0] - 2023-12-16
+
+### Changed
+
+- Relaxed dependency constraints. [#760](https://github.com/sdispater/pendulum/pull/760)
+- The testing helpers are now optional and must be opted-in via the `test` extra. [#778](https://github.com/sdispater/pendulum/pull/778)
+
+### Fixed
+
+- Removed remaining mentions of periods instead of intervals. [#757](https://github.com/sdispater/pendulum/pull/757)
+- Fixed the behavior of the `week_of_month` property for edge cases in January and December. [#774](https://github.com/sdispater/pendulum/pull/774)
+- Fixed the handling of the `fold` attribute when deep-copying a `DateTime` instance. [#776](https://github.com/sdispater/pendulum/pull/776)
+- Fixed errors where hours and days were not handled properly when adding durations. [#775](https://github.com/sdispater/pendulum/pull/775)
+- Fixed errors where hours and days were not handled properly when adding durations. [#775](https://github.com/sdispater/pendulum/pull/775)
+
+
+## [3.0.0b1] - 2023-10-01
+
+### Added
+
+- Made `instance()` support all native types (date, time, datetime). [#732](https://github.com/sdispater/pendulum/pull/732)
+
+### Changed
+
+- Dropped support for Python 3.7. [#734](https://github.com/sdispater/pendulum/pull/734)
+- Rewrote extensions in Rust. [#721](https://github.com/sdispater/pendulum/pull/721)
+- Made day of week convention more consistent across the codebase. [#731](https://github.com/sdispater/pendulum/pull/731)
+
+### Fixed
+
+- Fixed datetime string representation to match the native library. [#733](https://github.com/sdispater/pendulum/pull/733)
+- Fixed issues on some system when retrieving the local timezone. [#733](https://github.com/sdispater/pendulum/pull/733)
+- Fixed DST handling in `start_of()/end_of()` methods. [#713](https://github.com/sdispater/pendulum/pull/713)
+
+
## [3.0.0a1] - 2022-11-23
### Added
@@ -165,7 +200,9 @@
-[Unreleased]: https://github.com/sdispater/pendulum/compare/3.0.0a1...master
+[Unreleased]: https://github.com/sdispater/pendulum/compare/3.0.0...master
+[3.0.0]: https://github.com/sdispater/pendulum/releases/tag/3.0.0
+[3.0.0b1]: https://github.com/sdispater/pendulum/releases/tag/3.0.0b1
[3.0.0a1]: https://github.com/sdispater/pendulum/releases/tag/3.0.0a1
[2.1.1]: https://github.com/sdispater/pendulum/releases/tag/2.1.1
[2.1.0]: https://github.com/sdispater/pendulum/releases/tag/2.1.0
diff --git a/Makefile b/Makefile
index e68b94c..d6626b5 100644
--- a/Makefile
+++ b/Makefile
@@ -1,11 +1,3 @@
-# This file is part of orator
-# https://github.com/sdispater/orator
-
-# Licensed under the MIT license:
-# http://www.opensource.org/licenses/MIT-license
-# Copyright (c) 2015 Sébastien Eustace
-
-PENDULUM_RELEASE := $$(sed -n -E "s/VERSION = \"(.+)\"/\1/p" pendulum/version.py)
# lists all available targets
list:
@@ -16,38 +8,11 @@ list:
# required for list
no_targets__:
-# install all dependencies
-setup: setup-python
-
-# test your application (tests in the tests/ directory)
-test:
- @py.test --cov=pendulum --cov-config .coveragerc tests/ -sq
-
-linux_release: wheels_x64 wheels_i686
-
-release: wheels_x64 wheels_i686 wheel
-
-publish:
- @poetry publish --no-build
-
-tar:
- python setup.py sdist --formats=gztar
-
-wheel:
- @poetry build -v
-
-wheels_x64: build_wheels_x64
-
-wheels_i686: build_wheels_i686
-
-build_wheels_x64:
- docker pull quay.io/pypa/manylinux1_x86_64
- docker run --rm -v `pwd`:/io quay.io/pypa/manylinux1_x86_64 /io/build-wheels.sh
+lint-rust:
+ cd rust && cargo fmt --all -- --check
+ cd rust && cargo clippy --tests -- -D warnings
-build_wheels_i686:
- docker pull quay.io/pypa/manylinux1_i686
- docker run --rm -v `pwd`:/io quay.io/pypa/manylinux1_i686 /io/build-wheels.sh
-# run tests against all supported python versions
-tox:
- @tox
+format-rust:
+ cd rust && cargo fmt --all
+ cd rust && cargo clippy --tests --fix --allow-dirty -- -D warnings
diff --git a/README.rst b/README.rst
index 78437e2..65a0ad6 100644
--- a/README.rst
+++ b/README.rst
@@ -7,9 +7,6 @@ Pendulum
.. image:: https://img.shields.io/pypi/l/pendulum.svg
:target: https://pypi.python.org/pypi/pendulum
-.. image:: https://img.shields.io/codecov/c/github/sdispater/pendulum/master.svg
- :target: https://codecov.io/gh/sdispater/pendulum/branch/master
-
.. image:: https://github.com/sdispater/pendulum/actions/workflows/tests.yml/badge.svg
:alt: Pendulum Build status
:target: https://github.com/sdispater/pendulum/actions
@@ -17,7 +14,7 @@ Pendulum
Python datetimes made easy.
-Supports Python **2.7** and **3.4+**.
+Supports Python **3.8 and newer**.
.. code-block:: python
@@ -56,6 +53,13 @@ Supports Python **2.7** and **3.4+**.
'2013-03-31T03:00:00+02:00'
+Resources
+=========
+
+* `Official Website <https://pendulum.eustace.io>`_
+* `Documentation <https://pendulum.eustace.io/docs/>`_
+* `Issue Tracker <https://github.com/sdispater/pendulum/issues>`_
+
Why Pendulum?
=============
@@ -66,7 +70,7 @@ So it's still ``datetime`` but better.
Unlike other datetime libraries for Python, Pendulum is a drop-in replacement
for the standard ``datetime`` class (it inherits from it), so, basically, you can replace all your ``datetime``
-instances by ``DateTime`` instances in you code (exceptions exist for libraries that check
+instances by ``DateTime`` instances in your code (exceptions exist for libraries that check
the type of the objects by using the ``type`` function like ``sqlite3`` or ``PyMySQL`` for instance).
It also removes the notion of naive datetimes: each ``Pendulum`` instance is timezone-aware
@@ -121,14 +125,6 @@ a possible solution, if any:
return '' if val is None else val.isoformat()
-Resources
-=========
-
-* `Official Website <https://pendulum.eustace.io>`_
-* `Documentation <https://pendulum.eustace.io/docs/>`_
-* `Issue Tracker <https://github.com/sdispater/pendulum/issues>`_
-
-
Contributing
============
diff --git a/build-wheels.sh b/build-wheels.sh
deleted file mode 100755
index 1c8f1cb..0000000
--- a/build-wheels.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-set -e -x
-
-cd $(dirname $0)
-
-export PATH=/opt/python/cp38-cp38/bin/:$PATH
-
-curl -fsS -o get-poetry.py https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py
-/opt/python/cp38-cp38/bin/python get-poetry.py --preview -y
-rm get-poetry.py
-
-for PYBIN in /opt/python/cp3*/bin; do
- if [ "$PYBIN" == "/opt/python/cp34-cp34m/bin" ]; then
- continue
- fi
- if [ "$PYBIN" == "/opt/python/cp35-cp35m/bin" ]; then
- continue
- fi
- rm -rf build
- "${PYBIN}/python" $HOME/.poetry/bin/poetry build -vvv
-done
-
-cd dist
-for whl in *.whl; do
- auditwheel repair "$whl"
- rm "$whl"
-done
diff --git a/build.py b/build.py
deleted file mode 100644
index 95e63e1..0000000
--- a/build.py
+++ /dev/null
@@ -1,33 +0,0 @@
-import subprocess
-
-from pathlib import Path
-
-
-def meson(*args):
- subprocess.call(["meson"] + list(args))
-
-
-def _build():
- build_dir = Path(__file__).parent.joinpath("build")
- build_dir.mkdir(parents=True, exist_ok=True)
-
- meson("setup", build_dir.as_posix())
- meson("compile", "-C", build_dir.as_posix())
- meson("install", "-C", build_dir.as_posix())
-
-
-def build(setup_kwargs):
- """
- This function is mandatory in order to build the extensions.
- """
- try:
- _build()
- except Exception:
- print(
- " Unable to build C extensions, "
- "Pendulum will use the pure python version of the extensions."
- )
-
-
-if __name__ == "__main__":
- build({})
diff --git a/clock b/clock
index 17f35a3..acd8c55 100755
--- a/clock
+++ b/clock
@@ -47,7 +47,6 @@ class _LambdaCompiler(_GettextCompiler):
class LocaleCreate(Command):
-
name = "locale create"
description = "Creates locale translations."
@@ -113,8 +112,8 @@ translations = {{}}
data["days"] = {}
for fmt, names in days.items():
data["days"][fmt] = {}
- for value, name in names.items():
- data["days"][fmt][(value + 1) % 7] = name
+ for value, name in sorted(names.items()):
+ data["days"][fmt][value] = name
# Getting months names
months = content["months"]["format"]
@@ -159,6 +158,9 @@ translations = {{}}
# Day periods
data["day_periods"] = content["day_periods"]["format"]["wide"]
+ # Week data
+ data["week_data"] = content["week_data"]
+
result = self.TEMPLATE.format(
locale=locale,
plural=plural,
@@ -229,7 +231,6 @@ translations = {{}}
class LocaleRecreate(Command):
-
name = "locale recreate"
description = "Recreate existing locales."
@@ -240,11 +241,10 @@ class LocaleRecreate(Command):
locales = glob.glob(os.path.join(locales_dir, "*", "locale.py"))
locales = [os.path.basename(os.path.dirname(locale)) for locale in locales]
- self.call("locale:create", [("locales", locales)])
+ self.call("locale create", "locales " + " ".join(locales))
class WindowsTzDump(Command):
-
name = "windows dump-timezones"
description = "Dumps the mapping of Windows timezones to IANA timezones."
diff --git a/codecov.yml b/codecov.yml
deleted file mode 100644
index 3cbba28..0000000
--- a/codecov.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-comment: false
-
-coverage:
- status:
- patch:
- default:
- enabled: false
diff --git a/docs/docs/difference.md b/docs/docs/difference.md
index 3a7f063..2653f01 100644
--- a/docs/docs/difference.md
+++ b/docs/docs/difference.md
@@ -1,6 +1,6 @@
# Difference
-The `diff()` method returns a [Period](#period) instance that represents the total duration
+The `diff()` method returns an [Interval](#interval) instance that represents the total duration
between two `DateTime` instances. This interval can be then expressed in various units.
These interval methods always return *the total difference expressed* in the specified time requested.
All values are truncated and not rounded.
diff --git a/docs/docs/index.md b/docs/docs/index.md
index daca205..107e043 100644
--- a/docs/docs/index.md
+++ b/docs/docs/index.md
@@ -12,6 +12,6 @@
{!docs/modifiers.md!}
{!docs/timezones.md!}
{!docs/duration.md!}
-{!docs/period.md!}
+{!docs/interval.md!}
{!docs/testing.md!}
{!docs/limitations.md!}
diff --git a/docs/docs/installation.md b/docs/docs/installation.md
index bfa9641..9f80e87 100644
--- a/docs/docs/installation.md
+++ b/docs/docs/installation.md
@@ -11,3 +11,23 @@ or, if you are using [poetry](https://python-poetry.org):
```bash
$ poetry add pendulum
```
+
+## Optional features
+
+Pendulum provides optional features that you must explicitly require in order to use them.
+
+These optional features are:
+
+- `test`: Provides a set of helpers to make testing easier by allowing you to control the flow of time.
+
+You can install them by specifying them when installing Pendulum
+
+```bash
+$ pip install pendulum[test]
+```
+
+or, if you are using [poetry](https://python-poetry.org):
+
+```bash
+$ poetry add pendulum[test]
+```
diff --git a/docs/docs/period.md b/docs/docs/interval.md
index a4dde16..fc70fb3 100644
--- a/docs/docs/period.md
+++ b/docs/docs/interval.md
@@ -1,6 +1,6 @@
-# Period
+# Interval
-When you subtract a `DateTime` instance from another, or use the `diff()` method, it will return a `Period` instance.
+When you subtract a `DateTime` instance from another, or use the `diff()` method, it will return an `Interval` instance.
It inherits from the [Duration](#duration) class with the added benefit that it is aware of the
instances that generated it, so that it can give access to more methods and properties:
@@ -10,29 +10,29 @@ instances that generated it, so that it can give access to more methods and prop
>>> start = pendulum.datetime(2000, 11, 20)
>>> end = pendulum.datetime(2016, 11, 5)
->>> period = end - start
+>>> interval = end - start
->>> period.years
+>>> interval.years
15
->>> period.months
+>>> interval.months
11
->>> period.in_years()
+>>> interval.in_years()
15
->>> period.in_months()
+>>> interval.in_months()
191
# Note that the weeks property
# will change compared to the Duration class
->>> period.weeks
+>>> interval.weeks
2 # 832 for the duration
# However the days property will still remain the same
# to keep the compatibility with the timedelta class
->>> period.days
+>>> interval.days
5829
```
-Be aware that a period, just like an interval, is compatible with the `timedelta` class regarding
+Be aware that an interval, just like an duration, is compatible with the `timedelta` class regarding
its attributes. However, its custom attributes (like `remaining_days`) will be aware of any DST
transitions that might have occurred and adjust accordingly. Let's take an example:
@@ -42,42 +42,42 @@ transitions that might have occurred and adjust accordingly. Let's take an examp
>>> start = pendulum.datetime(2017, 3, 7, tz='America/Toronto')
>>> end = start.add(days=6)
->>> period = end - start
+>>> interval = end - start
# timedelta properties
->>> period.days
+>>> interval.days
5
->>> period.seconds
+>>> interval.seconds
82800
-# period properties
->>> period.remaining_days
+# interval properties
+>>> interval.remaining_days
6
->>> period.hours
+>>> interval.hours
0
->>> period.remaining_seconds
+>>> interval.remaining_seconds
0
```
!!!warning
Due to their nature (fixed duration between two datetimes), most arithmetic operations will
- return a `Duration` instead of a `Period`.
+ return a `Duration` instead of an `Interval`.
```python
>>> import pendulum
>>> dt1 = pendulum.datetime(2016, 8, 7, 12, 34, 56)
>>> dt2 = dt1.add(days=6, seconds=34)
- >>> period = pendulum.period(dt1, dt2)
- >>> period * 2
+ >>> interval = pendulum.interval(dt1, dt2)
+ >>> interval * 2
Duration(weeks=1, days=5, minutes=1, seconds=8)
```
## Instantiation
-You can create an instance by using the `period()` helper:
+You can create an instance by using the `interval()` helper:
```python
@@ -86,29 +86,29 @@ You can create an instance by using the `period()` helper:
>>> start = pendulum.datetime(2000, 1, 1)
>>> end = pendulum.datetime(2000, 1, 31)
->>> period = pendulum.period(start, end)
+>>> interval = pendulum.interval(start, end)
```
-You can also make an inverted period:
+You can also make an inverted interval:
```python
->>> period = pendulum.period(end, start)
->>> period.remaining_days
+>>> interval = pendulum.interval(end, start)
+>>> interval.remaining_days
-2
```
-If you have inverted dates but want to make sure that the period is positive,
+If you have inverted dates but want to make sure that the interval is positive,
you should set the `absolute` keyword argument to `True`:
```python
->>> period = pendulum.period(end, start, absolute=True)
->>> period.remaining_days
+>>> interval = pendulum.interval(end, start, absolute=True)
+>>> interval.remaining_days
2
```
## Range
-If you want to iterate over a period, you can use the `range()` method:
+If you want to iterate over a interval, you can use the `range()` method:
```python
>>> import pendulum
@@ -116,9 +116,9 @@ If you want to iterate over a period, you can use the `range()` method:
>>> start = pendulum.datetime(2000, 1, 1)
>>> end = pendulum.datetime(2000, 1, 10)
->>> period = pendulum.period(start, end)
+>>> interval = pendulum.interval(start, end)
->>> for dt in period.range('days'):
+>>> for dt in interval.range('days'):
>>> print(dt)
'2000-01-01T00:00:00+00:00'
@@ -141,7 +141,7 @@ If you want to iterate over a period, you can use the `range()` method:
You can pass an amount for the passed unit to control the length of the gap:
```python
->>> for dt in period.range('days', 2):
+>>> for dt in interval.range('days', 2):
>>> print(dt)
'2000-01-01T00:00:00+00:00'
@@ -151,18 +151,18 @@ You can pass an amount for the passed unit to control the length of the gap:
'2000-01-09T00:00:00+00:00'
```
-You can also directly iterate over the `Period` instance,
+You can also directly iterate over the `Interval` instance,
the unit will be `days` in this case:
```python
->>> for dt in period:
+>>> for dt in interval:
>>> print(dt)
```
-You can check if a `DateTime` instance is inside a period using the `in` keyword:
+You can check if a `DateTime` instance is inside a interval using the `in` keyword:
```python
>>> dt = pendulum.datetime(2000, 1, 4)
->>> dt in period
+>>> dt in interval
True
```
diff --git a/docs/docs/introduction.md b/docs/docs/introduction.md
index fbbab97..0078b48 100644
--- a/docs/docs/introduction.md
+++ b/docs/docs/introduction.md
@@ -18,4 +18,4 @@ For example, all comparisons are done in `UTC` or in the timezone of the datetim
3
```
-The default timezone, except when using the `now()`, method will always be `UTC`.
+The default timezone, except when using the `now()` method, will always be `UTC`.
diff --git a/docs/docs/limitations.md b/docs/docs/limitations.md
index 7deff23..913aca1 100644
--- a/docs/docs/limitations.md
+++ b/docs/docs/limitations.md
@@ -4,7 +4,7 @@ Even though the `DateTime` class is a subclass of `datetime`,
there are some rare cases where it can't replace the native class directly.
Here is a list (non-exhaustive) of the reported cases with a possible solution, if any:
-* `sqlite3` will use the the `type()` function to determine the type of the object by default. To work around it you can register a new adapter:
+* `sqlite3` will use the `type()` function to determine the type of the object by default. To work around it you can register a new adapter:
```python
import pendulum
@@ -13,7 +13,7 @@ Here is a list (non-exhaustive) of the reported cases with a possible solution,
register_adapter(pendulum.DateTime, lambda val: val.isoformat(' '))
```
-* `mysqlclient` (former `MySQLdb`) and `PyMySQL` will use the the `type()` function to determine the type of the object by default. To work around it you can register a new adapter:
+* `mysqlclient` (former `MySQLdb`) and `PyMySQL` will use the `type()` function to determine the type of the object by default. To work around it you can register a new adapter:
```python
import pendulum
diff --git a/docs/docs/testing.md b/docs/docs/testing.md
index dfca054..25aad8d 100644
--- a/docs/docs/testing.md
+++ b/docs/docs/testing.md
@@ -1,59 +1,87 @@
# Testing
-The testing methods allow you to set a `DateTime` instance (real or mock) to be returned
-when a "now" instance is created.
-The provided instance will be returned specifically under the following conditions:
+Pendulum provides a few helpers to help you control the flow of time in your tests. Note that
+these helpers are only available if you opted in the `test` extra during [installation](#installation).
-* A call to the `now()` method, ex. `pendulum.now()`.
-* When the string "now" is passed to the `parse()` method, ex. `pendulum.parse('now')`
+!!!warning
+ If you are migrating from Pendulum 2, note that the `set_test_now()` and `test()`
+ helpers have been removed.
+
+
+## Relative time travel
+
+You can travel in time relatively to the current time
```python
>>> import pendulum
-# Create testing datetime
->>> known = pendulum.datetime(2001, 5, 21, 12)
+>>> now = pendulum.now()
+>>> pendulum.travel(minutes=5)
+>>> pendulum.now().diff_for_humans(now)
+"5 minutes after"
+```
-# Set the mock
->>> pendulum.set_test_now(known)
+Note that once you've travelled in time the clock **keeps ticking**. If you prefer to stop the time completely
+you can use the `freeze` parameter:
->>> print(pendulum.now())
-'2001-05-21T12:00:00+00:00'
+```python
+>>> import pendulum
->>> print(pendulum.parse('now'))
-'2001-05-21T12:00:00+00:00'
+>>> now = pendulum.now()
+>>> pendulum.travel(minutes=5, freeze=True)
+>>> pendulum.now().diff_for_humans(now)
+"5 minutes after" # This will stay like this indefinitely
+```
-# Clear the mock
->>> pendulum.set_test_now()
->>> print(pendulum.now())
-'2016-07-10T22:10:33.954851-05:00'
-```
+## Absolute time travel
-Related methods will also return values mocked according to the **now** instance.
+Sometimes, you may want to place yourself at a specific point in time.
+This is possible by using the `travel_to()` helper. This helper accepts a `DateTime` instance
+that represents the point in time where you want to travel to.
```python
->>> print(pendulum.today())
-'2001-05-21T00:00:00+00:00'
+>>> import pendulum
->>> print(pendulum.tomorrow())
-'2001-05-22T00:00:00+00:00'
+>>> pendulum.travel_to(pendulum.yesterday())
+```
->>> print(pendulum.yesterday())
-'2001-05-20T00:00:00+00:00'
+Similarly to `travel`, it's important to remember that, by default, the time keeps ticking so, if you prefer
+stopping the time, use the `freeze` parameter:
+
+```python
+>>> import pendulum
+
+>>> pendulum.travel_to(pendulum.yesterday(), freeze=True)
```
-If you don't want to manually clear the mock (or you are afraid of forgetting),
-you can use the provided `test()` contextmanager.
+## Travelling back to the present
+
+Using any of the travel helpers will keep you in the past, or future, until you decide
+to travel back to the present time. To do so, you may use the `travel_back()` helper.
```python
>>> import pendulum
->>> known = pendulum.datetime(2001, 5, 21, 12)
+>>> now = pendulum.now()
+>>> pendulum.travel(minutes=5, freeze=True)
+>>> pendulum.now().diff_for_humans(now)
+"5 minutes after"
+>>> pendulum.travel_back()
+>>> pendulum.now().diff_for_humans(now)
+"a few seconds after"
+```
+
+However, it might be cumbersome to remember to travel back so, instead, you can use any of the helpers as a context
+manager:
->>> with pendulum.test(known):
->>> print(pendulum.now())
-'2001-05-21T12:00:00+00:00'
+```python
+>>> import pendulum
->>> print(pendulum.now())
-'2016-07-10T22:10:33.954851-05:00'
+>>> now = pendulum.now()
+>>> with pendulum.travel(minutes=5, freeze=True):
+>>> pendulum.now().diff_for_humans(now)
+"5 minutes after"
+>>> pendulum.now().diff_for_humans(now)
+"a few seconds after"
```
diff --git a/docs/docs/timezones.md b/docs/docs/timezones.md
index 85ff147..e70034e 100644
--- a/docs/docs/timezones.md
+++ b/docs/docs/timezones.md
@@ -67,7 +67,7 @@ adopt the proper behavior and apply the transition accordingly.
>>> dt = dt.add(microseconds=1)
'2013-03-31T03:00:00+02:00'
>>> dt.subtract(microseconds=1)
-'2013-03-31T01:59:59.999998+01:00'
+'2013-03-31T01:59:59.999999+01:00'
>>> dt = pendulum.datetime(2013, 10, 27, 2, 59, 59, 999999,
tz='Europe/Paris',
diff --git a/meson.build b/meson.build
deleted file mode 100644
index 666c281..0000000
--- a/meson.build
+++ /dev/null
@@ -1,20 +0,0 @@
-project('pendulum C extensions', 'c')
-
-py_mod = import('python')
-py = py_mod.find_installation()
-py_dep = py.dependency()
-
-extensions = [
- ['_helpers', 'pendulum/_extensions/_helpers.c', meson.source_root() / 'pendulum/_extensions/'],
- ['_iso8601', 'pendulum/parsing/_iso8601.c', meson.source_root() / 'pendulum/parsing/'],
-]
-
-foreach extension : extensions
- py.extension_module(
- extension[0],
- extension[1],
- dependencies : py_dep,
- install : true,
- install_dir: extension[2]
- )
-endforeach
diff --git a/pendulum/__version__.py b/pendulum/__version__.py
deleted file mode 100644
index 29e86a8..0000000
--- a/pendulum/__version__.py
+++ /dev/null
@@ -1 +0,0 @@
-__version__ = "3.0.0a"
diff --git a/pendulum/_extensions/_helpers.c b/pendulum/_extensions/_helpers.c
deleted file mode 100644
index a3114d9..0000000
--- a/pendulum/_extensions/_helpers.c
+++ /dev/null
@@ -1,931 +0,0 @@
-/* ------------------------------------------------------------------------- */
-
-#include <Python.h>
-#include <datetime.h>
-#include <structmember.h>
-#include <math.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
-
-/* ------------------------------------------------------------------------- */
-
-#define EPOCH_YEAR 1970
-
-#define DAYS_PER_N_YEAR 365
-#define DAYS_PER_L_YEAR 366
-
-#define USECS_PER_SEC 1000000
-
-#define SECS_PER_MIN 60
-#define SECS_PER_HOUR (60 * SECS_PER_MIN)
-#define SECS_PER_DAY (SECS_PER_HOUR * 24)
-
-// 400-year chunks always have 146097 days (20871 weeks).
-#define DAYS_PER_400_YEARS 146097L
-#define SECS_PER_400_YEARS ((int64_t)DAYS_PER_400_YEARS * (int64_t)SECS_PER_DAY)
-
-// The number of seconds in an aligned 100-year chunk, for those that
-// do not begin with a leap year and those that do respectively.
-const int64_t SECS_PER_100_YEARS[2] = {
- (uint64_t)(76L * DAYS_PER_N_YEAR + 24L * DAYS_PER_L_YEAR) * SECS_PER_DAY,
- (uint64_t)(75L * DAYS_PER_N_YEAR + 25L * DAYS_PER_L_YEAR) * SECS_PER_DAY};
-
-// The number of seconds in an aligned 4-year chunk, for those that
-// do not begin with a leap year and those that do respectively.
-const int32_t SECS_PER_4_YEARS[2] = {
- (4 * DAYS_PER_N_YEAR + 0 * DAYS_PER_L_YEAR) * SECS_PER_DAY,
- (3 * DAYS_PER_N_YEAR + 1 * DAYS_PER_L_YEAR) * SECS_PER_DAY};
-
-// The number of seconds in non-leap and leap years respectively.
-const int32_t SECS_PER_YEAR[2] = {
- DAYS_PER_N_YEAR * SECS_PER_DAY,
- DAYS_PER_L_YEAR *SECS_PER_DAY};
-
-#define MONTHS_PER_YEAR 12
-
-// The month lengths in non-leap and leap years respectively.
-const int32_t DAYS_PER_MONTHS[2][13] = {
- {-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
- {-1, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}};
-
-// The day offsets of the beginning of each (1-based) month in non-leap
-// and leap years respectively.
-// For example, in a leap year there are 335 days before December.
-const int32_t MONTHS_OFFSETS[2][14] = {
- {-1, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
- {-1, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}};
-
-const int DAY_OF_WEEK_TABLE[12] = {
- 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
-
-#define TM_SUNDAY 0
-#define TM_MONDAY 1
-#define TM_TUESDAY 2
-#define TM_WEDNESDAY 3
-#define TM_THURSDAY 4
-#define TM_FRIDAY 5
-#define TM_SATURDAY 6
-
-#define TM_JANUARY 0
-#define TM_FEBRUARY 1
-#define TM_MARCH 2
-#define TM_APRIL 3
-#define TM_MAY 4
-#define TM_JUNE 5
-#define TM_JULY 6
-#define TM_AUGUST 7
-#define TM_SEPTEMBER 8
-#define TM_OCTOBER 9
-#define TM_NOVEMBER 10
-#define TM_DECEMBER 11
-
-/* ------------------------------------------------------------------------- */
-
-int _p(int y)
-{
- return y + y / 4 - y / 100 + y / 400;
-}
-
-int _is_leap(int year)
-{
- return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
-}
-
-int _is_long_year(int year)
-{
- return (_p(year) % 7 == 4) || (_p(year - 1) % 7 == 3);
-}
-
-int _week_day(int year, int month, int day)
-{
- int y;
- int w;
-
- y = year - (month < 3);
-
- w = (_p(y) + DAY_OF_WEEK_TABLE[month - 1] + day) % 7;
-
- if (!w)
- {
- w = 7;
- }
-
- return w;
-}
-
-int _days_in_year(int year)
-{
- if (_is_leap(year))
- {
- return DAYS_PER_L_YEAR;
- }
-
- return DAYS_PER_N_YEAR;
-}
-
-int _day_number(int year, int month, int day)
-{
- month = (month + 9) % 12;
- year = year - month / 10;
-
- return (
- 365 * year + year / 4 - year / 100 + year / 400 + (month * 306 + 5) / 10 + (day - 1));
-}
-
-int _get_offset(PyObject *dt)
-{
- PyObject *tzinfo;
- PyObject *offset;
-
- tzinfo = ((PyDateTime_DateTime *)(dt))->tzinfo;
-
- if (tzinfo != Py_None)
- {
- offset = PyObject_CallMethod(tzinfo, "utcoffset", "O", dt);
-
- return PyDateTime_DELTA_GET_DAYS(offset) * SECS_PER_DAY + PyDateTime_DELTA_GET_SECONDS(offset);
- }
-
- return 0;
-}
-
-int _has_tzinfo(PyObject *dt)
-{
- return ((_PyDateTime_BaseTZInfo *)(dt))->hastzinfo;
-}
-
-char *_get_tz_name(PyObject *dt)
-{
- PyObject *tzinfo;
- char *tz = "";
-
- tzinfo = ((PyDateTime_DateTime *)(dt))->tzinfo;
-
- if (tzinfo != Py_None)
- {
- if (PyObject_HasAttrString(tzinfo, "key"))
- {
- // zoneinfo timezone
- tz = (char *)PyUnicode_AsUTF8(
- PyObject_GetAttrString(tzinfo, "name"));
- }
- else if (PyObject_HasAttrString(tzinfo, "name"))
- {
- // Pendulum timezone
- tz = (char *)PyUnicode_AsUTF8(
- PyObject_GetAttrString(tzinfo, "name"));
- }
- else if (PyObject_HasAttrString(tzinfo, "zone"))
- {
- // pytz timezone
- tz = (char *)PyUnicode_AsUTF8(
- PyObject_GetAttrString(tzinfo, "zone"));
- }
- }
-
- return tz;
-}
-
-/* ------------------------ Custom Types ------------------------------- */
-
-/*
- * class Diff():
- */
-typedef struct
-{
- PyObject_HEAD int years;
- int months;
- int days;
- int hours;
- int minutes;
- int seconds;
- int microseconds;
- int total_days;
-} Diff;
-
-/*
- * def __init__(self, years, months, days, hours, minutes, seconds, microseconds, total_days):
- * self.years = years
- * # ...
-*/
-static int Diff_init(Diff *self, PyObject *args, PyObject *kwargs)
-{
- int years;
- int months;
- int days;
- int hours;
- int minutes;
- int seconds;
- int microseconds;
- int total_days;
-
- if (!PyArg_ParseTuple(args, "iiiiiii", &years, &months, &days, &hours, &minutes, &seconds, &microseconds, &total_days))
- return -1;
-
- self->years = years;
- self->months = months;
- self->days = days;
- self->hours = hours;
- self->minutes = minutes;
- self->seconds = seconds;
- self->microseconds = microseconds;
- self->total_days = total_days;
-
- return 0;
-}
-
-/*
- * def __repr__(self):
- * return '{} years {} months {} days {} hours {} minutes {} seconds {} microseconds'.format(
- * self.years, self.months, self.days, self.minutes, self.hours, self.seconds, self.microseconds
- * )
- */
-static PyObject *Diff_repr(Diff *self)
-{
- return PyUnicode_FromFormat(
- "%d years %d months %d days %d hours %d minutes %d seconds %d microseconds",
- self->years,
- self->months,
- self->days,
- self->hours,
- self->minutes,
- self->seconds,
- self->microseconds);
-}
-
-/*
- * Instantiate new Diff_type object
- * Skip overhead of calling PyObject_New and PyObject_Init.
- * Directly allocate object.
- */
-static PyObject *new_diff_ex(int years, int months, int days, int hours, int minutes, int seconds, int microseconds, int total_days, PyTypeObject *type)
-{
- Diff *self = (Diff *)(type->tp_alloc(type, 0));
-
- if (self != NULL)
- {
- self->years = years;
- self->months = months;
- self->days = days;
- self->hours = hours;
- self->minutes = minutes;
- self->seconds = seconds;
- self->microseconds = microseconds;
- self->total_days = total_days;
- }
-
- return (PyObject *)self;
-}
-
-/*
- * Class member / class attributes
- */
-static PyMemberDef Diff_members[] = {
- {"years", T_INT, offsetof(Diff, years), 0, "years in diff"},
- {"months", T_INT, offsetof(Diff, months), 0, "months in diff"},
- {"days", T_INT, offsetof(Diff, days), 0, "days in diff"},
- {"hours", T_INT, offsetof(Diff, hours), 0, "hours in diff"},
- {"minutes", T_INT, offsetof(Diff, minutes), 0, "minutes in diff"},
- {"seconds", T_INT, offsetof(Diff, seconds), 0, "seconds in diff"},
- {"microseconds", T_INT, offsetof(Diff, microseconds), 0, "microseconds in diff"},
- {"total_days", T_INT, offsetof(Diff, total_days), 0, "total days in diff"},
- {NULL}};
-
-static PyTypeObject Diff_type = {
- PyVarObject_HEAD_INIT(NULL, 0) "PreciseDiff", /* tp_name */
- sizeof(Diff), /* tp_basicsize */
- 0, /* tp_itemsize */
- 0, /* tp_dealloc */
- 0, /* tp_print */
- 0, /* tp_getattr */
- 0, /* tp_setattr */
- 0, /* tp_as_async */
- (reprfunc)Diff_repr, /* tp_repr */
- 0, /* tp_as_number */
- 0, /* tp_as_sequence */
- 0, /* tp_as_mapping */
- 0, /* tp_hash */
- 0, /* tp_call */
- (reprfunc)Diff_repr, /* tp_str */
- 0, /* tp_getattro */
- 0, /* tp_setattro */
- 0, /* tp_as_buffer */
- Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
- "Precise difference between two datetime objects", /* tp_doc */
-};
-
-#define new_diff(years, months, days, hours, minutes, seconds, microseconds, total_days) new_diff_ex(years, months, days, hours, minutes, seconds, microseconds, total_days, &Diff_type)
-
-/* -------------------------- Functions --------------------------*/
-
-PyObject *is_leap(PyObject *self, PyObject *args)
-{
- PyObject *leap;
- int year;
-
- if (!PyArg_ParseTuple(args, "i", &year))
- {
- PyErr_SetString(
- PyExc_ValueError, "Invalid parameters");
- return NULL;
- }
-
- leap = PyBool_FromLong(_is_leap(year));
-
- return leap;
-}
-
-PyObject *is_long_year(PyObject *self, PyObject *args)
-{
- PyObject *is_long;
- int year;
-
- if (!PyArg_ParseTuple(args, "i", &year))
- {
- PyErr_SetString(
- PyExc_ValueError, "Invalid parameters");
- return NULL;
- }
-
- is_long = PyBool_FromLong(_is_long_year(year));
-
- return is_long;
-}
-
-PyObject *week_day(PyObject *self, PyObject *args)
-{
- PyObject *wd;
- int year;
- int month;
- int day;
-
- if (!PyArg_ParseTuple(args, "iii", &year, &month, &day))
- {
- PyErr_SetString(
- PyExc_ValueError, "Invalid parameters");
- return NULL;
- }
-
- wd = PyLong_FromLong(_week_day(year, month, day));
-
- return wd;
-}
-
-PyObject *days_in_year(PyObject *self, PyObject *args)
-{
- PyObject *ndays;
- int year;
-
- if (!PyArg_ParseTuple(args, "i", &year))
- {
- PyErr_SetString(
- PyExc_ValueError, "Invalid parameters");
- return NULL;
- }
-
- ndays = PyLong_FromLong(_days_in_year(year));
-
- return ndays;
-}
-
-PyObject *timestamp(PyObject *self, PyObject *args)
-{
- int64_t result;
- PyObject *dt;
-
- if (!PyArg_ParseTuple(args, "O", &dt))
- {
- PyErr_SetString(
- PyExc_ValueError, "Invalid parameters");
- return NULL;
- }
-
- int year = (double)PyDateTime_GET_YEAR(dt);
- int month = PyDateTime_GET_MONTH(dt);
- int day = PyDateTime_GET_DAY(dt);
- int hour = PyDateTime_DATE_GET_HOUR(dt);
- int minute = PyDateTime_DATE_GET_MINUTE(dt);
- int second = PyDateTime_DATE_GET_SECOND(dt);
-
- result = (year - 1970) * 365 + MONTHS_OFFSETS[0][month];
- result += (int)floor((double)(year - 1968) / 4);
- result -= (year - 1900) / 100;
- result += (year - 1600) / 400;
-
- if (_is_leap(year) && month < 3)
- {
- result -= 1;
- }
-
- result += day - 1;
- result *= 24;
- result += hour;
- result *= 60;
- result += minute;
- result *= 60;
- result += second;
-
- return PyLong_FromSsize_t(result);
-}
-
-PyObject *local_time(PyObject *self, PyObject *args)
-{
- double unix_time;
- int32_t utc_offset;
- int32_t year;
- int32_t microsecond;
- int64_t seconds;
- int32_t leap_year;
- int64_t sec_per_100years;
- int64_t sec_per_4years;
- int32_t sec_per_year;
- int32_t month;
- int32_t day;
- int32_t month_offset;
- int32_t hour;
- int32_t minute;
- int32_t second;
-
- if (!PyArg_ParseTuple(args, "dii", &unix_time, &utc_offset, &microsecond))
- {
- PyErr_SetString(
- PyExc_ValueError, "Invalid parameters");
- return NULL;
- }
-
- year = EPOCH_YEAR;
- seconds = (int64_t)floor(unix_time);
-
- // Shift to a base year that is 400-year aligned.
- if (seconds >= 0)
- {
- seconds -= 10957L * SECS_PER_DAY;
- year += 30; // == 2000;
- }
- else
- {
- seconds += (int64_t)(146097L - 10957L) * SECS_PER_DAY;
- year -= 370; // == 1600;
- }
-
- seconds += utc_offset;
-
- // Handle years in chunks of 400/100/4/1
- year += 400 * (seconds / SECS_PER_400_YEARS);
- seconds %= SECS_PER_400_YEARS;
- if (seconds < 0)
- {
- seconds += SECS_PER_400_YEARS;
- year -= 400;
- }
-
- leap_year = 1; // 4-century aligned
-
- sec_per_100years = SECS_PER_100_YEARS[leap_year];
-
- while (seconds >= sec_per_100years)
- {
- seconds -= sec_per_100years;
- year += 100;
- leap_year = 0; // 1-century, non 4-century aligned
- sec_per_100years = SECS_PER_100_YEARS[leap_year];
- }
-
- sec_per_4years = SECS_PER_4_YEARS[leap_year];
- while (seconds >= sec_per_4years)
- {
- seconds -= sec_per_4years;
- year += 4;
- leap_year = 1; // 4-year, non century aligned
- sec_per_4years = SECS_PER_4_YEARS[leap_year];
- }
-
- sec_per_year = SECS_PER_YEAR[leap_year];
- while (seconds >= sec_per_year)
- {
- seconds -= sec_per_year;
- year += 1;
- leap_year = 0; // non 4-year aligned
- sec_per_year = SECS_PER_YEAR[leap_year];
- }
-
- // Handle months and days
- month = TM_DECEMBER + 1;
- day = seconds / SECS_PER_DAY + 1;
- seconds %= SECS_PER_DAY;
- while (month != TM_JANUARY + 1)
- {
- month_offset = MONTHS_OFFSETS[leap_year][month];
- if (day > month_offset)
- {
- day -= month_offset;
- break;
- }
-
- month -= 1;
- }
-
- // Handle hours, minutes and seconds
- hour = seconds / SECS_PER_HOUR;
- seconds %= SECS_PER_HOUR;
- minute = seconds / SECS_PER_MIN;
- second = seconds % SECS_PER_MIN;
-
- return Py_BuildValue("NNNNNNN",
- PyLong_FromLong(year),
- PyLong_FromLong(month),
- PyLong_FromLong(day),
- PyLong_FromLong(hour),
- PyLong_FromLong(minute),
- PyLong_FromLong(second),
- PyLong_FromLong(microsecond));
-}
-
-// Calculate a precise difference between two datetimes.
-PyObject *precise_diff(PyObject *self, PyObject *args)
-{
- PyObject *dt1;
- PyObject *dt2;
-
- if (!PyArg_ParseTuple(args, "OO", &dt1, &dt2))
- {
- PyErr_SetString(
- PyExc_ValueError, "Invalid parameters");
- return NULL;
- }
-
- int year_diff = 0;
- int month_diff = 0;
- int day_diff = 0;
- int hour_diff = 0;
- int minute_diff = 0;
- int second_diff = 0;
- int microsecond_diff = 0;
- int sign = 1;
- int year;
- int month;
- int leap;
- int days_in_last_month;
- int days_in_month;
- int dt1_year = PyDateTime_GET_YEAR(dt1);
- int dt2_year = PyDateTime_GET_YEAR(dt2);
- int dt1_month = PyDateTime_GET_MONTH(dt1);
- int dt2_month = PyDateTime_GET_MONTH(dt2);
- int dt1_day = PyDateTime_GET_DAY(dt1);
- int dt2_day = PyDateTime_GET_DAY(dt2);
- int dt1_hour = 0;
- int dt2_hour = 0;
- int dt1_minute = 0;
- int dt2_minute = 0;
- int dt1_second = 0;
- int dt2_second = 0;
- int dt1_microsecond = 0;
- int dt2_microsecond = 0;
- int dt1_total_seconds = 0;
- int dt2_total_seconds = 0;
- int dt1_offset = 0;
- int dt2_offset = 0;
- int dt1_is_datetime = PyDateTime_Check(dt1);
- int dt2_is_datetime = PyDateTime_Check(dt2);
- char *tz1 = "";
- char *tz2 = "";
- int in_same_tz = 0;
- int total_days = (_day_number(dt2_year, dt2_month, dt2_day) - _day_number(dt1_year, dt1_month, dt1_day));
-
- // If both dates are datetimes, we check
- // If we are in the same timezone
- if (dt1_is_datetime && dt2_is_datetime)
- {
- if (_has_tzinfo(dt1))
- {
- tz1 = _get_tz_name(dt1);
- dt1_offset = _get_offset(dt1);
- }
-
- if (_has_tzinfo(dt2))
- {
- tz2 = _get_tz_name(dt2);
- dt2_offset = _get_offset(dt2);
- }
-
- in_same_tz = tz1 == tz2 && strncmp(tz1, "", 1);
- }
-
- // If we have datetimes (and not only dates)
- // we get the information we need
- if (dt1_is_datetime)
- {
- dt1_hour = PyDateTime_DATE_GET_HOUR(dt1);
- dt1_minute = PyDateTime_DATE_GET_MINUTE(dt1);
- dt1_second = PyDateTime_DATE_GET_SECOND(dt1);
- dt1_microsecond = PyDateTime_DATE_GET_MICROSECOND(dt1);
-
- if ((!in_same_tz && dt1_offset != 0) || total_days == 0)
- {
- dt1_hour -= dt1_offset / SECS_PER_HOUR;
- dt1_offset %= SECS_PER_HOUR;
- dt1_minute -= dt1_offset / SECS_PER_MIN;
- dt1_offset %= SECS_PER_MIN;
- dt1_second -= dt1_offset;
-
- if (dt1_second < 0)
- {
- dt1_second += 60;
- dt1_minute -= 1;
- }
- else if (dt1_second > 60)
- {
- dt1_second -= 60;
- dt1_minute += 1;
- }
-
- if (dt1_minute < 0)
- {
- dt1_minute += 60;
- dt1_hour -= 1;
- }
- else if (dt1_minute > 60)
- {
- dt1_minute -= 60;
- dt1_hour += 1;
- }
-
- if (dt1_hour < 0)
- {
- dt1_hour += 24;
- dt1_day -= 1;
- }
- else if (dt1_hour > 24)
- {
- dt1_hour -= 24;
- dt1_day += 1;
- }
- }
-
- dt1_total_seconds = (dt1_hour * SECS_PER_HOUR + dt1_minute * SECS_PER_MIN + dt1_second);
- }
-
- if (dt2_is_datetime)
- {
- dt2_hour = PyDateTime_DATE_GET_HOUR(dt2);
- dt2_minute = PyDateTime_DATE_GET_MINUTE(dt2);
- dt2_second = PyDateTime_DATE_GET_SECOND(dt2);
- dt2_microsecond = PyDateTime_DATE_GET_MICROSECOND(dt2);
-
- if ((!in_same_tz && dt2_offset != 0) || total_days == 0)
- {
- dt2_hour -= dt2_offset / SECS_PER_HOUR;
- dt2_offset %= SECS_PER_HOUR;
- dt2_minute -= dt2_offset / SECS_PER_MIN;
- dt2_offset %= SECS_PER_MIN;
- dt2_second -= dt2_offset;
-
- if (dt2_second < 0)
- {
- dt2_second += 60;
- dt2_minute -= 1;
- }
- else if (dt2_second > 60)
- {
- dt2_second -= 60;
- dt2_minute += 1;
- }
-
- if (dt2_minute < 0)
- {
- dt2_minute += 60;
- dt2_hour -= 1;
- }
- else if (dt2_minute > 60)
- {
- dt2_minute -= 60;
- dt2_hour += 1;
- }
-
- if (dt2_hour < 0)
- {
- dt2_hour += 24;
- dt2_day -= 1;
- }
- else if (dt2_hour > 24)
- {
- dt2_hour -= 24;
- dt2_day += 1;
- }
- }
-
- dt2_total_seconds = (dt2_hour * SECS_PER_HOUR + dt2_minute * SECS_PER_MIN + dt2_second);
- }
-
- // Direct comparison between two datetimes does not work
- // so we need to check by properties
- int dt1_gt_dt2 = (dt1_year > dt2_year || (dt1_year == dt2_year && dt1_month > dt2_month) || (dt1_year == dt2_year && dt1_month == dt2_month && dt1_day > dt2_day) || (dt1_year == dt2_year && dt1_month == dt2_month && dt1_day == dt2_day && dt1_total_seconds > dt2_total_seconds) || (dt1_year == dt2_year && dt1_month == dt2_month && dt1_day == dt2_day && dt1_total_seconds == dt2_total_seconds && dt1_microsecond > dt2_microsecond));
-
- if (dt1_gt_dt2)
- {
- PyObject *temp;
- temp = dt1;
- dt1 = dt2;
- dt2 = temp;
- sign = -1;
-
- // Retrieving properties
- dt1_year = PyDateTime_GET_YEAR(dt1);
- dt2_year = PyDateTime_GET_YEAR(dt2);
- dt1_month = PyDateTime_GET_MONTH(dt1);
- dt2_month = PyDateTime_GET_MONTH(dt2);
- dt1_day = PyDateTime_GET_DAY(dt1);
- dt2_day = PyDateTime_GET_DAY(dt2);
-
- if (dt2_is_datetime)
- {
- dt1_hour = PyDateTime_DATE_GET_HOUR(dt1);
- dt1_minute = PyDateTime_DATE_GET_MINUTE(dt1);
- dt1_second = PyDateTime_DATE_GET_SECOND(dt1);
- dt1_microsecond = PyDateTime_DATE_GET_MICROSECOND(dt1);
- }
-
- if (dt1_is_datetime)
- {
- dt2_hour = PyDateTime_DATE_GET_HOUR(dt2);
- dt2_minute = PyDateTime_DATE_GET_MINUTE(dt2);
- dt2_second = PyDateTime_DATE_GET_SECOND(dt2);
- dt2_microsecond = PyDateTime_DATE_GET_MICROSECOND(dt2);
- }
-
- total_days = (_day_number(dt2_year, dt2_month, dt2_day) - _day_number(dt1_year, dt1_month, dt1_day));
- }
-
- year_diff = dt2_year - dt1_year;
- month_diff = dt2_month - dt1_month;
- day_diff = dt2_day - dt1_day;
- hour_diff = dt2_hour - dt1_hour;
- minute_diff = dt2_minute - dt1_minute;
- second_diff = dt2_second - dt1_second;
- microsecond_diff = dt2_microsecond - dt1_microsecond;
-
- if (microsecond_diff < 0)
- {
- microsecond_diff += 1e6;
- second_diff -= 1;
- }
-
- if (second_diff < 0)
- {
- second_diff += 60;
- minute_diff -= 1;
- }
-
- if (minute_diff < 0)
- {
- minute_diff += 60;
- hour_diff -= 1;
- }
-
- if (hour_diff < 0)
- {
- hour_diff += 24;
- day_diff -= 1;
- }
-
- if (day_diff < 0)
- {
- // If we have a difference in days,
- // we have to check if they represent months
- year = dt2_year;
- month = dt2_month;
-
- if (month == 1)
- {
- month = 12;
- year -= 1;
- }
- else
- {
- month -= 1;
- }
-
- leap = _is_leap(year);
-
- days_in_last_month = DAYS_PER_MONTHS[leap][month];
- days_in_month = DAYS_PER_MONTHS[_is_leap(dt2_year)][dt2_month];
-
- if (day_diff < days_in_month - days_in_last_month)
- {
- // We don't have a full month, we calculate days
- if (days_in_last_month < dt1_day)
- {
- day_diff += dt1_day;
- }
- else
- {
- day_diff += days_in_last_month;
- }
- }
- else if (day_diff == days_in_month - days_in_last_month)
- {
- // We have exactly a full month
- // We remove the days difference
- // and add one to the months difference
- day_diff = 0;
- month_diff += 1;
- }
- else
- {
- // We have a full month
- day_diff += days_in_last_month;
- }
-
- month_diff -= 1;
- }
-
- if (month_diff < 0)
- {
- month_diff += 12;
- year_diff -= 1;
- }
-
- return new_diff(
- year_diff * sign,
- month_diff * sign,
- day_diff * sign,
- hour_diff * sign,
- minute_diff * sign,
- second_diff * sign,
- microsecond_diff * sign,
- total_days * sign);
-}
-
-/* ------------------------------------------------------------------------- */
-
-static PyMethodDef helpers_methods[] = {
- {"is_leap",
- (PyCFunction)is_leap,
- METH_VARARGS,
- PyDoc_STR("Checks if a year is a leap year.")},
- {"is_long_year",
- (PyCFunction)is_long_year,
- METH_VARARGS,
- PyDoc_STR("Checks if a year is a long year.")},
- {"week_day",
- (PyCFunction)week_day,
- METH_VARARGS,
- PyDoc_STR("Returns the weekday number.")},
- {"days_in_year",
- (PyCFunction)days_in_year,
- METH_VARARGS,
- PyDoc_STR("Returns the number of days in the given year.")},
- {"timestamp",
- (PyCFunction)timestamp,
- METH_VARARGS,
- PyDoc_STR("Returns the timestamp of the given datetime.")},
- {"local_time",
- (PyCFunction)local_time,
- METH_VARARGS,
- PyDoc_STR("Returns a UNIX time as a broken down time for a particular transition type.")},
- {"precise_diff",
- (PyCFunction)precise_diff,
- METH_VARARGS,
- PyDoc_STR("Calculate a precise difference between two datetimes.")},
- {NULL}};
-
-/* ------------------------------------------------------------------------- */
-
-static struct PyModuleDef moduledef = {
- PyModuleDef_HEAD_INIT,
- "_helpers",
- NULL,
- -1,
- helpers_methods,
- NULL,
- NULL,
- NULL,
- NULL,
-};
-
-PyMODINIT_FUNC
-PyInit__helpers(void)
-{
- PyObject *module;
-
- PyDateTime_IMPORT;
-
- module = PyModule_Create(&moduledef);
-
- if (module == NULL)
- return NULL;
-
- // Diff declaration
- Diff_type.tp_new = PyType_GenericNew;
- Diff_type.tp_members = Diff_members;
- Diff_type.tp_init = (initproc)Diff_init;
-
- if (PyType_Ready(&Diff_type) < 0)
- return NULL;
-
- PyModule_AddObject(module, "PreciseDiff", (PyObject *)&Diff_type);
-
- return module;
-}
diff --git a/pendulum/exceptions.py b/pendulum/exceptions.py
deleted file mode 100644
index 3ab4db9..0000000
--- a/pendulum/exceptions.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from __future__ import annotations
-
-from .parsing.exceptions import ParserError # noqa
-
-
-class PendulumException(Exception):
-
- pass
diff --git a/pendulum/parsing/_iso8601.c b/pendulum/parsing/_iso8601.c
deleted file mode 100644
index 1322423..0000000
--- a/pendulum/parsing/_iso8601.c
+++ /dev/null
@@ -1,1361 +0,0 @@
-/* ------------------------------------------------------------------------- */
-
-#include <Python.h>
-#include <datetime.h>
-#include <structmember.h>
-#include <math.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdint.h>
-
-#ifndef PyVarObject_HEAD_INIT
-#define PyVarObject_HEAD_INIT(type, size) PyObject_HEAD_INIT(type) size,
-#endif
-
-
-/* ------------------------------------------------------------------------- */
-
-#define EPOCH_YEAR 1970
-
-#define DAYS_PER_N_YEAR 365
-#define DAYS_PER_L_YEAR 366
-
-#define USECS_PER_SEC 1000000
-
-#define SECS_PER_MIN 60
-#define SECS_PER_HOUR (60 * SECS_PER_MIN)
-#define SECS_PER_DAY (SECS_PER_HOUR * 24)
-
-// 400-year chunks always have 146097 days (20871 weeks).
-#define DAYS_PER_400_YEARS 146097L
-#define SECS_PER_400_YEARS ((int64_t)DAYS_PER_400_YEARS * (int64_t)SECS_PER_DAY)
-
-// The number of seconds in an aligned 100-year chunk, for those that
-// do not begin with a leap year and those that do respectively.
-const int64_t SECS_PER_100_YEARS[2] = {
- (uint64_t)(76L * DAYS_PER_N_YEAR + 24L * DAYS_PER_L_YEAR) * SECS_PER_DAY,
- (uint64_t)(75L * DAYS_PER_N_YEAR + 25L * DAYS_PER_L_YEAR) * SECS_PER_DAY
-};
-
-// The number of seconds in an aligned 4-year chunk, for those that
-// do not begin with a leap year and those that do respectively.
-const int32_t SECS_PER_4_YEARS[2] = {
- (4 * DAYS_PER_N_YEAR + 0 * DAYS_PER_L_YEAR) * SECS_PER_DAY,
- (3 * DAYS_PER_N_YEAR + 1 * DAYS_PER_L_YEAR) * SECS_PER_DAY
-};
-
-// The number of seconds in non-leap and leap years respectively.
-const int32_t SECS_PER_YEAR[2] = {
- DAYS_PER_N_YEAR * SECS_PER_DAY,
- DAYS_PER_L_YEAR * SECS_PER_DAY
-};
-
-#define MONTHS_PER_YEAR 12
-
-// The month lengths in non-leap and leap years respectively.
-const int32_t DAYS_PER_MONTHS[2][13] = {
- {-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
- {-1, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
-};
-
-// The day offsets of the beginning of each (1-based) month in non-leap
-// and leap years respectively.
-// For example, in a leap year there are 335 days before December.
-const int32_t MONTHS_OFFSETS[2][14] = {
- {-1, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
- {-1, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}
-};
-
-const int DAY_OF_WEEK_TABLE[12] = {
- 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4
-};
-
-#define TM_SUNDAY 0
-#define TM_MONDAY 1
-#define TM_TUESDAY 2
-#define TM_WEDNESDAY 3
-#define TM_THURSDAY 4
-#define TM_FRIDAY 5
-#define TM_SATURDAY 6
-
-#define TM_JANUARY 0
-#define TM_FEBRUARY 1
-#define TM_MARCH 2
-#define TM_APRIL 3
-#define TM_MAY 4
-#define TM_JUNE 5
-#define TM_JULY 6
-#define TM_AUGUST 7
-#define TM_SEPTEMBER 8
-#define TM_OCTOBER 9
-#define TM_NOVEMBER 10
-#define TM_DECEMBER 11
-
-// Parsing errors
-const int PARSER_INVALID_ISO8601 = 0;
-const int PARSER_INVALID_DATE = 1;
-const int PARSER_INVALID_TIME = 2;
-const int PARSER_INVALID_WEEK_DATE = 3;
-const int PARSER_INVALID_WEEK_NUMBER = 4;
-const int PARSER_INVALID_WEEKDAY_NUMBER = 5;
-const int PARSER_INVALID_ORDINAL_DAY_FOR_YEAR = 6;
-const int PARSER_INVALID_MONTH_OR_DAY = 7;
-const int PARSER_INVALID_MONTH = 8;
-const int PARSER_INVALID_DAY_FOR_MONTH = 9;
-const int PARSER_INVALID_HOUR = 10;
-const int PARSER_INVALID_MINUTE = 11;
-const int PARSER_INVALID_SECOND = 12;
-const int PARSER_INVALID_SUBSECOND = 13;
-const int PARSER_INVALID_TZ_OFFSET = 14;
-const int PARSER_INVALID_DURATION = 15;
-const int PARSER_INVALID_DURATION_FLOAT_YEAR_MONTH_NOT_SUPPORTED = 16;
-
-const char PARSER_ERRORS[17][80] = {
- "Invalid ISO 8601 string",
- "Invalid date",
- "Invalid time",
- "Invalid week date",
- "Invalid week number",
- "Invalid weekday number",
- "Invalid ordinal day for year",
- "Invalid month and/or day",
- "Invalid month",
- "Invalid day for month",
- "Invalid hour",
- "Invalid minute",
- "Invalid second",
- "Invalid subsecond",
- "Invalid timezone offset",
- "Invalid duration",
- "Float years and months are not supported"
-};
-
-/* ------------------------------------------------------------------------- */
-
-
-int p(int y) {
- return y + y/4 - y/100 + y/400;
-}
-
-int is_leap(int year) {
- return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
-}
-
-int week_day(int year, int month, int day) {
- int y;
- int w;
-
- y = year - (month < 3);
-
- w = (p(y) + DAY_OF_WEEK_TABLE[month - 1] + day) % 7;
-
- if (!w) {
- w = 7;
- }
-
- return w;
-}
-
-int days_in_year(int year) {
- if (is_leap(year)) {
- return DAYS_PER_L_YEAR;
- }
-
- return DAYS_PER_N_YEAR;
-}
-
-int is_long_year(int year) {
- return (p(year) % 7 == 4) || (p(year - 1) % 7 == 3);
-}
-
-
-/* ------------------------ Custom Types ------------------------------- */
-
-
-/*
- * class FixedOffset(tzinfo):
- */
-typedef struct {
- PyObject_HEAD
- int offset;
- char *tzname;
-} FixedOffset;
-
-/*
- * def __init__(self, offset):
- * self.offset = offset
-*/
-static int FixedOffset_init(FixedOffset *self, PyObject *args, PyObject *kwargs) {
- int offset;
- char *tzname = NULL;
-
- static char *kwlist[] = {"offset", "tzname", NULL};
-
- if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|s", kwlist, &offset, &tzname))
- return -1;
-
- self->offset = offset;
- self->tzname = tzname;
-
- return 0;
-}
-
-/*
- * def utcoffset(self, dt):
- * return timedelta(seconds=self.offset * 60)
- */
-static PyObject *FixedOffset_utcoffset(FixedOffset *self, PyObject *args) {
- return PyDelta_FromDSU(0, self->offset, 0);
-}
-
-/*
- * def dst(self, dt):
- * return timedelta(seconds=self.offset * 60)
- */
-static PyObject *FixedOffset_dst(FixedOffset *self, PyObject *args) {
- return PyDelta_FromDSU(0, self->offset, 0);
-}
-
-/*
- * def tzname(self, dt):
- * sign = '+'
- * if self.offset < 0:
- * sign = '-'
- * return f"{sign}{self.offset / 60}:{self.offset % 60}"
- */
-static PyObject *FixedOffset_tzname(FixedOffset *self, PyObject *args) {
- if (self->tzname != NULL) {
- return PyUnicode_FromString(self->tzname);
- }
-
- char sign = '+';
- int offset = self->offset;
-
- if (offset < 0) {
- sign = '-';
- offset *= -1;
- }
-
- return PyUnicode_FromFormat(
- "%c%02d:%02d",
- sign,
- offset / SECS_PER_HOUR,
- offset / SECS_PER_MIN % SECS_PER_MIN
- );
-}
-
-/*
- * def __repr__(self):
- * return self.tzname()
- */
-static PyObject *FixedOffset_repr(FixedOffset *self) {
- return FixedOffset_tzname(self, NULL);
-}
-
-/*
- * Class member / class attributes
- */
-static PyMemberDef FixedOffset_members[] = {
- {"offset", T_INT, offsetof(FixedOffset, offset), 0, "UTC offset"},
- {NULL}
-};
-
-/*
- * Class methods
- */
-static PyMethodDef FixedOffset_methods[] = {
- {"utcoffset", (PyCFunction)FixedOffset_utcoffset, METH_VARARGS, ""},
- {"dst", (PyCFunction)FixedOffset_dst, METH_VARARGS, ""},
- {"tzname", (PyCFunction)FixedOffset_tzname, METH_VARARGS, ""},
- {NULL}
-};
-
-static PyTypeObject FixedOffset_type = {
- PyVarObject_HEAD_INIT(NULL, 0)
- "FixedOffset_type", /* tp_name */
- sizeof(FixedOffset), /* tp_basicsize */
- 0, /* tp_itemsize */
- 0, /* tp_dealloc */
- 0, /* tp_print */
- 0, /* tp_getattr */
- 0, /* tp_setattr */
- 0, /* tp_as_async */
- (reprfunc)FixedOffset_repr, /* tp_repr */
- 0, /* tp_as_number */
- 0, /* tp_as_sequence */
- 0, /* tp_as_mapping */
- 0, /* tp_hash */
- 0, /* tp_call */
- (reprfunc)FixedOffset_repr, /* tp_str */
- 0, /* tp_getattro */
- 0, /* tp_setattro */
- 0, /* tp_as_buffer */
- Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /* tp_flags */
- "TZInfo with fixed offset", /* tp_doc */
-};
-
-/*
- * Instantiate new FixedOffset_type object
- * Skip overhead of calling PyObject_New and PyObject_Init.
- * Directly allocate object.
- */
-static PyObject *new_fixed_offset_ex(int offset, char *name, PyTypeObject *type) {
- FixedOffset *self = (FixedOffset *) (type->tp_alloc(type, 0));
-
- if (self != NULL) {
- self->offset = offset;
- self->tzname = name;
- }
-
- return (PyObject *) self;
-}
-
-#define new_fixed_offset(offset, name) new_fixed_offset_ex(offset, name, &FixedOffset_type)
-
-
-/*
- * class Duration():
- */
-typedef struct {
- PyObject_HEAD
- int years;
- int months;
- int weeks;
- int days;
- int hours;
- int minutes;
- int seconds;
- int microseconds;
-} Duration;
-
-/*
- * def __init__(self, years, months, days, hours, minutes, seconds, microseconds):
- * self.years = years
- * # ...
-*/
-static int Duration_init(Duration *self, PyObject *args, PyObject *kwargs) {
- int years;
- int months;
- int weeks;
- int days;
- int hours;
- int minutes;
- int seconds;
- int microseconds;
-
- if (!PyArg_ParseTuple(args, "iiiiiiii", &years, &months, &weeks, &days, &hours, &minutes, &seconds, &microseconds))
- return -1;
-
- self->years = years;
- self->months = months;
- self->weeks = weeks;
- self->days = days;
- self->hours = hours;
- self->minutes = minutes;
- self->seconds = seconds;
- self->microseconds = microseconds;
-
- return 0;
-}
-
-/*
- * def __repr__(self):
- * return '{} years {} months {} days {} hours {} minutes {} seconds {} microseconds'.format(
- * self.years, self.months, self.days, self.minutes, self.hours, self.seconds, self.microseconds
- * )
- */
-static PyObject *Duration_repr(Duration *self) {
- return PyUnicode_FromFormat(
- "%d years %d months %d weeks %d days %d hours %d minutes %d seconds %d microseconds",
- self->years,
- self->months,
- self->weeks,
- self->days,
- self->hours,
- self->minutes,
- self->seconds,
- self->microseconds
- );
-}
-
-/*
- * Instantiate new Duration_type object
- * Skip overhead of calling PyObject_New and PyObject_Init.
- * Directly allocate object.
- */
-static PyObject *new_duration_ex(int years, int months, int weeks, int days, int hours, int minutes, int seconds, int microseconds, PyTypeObject *type) {
- Duration *self = (Duration *) (type->tp_alloc(type, 0));
-
- if (self != NULL) {
- self->years = years;
- self->months = months;
- self->weeks = weeks;
- self->days = days;
- self->hours = hours;
- self->minutes = minutes;
- self->seconds = seconds;
- self->microseconds = microseconds;
- }
-
- return (PyObject *) self;
-}
-
-/*
- * Class member / class attributes
- */
-static PyMemberDef Duration_members[] = {
- {"years", T_INT, offsetof(Duration, years), 0, "years in duration"},
- {"months", T_INT, offsetof(Duration, months), 0, "months in duration"},
- {"weeks", T_INT, offsetof(Duration, weeks), 0, "weeks in duration"},
- {"days", T_INT, offsetof(Duration, days), 0, "days in duration"},
- {"remaining_days", T_INT, offsetof(Duration, days), 0, "days in duration"},
- {"hours", T_INT, offsetof(Duration, hours), 0, "hours in duration"},
- {"minutes", T_INT, offsetof(Duration, minutes), 0, "minutes in duration"},
- {"seconds", T_INT, offsetof(Duration, seconds), 0, "seconds in duration"},
- {"remaining_seconds", T_INT, offsetof(Duration, seconds), 0, "seconds in duration"},
- {"microseconds", T_INT, offsetof(Duration, microseconds), 0, "microseconds in duration"},
- {NULL}
-};
-
-static PyTypeObject Duration_type = {
- PyVarObject_HEAD_INIT(NULL, 0)
- "Duration", /* tp_name */
- sizeof(Duration), /* tp_basicsize */
- 0, /* tp_itemsize */
- 0, /* tp_dealloc */
- 0, /* tp_print */
- 0, /* tp_getattr */
- 0, /* tp_setattr */
- 0, /* tp_as_async */
- (reprfunc)Duration_repr, /* tp_repr */
- 0, /* tp_as_number */
- 0, /* tp_as_sequence */
- 0, /* tp_as_mapping */
- 0, /* tp_hash */
- 0, /* tp_call */
- (reprfunc)Duration_repr, /* tp_str */
- 0, /* tp_getattro */
- 0, /* tp_setattro */
- 0, /* tp_as_buffer */
- Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /* tp_flags */
- "Duration", /* tp_doc */
-};
-
-#define new_duration(years, months, weeks, days, hours, minutes, seconds, microseconds) new_duration_ex(years, months, weeks, days, hours, minutes, seconds, microseconds, &Duration_type)
-
-typedef struct {
- int is_date;
- int is_time;
- int is_datetime;
- int is_duration;
- int is_period;
- int ambiguous;
- int year;
- int month;
- int day;
- int hour;
- int minute;
- int second;
- int microsecond;
- int offset;
- int has_offset;
- char *tzname;
- int years;
- int months;
- int weeks;
- int days;
- int hours;
- int minutes;
- int seconds;
- int microseconds;
- int error;
-} Parsed;
-
-
-Parsed* new_parsed() {
- Parsed *parsed;
-
- if((parsed = malloc(sizeof *parsed)) != NULL) {
- parsed->is_date = 0;
- parsed->is_time = 0;
- parsed->is_datetime = 0;
- parsed->is_duration = 0;
- parsed->is_period = 0;
-
- parsed->ambiguous = 0;
- parsed->year = 0;
- parsed->month = 1;
- parsed->day = 1;
- parsed->hour = 0;
- parsed->minute = 0;
- parsed->second = 0;
- parsed->microsecond = 0;
- parsed->offset = 0;
- parsed->has_offset = 0;
- parsed->tzname = NULL;
-
- parsed->years = 0;
- parsed->months = 0;
- parsed->weeks = 0;
- parsed->days = 0;
- parsed->hours = 0;
- parsed->minutes = 0;
- parsed->seconds = 0;
- parsed->microseconds = 0;
-
- parsed->error = -1;
- }
-
- return parsed;
-}
-
-
-/* -------------------------- Functions --------------------------*/
-
-Parsed* _parse_iso8601_datetime(char *str, Parsed *parsed) {
- char* c;
- int monthday = 0;
- int week = 0;
- int weekday = 1;
- int ordinal;
- int tz_sign = 0;
- int leap = 0;
- int separators = 0;
- int time = 0;
- int i;
- int j;
-
- // Assuming date only for now
- parsed->is_date = 1;
-
- c = str;
-
- for (i = 0; i < 4; i++) {
- if (*c >= '0' && *c <= '9') {
- parsed->year = 10 * parsed->year + *c++ - '0';
- } else {
- parsed->error = PARSER_INVALID_ISO8601;
-
- return NULL;
- }
- }
-
- leap = is_leap(parsed->year);
-
- // Optional separator
- if (*c == '-') {
- separators++;
- c++;
- }
-
- // Checking for week dates
- if (*c == 'W') {
- c++;
-
- i = 0;
- while (*c != '\0' && *c != ' ' && *c != 'T') {
- if (*c == '-') {
- separators++;
- c++;
- continue;
- }
-
- week = 10 * week + *c++ - '0';
-
- i++;
- }
-
- switch (i) {
- case 2:
- // Only week number
- break;
- case 3:
- // Week with weekday
- if (!(separators == 0 || separators == 2)) {
- // We should have 2 or no separator
- parsed->error = PARSER_INVALID_WEEK_DATE;
-
- return NULL;
- }
-
- weekday = week % 10;
- week /= 10;
-
- break;
- default:
- // Any other case is wrong
- parsed->error = PARSER_INVALID_WEEK_DATE;
-
- return NULL;
- }
-
- // Checks
- if (week > 53 || (week > 52 && !is_long_year(parsed->year))) {
- parsed->error = PARSER_INVALID_WEEK_NUMBER;
-
- return NULL;
- }
-
- if (weekday > 7) {
- parsed->error = PARSER_INVALID_WEEKDAY_NUMBER;
-
- return NULL;
- }
-
- // Calculating ordinal day
- ordinal = week * 7 + weekday - (week_day(parsed->year, 1, 4) + 3);
-
- if (ordinal < 1) {
- // Previous year
- ordinal += days_in_year(parsed->year - 1);
- parsed->year -= 1;
- leap = is_leap(parsed->year);
- }
-
- if (ordinal > days_in_year(parsed->year)) {
- // Next year
- ordinal -= days_in_year(parsed->year);
- parsed->year += 1;
- leap = is_leap(parsed->year);
- }
-
- for (j = 1; j < 14; j++) {
- if (ordinal <= MONTHS_OFFSETS[leap][j]) {
- parsed->day = ordinal - MONTHS_OFFSETS[leap][j - 1];
- parsed->month = j - 1;
-
- break;
- }
- }
- } else {
- // At this point we need to check the number
- // of characters until the end of the date part
- // (or the end of the string).
- //
- // If two, we have only a month if there is a separator, it may be a time otherwise.
- // If three, we have an ordinal date.
- // If four, we have a complete date
- i = 0;
- while (*c != '\0' && *c != ' ' && *c != 'T') {
- if (*c == '-') {
- separators++;
- c++;
- continue;
- }
-
- if (!(*c >= '0' && *c <='9')) {
- parsed->error = PARSER_INVALID_DATE;
-
- return NULL;
- }
-
- monthday = 10 * monthday + *c++ - '0';
-
- i++;
- }
-
- switch (i) {
- case 0:
- // No month/day specified (only a year)
- break;
- case 2:
- if (!separators) {
- // The date looks like 201207
- // which is invalid for a date
- // But it might be a time in the form hhmmss
- parsed->ambiguous = 1;
- } else if (separators > 1) {
- parsed->error = PARSER_INVALID_DATE;
-
- return NULL;
- }
-
- parsed->month = monthday;
- break;
- case 3:
- // Ordinal day
- if (separators > 1) {
- parsed->error = PARSER_INVALID_DATE;
-
- return NULL;
- }
-
- if (monthday < 1 || monthday > MONTHS_OFFSETS[leap][13]) {
- parsed->error = PARSER_INVALID_ORDINAL_DAY_FOR_YEAR;
-
- return NULL;
- }
-
- for (j = 1; j < 14; j++) {
- if (monthday <= MONTHS_OFFSETS[leap][j]) {
- parsed->day = monthday - MONTHS_OFFSETS[leap][j - 1];
- parsed->month = j - 1;
-
- break;
- }
- }
-
- break;
- case 4:
- // Month and day
- parsed->month = monthday / 100;
- parsed->day = monthday % 100;
-
- break;
- default:
- parsed->error = PARSER_INVALID_MONTH_OR_DAY;
-
- return NULL;
- }
- }
-
- // Checks
- if (separators && !monthday && !week) {
- parsed->error = PARSER_INVALID_DATE;
-
- return NULL;
- }
-
- if (parsed->month > 12) {
- parsed->error = PARSER_INVALID_MONTH;
-
- return NULL;
- }
-
- if (parsed->day > DAYS_PER_MONTHS[leap][parsed->month]) {
- parsed->error = PARSER_INVALID_DAY_FOR_MONTH;
-
- return NULL;
- }
-
- separators = 0;
- if (*c == 'T' || *c == ' ') {
- if (parsed->ambiguous) {
- parsed->error = PARSER_INVALID_DATE;
-
- return NULL;
- }
-
- // We have time so we have a datetime
- parsed->is_datetime = 1;
- parsed->is_date = 0;
-
- c++;
-
- // Grabbing time information
- i = 0;
- while (*c != '\0' && *c != '.' && *c != ',' && *c != 'Z' && *c != '+' && *c != '-') {
- if (*c == ':') {
- separators++;
- c++;
- continue;
- }
-
- if (!(*c >= '0' && *c <='9')) {
- parsed->error = PARSER_INVALID_TIME;
-
- return NULL;
- }
-
- time = 10 * time + *c++ - '0';
- i++;
- }
-
- switch (i) {
- case 2:
- // Hours only
- if (separators > 0) {
- // Extraneous separators
- parsed->error = PARSER_INVALID_TIME;
-
- return NULL;
- }
-
- parsed->hour = time;
- break;
- case 4:
- // Hours and minutes
- if (separators > 1) {
- // Extraneous separators
- parsed->error = PARSER_INVALID_TIME;
-
- return NULL;
- }
-
- parsed->hour = time / 100;
- parsed->minute = time % 100;
- break;
- case 6:
- // Hours, minutes and seconds
- if (!(separators == 0 || separators == 2)) {
- // We should have either two separators or none
- parsed->error = PARSER_INVALID_TIME;
-
- return NULL;
- }
-
- parsed->hour = time / 10000;
- parsed->minute = time / 100 % 100;
- parsed->second = time % 100;
- break;
- default:
- // Any other case is wrong
- parsed->error = PARSER_INVALID_TIME;
-
- return NULL;
- }
-
- // Checks
- if (parsed->hour > 23) {
- parsed->error = PARSER_INVALID_HOUR;
-
- return NULL;
- }
-
- if (parsed->minute > 59) {
- parsed->error = PARSER_INVALID_MINUTE;
-
- return NULL;
- }
-
- if (parsed->second > 59) {
- parsed->error = PARSER_INVALID_SECOND;
-
- return NULL;
- }
-
- // Subsecond
- if (*c == '.' || *c == ',') {
- c++;
-
- time = 0;
- i = 0;
- while (*c != '\0' && *c != 'Z' && *c != '+' && *c != '-') {
- if (!(*c >= '0' && *c <='9')) {
- parsed->error = PARSER_INVALID_SUBSECOND;
-
- return NULL;
- }
-
- time = 10 * time + *c++ - '0';
- i++;
- }
-
- // adjust to microseconds
- if (i > 6) {
- parsed->microsecond = time / pow(10, i - 6);
- } else if (i <= 6) {
- parsed->microsecond = time * pow(10, 6 - i);
- }
- }
-
- // Timezone
- if (*c == 'Z') {
- parsed->has_offset = 1;
- parsed->tzname = "UTC";
- c++;
- } else if (*c == '+' || *c == '-') {
- tz_sign = 1;
- if (*c == '-') {
- tz_sign = -1;
- }
-
- parsed->has_offset = 1;
- c++;
-
- i = 0;
- time = 0;
- separators = 0;
- while (*c != '\0') {
- if (*c == ':') {
- separators++;
- c++;
- continue;
- }
-
- if (!(*c >= '0' && *c <= '9')) {
- parsed->error = PARSER_INVALID_TZ_OFFSET;
-
- return NULL;
- }
-
- time = 10 * time + *c++ - '0';
- i++;
- }
-
- switch (i) {
- case 2:
- // hh Format
- if (separators) {
- // Extraneous separators
- parsed->error = PARSER_INVALID_TZ_OFFSET;
-
- return NULL;
- }
-
- parsed->offset = tz_sign * (time * 3600);
- break;
- case 4:
- // hhmm Format
- if (separators > 1) {
- // Extraneous separators
- parsed->error = PARSER_INVALID_TZ_OFFSET;
-
- return NULL;
- }
-
- parsed->offset = tz_sign * ((time / 100 * 3600) + (time % 100 * 60));
- break;
- default:
- // Wrong format
- parsed->error = PARSER_INVALID_TZ_OFFSET;
-
- return NULL;
- }
- }
- }
-
- // At this point we should be at the end of the string
- // If not, the string is invalid
- if (*c != '\0') {
- parsed->error = PARSER_INVALID_ISO8601;
-
- return NULL;
- }
-
- return parsed;
-}
-
-
-Parsed* _parse_iso8601_duration(char *str, Parsed *parsed) {
- char* c;
- int value = 0;
- int grabbed = 0;
- int in_time = 0;
- int in_fraction = 0;
- int fraction_length = 0;
- int has_fractional = 0;
- int fraction = 0;
- int has_ymd = 0;
- int has_week = 0;
- int has_month = 0;
- int has_day = 0;
- int has_hour = 0;
- int has_minute = 0;
- int has_second = 0;
-
- c = str;
-
- // Removing P operator
- c++;
-
- parsed->is_duration = 1;
-
- for (; *c != '\0'; c++) {
- switch (*c) {
- case 'Y':
- if (!grabbed || in_time || has_week || has_ymd) {
- // No value grabbed
- parsed->error = PARSER_INVALID_DURATION;
-
- return NULL;
- }
-
- if (fraction) {
- parsed->error = PARSER_INVALID_DURATION_FLOAT_YEAR_MONTH_NOT_SUPPORTED;
-
- return NULL;
- }
-
- parsed->years = value;
-
- grabbed = 0;
- value = 0;
- fraction = 0;
- in_fraction = 0;
- has_ymd = 1;
-
- break;
- case 'M':
- if (!grabbed || has_week) {
- // No value grabbed
- parsed->error = PARSER_INVALID_DURATION;
-
- return NULL;
- }
-
- if (in_time) {
- if (has_second) {
- parsed->error = PARSER_INVALID_DURATION;
-
- return NULL;
- }
-
- if (has_fractional) {
- parsed->error = PARSER_INVALID_DURATION;
-
- return NULL;
- }
-
- parsed->minutes = value;
- if (fraction) {
- parsed->seconds = fraction * 6;
- has_fractional = 1;
- }
-
- has_minute = 1;
- } else {
- if (fraction) {
- parsed->error = PARSER_INVALID_DURATION_FLOAT_YEAR_MONTH_NOT_SUPPORTED;
-
- return NULL;
- }
-
- if (has_month || has_day) {
- parsed->error = PARSER_INVALID_DURATION;
-
- return NULL;
- }
-
- parsed->months = value;
- has_ymd = 1;
- has_month = 1;
- }
-
- grabbed = 0;
- value = 0;
- fraction = 0;
- in_fraction = 0;
-
- break;
- case 'D':
- if (!grabbed || in_time || has_week) {
- // No value grabbed
- parsed->error = PARSER_INVALID_DURATION;
-
- return NULL;
- }
-
- if (has_day) {
- parsed->error = PARSER_INVALID_DURATION;
-
- return NULL;
- }
-
- parsed->days = value;
- if (fraction) {
- parsed->hours = fraction * 2.4;
- has_fractional = 1;
- }
-
- grabbed = 0;
- value = 0;
- fraction = 0;
- in_fraction = 0;
- has_ymd = 1;
- has_day = 1;
-
- break;
- case 'T':
- if (grabbed) {
- parsed->error = PARSER_INVALID_DURATION;
-
- return NULL;
- }
-
- in_time = 1;
-
- break;
- case 'H':
- if (!grabbed || !in_time || has_week) {
- // No value grabbed
- parsed->error = PARSER_INVALID_DURATION;
-
- return NULL;
- }
-
- if (has_hour || has_second || has_minute) {
- parsed->error = PARSER_INVALID_DURATION;
-
- return NULL;
- }
-
- if (has_fractional) {
- parsed->error = PARSER_INVALID_DURATION;
-
- return NULL;
- }
-
- parsed->hours = value;
- if (fraction) {
- parsed->minutes = fraction * 6;
- has_fractional = 1;
- }
-
- grabbed = 0;
- value = 0;
- fraction = 0;
- in_fraction = 0;
- has_hour = 1;
-
- break;
- case 'S':
- if (!grabbed || !in_time || has_week) {
- // No value grabbed
- parsed->error = PARSER_INVALID_DURATION;
-
- return NULL;
- }
-
- if (has_second) {
- parsed->error = PARSER_INVALID_DURATION;
-
- return NULL;
- }
-
- if (has_fractional) {
- parsed->error = PARSER_INVALID_DURATION;
-
- return NULL;
- }
-
- if (fraction) {
- parsed->seconds = value;
- if (fraction_length > 6) {
- parsed->microseconds = fraction / pow(10, fraction_length - 6);
- } else {
- parsed->microseconds = fraction * pow(10, 6 - fraction_length);
- }
- has_fractional = 1;
- } else {
- parsed->seconds = value;
- }
-
- grabbed = 0;
- value = 0;
- fraction = 0;
- in_fraction = 0;
- has_second = 1;
-
- break;
- case 'W':
- if (!grabbed || in_time || has_ymd) {
- // No value grabbed
- parsed->error = PARSER_INVALID_DURATION;
-
- return NULL;
- }
-
- parsed->weeks = value;
- if (fraction) {
- float days;
- days = fraction * 0.7;
- parsed->hours = (int) ((days - (int) days) * 24);
- parsed->days = (int) days;
- }
-
- grabbed = 0;
- value = 0;
- fraction = 0;
- in_fraction = 0;
- has_week = 1;
-
- break;
- case '.':
- if (!grabbed || has_fractional) {
- // No value grabbed
- parsed->error = PARSER_INVALID_DURATION;
-
- return NULL;
- }
-
- in_fraction = 1;
-
- break;
- case ',':
- if (!grabbed || has_fractional) {
- // No value grabbed
- parsed->error = PARSER_INVALID_DURATION;
-
- return NULL;
- }
-
- in_fraction = 1;
-
- break;
- default:
- if (*c >= '0' && *c <='9') {
- if (in_fraction) {
- fraction = 10 * fraction + *c - '0';
- fraction_length++;
- } else {
- value = 10 * value + *c - '0';
- grabbed = 1;
- }
- break;
- }
-
- parsed->error = PARSER_INVALID_DURATION;
-
- return NULL;
- }
- }
-
- return parsed;
-}
-
-
-PyObject* parse_iso8601(PyObject *self, PyObject *args) {
- char* str;
- PyObject *obj;
- PyObject *tzinfo;
- Parsed *parsed = new_parsed();
-
- if (!PyArg_ParseTuple(args, "s", &str)) {
- PyErr_SetString(
- PyExc_ValueError, "Invalid parameters"
- );
- free(parsed);
- return NULL;
- }
-
- if (*str == 'P') {
- // Duration (or interval)
- if (_parse_iso8601_duration(str, parsed) == NULL) {
- PyErr_SetString(
- PyExc_ValueError, PARSER_ERRORS[parsed->error]
- );
-
- free(parsed);
- return NULL;
- }
- } else if (_parse_iso8601_datetime(str, parsed) == NULL) {
- PyErr_SetString(
- PyExc_ValueError, PARSER_ERRORS[parsed->error]
- );
-
- free(parsed);
- return NULL;
- }
-
- if (parsed->is_date) {
- // Date only
- if (parsed->ambiguous) {
- // We can "safely" assume that the ambiguous
- // date was actually a time in the form hhmmss
- parsed->hour = parsed->year / 100;
- parsed->minute = parsed->year % 100;
- parsed->second = parsed->month;
-
- obj = PyDateTimeAPI->Time_FromTime(
- parsed->hour, parsed->minute, parsed->second, parsed->microsecond,
- Py_BuildValue(""),
- PyDateTimeAPI->TimeType
- );
- } else {
- obj = PyDateTimeAPI->Date_FromDate(
- parsed->year, parsed->month, parsed->day,
- PyDateTimeAPI->DateType
- );
- }
- } else if (parsed->is_datetime) {
- if (!parsed->has_offset) {
- tzinfo = Py_BuildValue("");
- } else {
- tzinfo = new_fixed_offset(parsed->offset, parsed->tzname);
- }
-
- obj = PyDateTimeAPI->DateTime_FromDateAndTime(
- parsed->year,
- parsed->month,
- parsed->day,
- parsed->hour,
- parsed->minute,
- parsed->second,
- parsed->microsecond,
- tzinfo,
- PyDateTimeAPI->DateTimeType
- );
-
- Py_DECREF(tzinfo);
- } else if (parsed->is_duration) {
- obj = new_duration(
- parsed->years, parsed->months, parsed->weeks, parsed->days,
- parsed->hours, parsed->minutes, parsed->seconds, parsed->microseconds
- );
- } else {
- free(parsed);
- return NULL;
- }
-
- free(parsed);
-
- return obj;
-}
-
-
-/* ------------------------------------------------------------------------- */
-
-static PyMethodDef helpers_methods[] = {
- {
- "parse_iso8601",
- (PyCFunction) parse_iso8601,
- METH_VARARGS,
- PyDoc_STR("Parses a ISO8601 string into a tuple.")
- },
- {NULL}
-};
-
-
-/* ------------------------------------------------------------------------- */
-
-static struct PyModuleDef moduledef = {
- PyModuleDef_HEAD_INIT,
- "_iso8601",
- NULL,
- -1,
- helpers_methods,
- NULL,
- NULL,
- NULL,
- NULL,
-};
-
-PyMODINIT_FUNC
-PyInit__iso8601(void)
-{
- PyObject *module;
-
- PyDateTime_IMPORT;
-
- module = PyModule_Create(&moduledef);
-
- if (module == NULL)
- return NULL;
-
- // FixedOffset declaration
- FixedOffset_type.tp_new = PyType_GenericNew;
- FixedOffset_type.tp_base = PyDateTimeAPI->TZInfoType;
- FixedOffset_type.tp_methods = FixedOffset_methods;
- FixedOffset_type.tp_members = FixedOffset_members;
- FixedOffset_type.tp_init = (initproc)FixedOffset_init;
-
- if (PyType_Ready(&FixedOffset_type) < 0)
- return NULL;
-
- // Duration declaration
- Duration_type.tp_new = PyType_GenericNew;
- Duration_type.tp_members = Duration_members;
- Duration_type.tp_init = (initproc)Duration_init;
-
- if (PyType_Ready(&Duration_type) < 0)
- return NULL;
-
- Py_INCREF(&FixedOffset_type);
- Py_INCREF(&Duration_type);
-
- PyModule_AddObject(module, "TZFixedOffset", (PyObject *)&FixedOffset_type);
- PyModule_AddObject(module, "Duration", (PyObject *)&Duration_type);
-
- return module;
-}
diff --git a/pendulum/parsing/_iso8601.pyi b/pendulum/parsing/_iso8601.pyi
deleted file mode 100644
index b9ce5d4..0000000
--- a/pendulum/parsing/_iso8601.pyi
+++ /dev/null
@@ -1,22 +0,0 @@
-from __future__ import annotations
-
-from datetime import date
-from datetime import datetime
-from datetime import time
-
-class Duration:
-
- years: int = 0
- months: int = 0
- weeks: int = 0
- days: int = 0
- remaining_days: int = 0
- hours: int = 0
- minutes: int = 0
- seconds: int = 0
- remaining_seconds: int = 0
- microseconds: int = 0
-
-def parse_iso8601(
- text: str,
-) -> datetime | date | time | Duration: ...
diff --git a/pendulum/testing/traveller.py b/pendulum/testing/traveller.py
deleted file mode 100644
index 3c1d885..0000000
--- a/pendulum/testing/traveller.py
+++ /dev/null
@@ -1,139 +0,0 @@
-from __future__ import annotations
-
-from typing import TYPE_CHECKING
-from typing import cast
-
-from pendulum.datetime import DateTime
-from pendulum.utils._compat import PYPY
-
-if TYPE_CHECKING:
- from types import TracebackType
-
-
-class BaseTraveller:
- def __init__(self, datetime_class: type[DateTime] = DateTime) -> None:
- self._datetime_class: type[DateTime] = datetime_class
-
- def freeze(self: BaseTraveller) -> BaseTraveller:
- raise NotImplementedError()
-
- def travel_back(self: BaseTraveller) -> BaseTraveller:
- raise NotImplementedError()
-
- def travel(
- self,
- years: int = 0,
- months: int = 0,
- weeks: int = 0,
- days: int = 0,
- hours: int = 0,
- minutes: int = 0,
- seconds: int = 0,
- microseconds: int = 0,
- ) -> BaseTraveller:
- raise NotImplementedError()
-
- def travel_to(self, dt: DateTime) -> BaseTraveller:
- raise NotImplementedError()
-
-
-if not PYPY:
- import time_machine
-
- class Traveller(BaseTraveller):
- def __init__(self, datetime_class: type[DateTime] = DateTime) -> None:
- super().__init__(datetime_class)
-
- self._started: bool = False
- self._traveller: time_machine.travel | None = None
- self._coordinates: time_machine.Coordinates | None = None
-
- def freeze(self) -> Traveller:
- if self._started:
- cast(time_machine.Coordinates, self._coordinates).move_to(
- self._datetime_class.now(), tick=False
- )
- else:
- self._start(freeze=True)
-
- return self
-
- def travel_back(self) -> Traveller:
- if not self._started:
- return self
-
- cast(time_machine.travel, self._traveller).stop()
- self._coordinates = None
- self._traveller = None
- self._started = False
-
- return self
-
- def travel(
- self,
- years: int = 0,
- months: int = 0,
- weeks: int = 0,
- days: int = 0,
- hours: int = 0,
- minutes: int = 0,
- seconds: int = 0,
- microseconds: int = 0,
- *,
- freeze: bool = False,
- ) -> Traveller:
- self._start(freeze=freeze)
-
- cast(time_machine.Coordinates, self._coordinates).move_to(
- self._datetime_class.now().add(
- years=years,
- months=months,
- weeks=weeks,
- days=days,
- hours=hours,
- minutes=minutes,
- seconds=seconds,
- microseconds=microseconds,
- )
- )
-
- return self
-
- def travel_to(self, dt: DateTime, *, freeze: bool = False) -> Traveller:
- self._start(freeze=freeze)
-
- cast(time_machine.Coordinates, self._coordinates).move_to(dt)
-
- return self
-
- def _start(self, freeze: bool = False) -> None:
- if self._started:
- return
-
- if not self._traveller:
- self._traveller = time_machine.travel(
- self._datetime_class.now(), tick=not freeze
- )
-
- self._coordinates = self._traveller.start()
-
- self._started = True
-
- def __enter__(self) -> Traveller:
- self._start()
-
- return self
-
- def __exit__(
- self,
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: TracebackType,
- ) -> None:
- self.travel_back()
-
-else:
-
- class Traveller(BaseTraveller): # type: ignore[no-redef]
-
- ...
diff --git a/pendulum/utils/_compat.py b/pendulum/utils/_compat.py
deleted file mode 100644
index 8f32f9e..0000000
--- a/pendulum/utils/_compat.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from __future__ import annotations
-
-import sys
-
-PYPY = hasattr(sys, "pypy_version_info")
-PY38 = sys.version_info[:2] >= (3, 8)
-
-try:
- from backports import zoneinfo
-except ImportError:
- import zoneinfo # type: ignore[no-redef]
-
-__all__ = ["zoneinfo"]
diff --git a/poetry.lock b/poetry.lock
index 03a2975..b792b1d 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,161 +1,261 @@
-# This file is automatically @generated by Poetry and should not be changed by hand.
-
-[[package]]
-name = "atomicwrites"
-version = "1.4.1"
-description = "Atomic file writes."
-category = "dev"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-
-[[package]]
-name = "attrs"
-version = "22.1.0"
-description = "Classes Without Boilerplate"
-category = "dev"
-optional = false
-python-versions = ">=3.5"
-
-[package.extras]
-dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"]
-docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"]
-tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"]
-tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"]
+# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
[[package]]
name = "babel"
-version = "2.10.3"
+version = "2.14.0"
description = "Internationalization utilities"
-category = "dev"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
+files = [
+ {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"},
+ {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"},
+]
[package.dependencies]
-pytz = ">=2015.7"
+pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""}
+
+[package.extras]
+dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"]
[[package]]
name = "backports-zoneinfo"
version = "0.2.1"
description = "Backport of the standard library zoneinfo module"
-category = "main"
optional = false
python-versions = ">=3.6"
+files = [
+ {file = "backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"},
+ {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"},
+ {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546"},
+ {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win32.whl", hash = "sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08"},
+ {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7"},
+ {file = "backports.zoneinfo-0.2.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac"},
+ {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf"},
+ {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570"},
+ {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win32.whl", hash = "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b"},
+ {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582"},
+ {file = "backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987"},
+ {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1"},
+ {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9"},
+ {file = "backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328"},
+ {file = "backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6"},
+ {file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"},
+]
[package.extras]
tzdata = ["tzdata"]
[[package]]
-name = "black"
-version = "22.6.0"
-description = "The uncompromising code formatter."
-category = "dev"
+name = "cachetools"
+version = "5.3.2"
+description = "Extensible memoizing collections and decorators"
optional = false
-python-versions = ">=3.6.2"
+python-versions = ">=3.7"
+files = [
+ {file = "cachetools-5.3.2-py3-none-any.whl", hash = "sha256:861f35a13a451f94e301ce2bec7cac63e881232ccce7ed67fab9b5df4d3beaa1"},
+ {file = "cachetools-5.3.2.tar.gz", hash = "sha256:086ee420196f7b2ab9ca2db2520aca326318b68fe5ba8bc4d49cca91add450f2"},
+]
-[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]]
+name = "cffi"
+version = "1.15.1"
+description = "Foreign Function Interface for Python calling C code."
+optional = false
+python-versions = "*"
+files = [
+ {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"},
+ {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"},
+ {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"},
+ {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"},
+ {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"},
+ {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"},
+ {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"},
+ {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"},
+ {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"},
+ {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"},
+ {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"},
+ {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"},
+ {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"},
+ {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"},
+ {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"},
+ {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"},
+ {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"},
+ {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"},
+ {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"},
+ {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"},
+ {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"},
+ {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"},
+ {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"},
+ {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"},
+ {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"},
+ {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"},
+ {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"},
+ {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"},
+ {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"},
+ {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"},
+ {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"},
+ {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"},
+ {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"},
+ {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"},
+ {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"},
+ {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"},
+ {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"},
+ {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"},
+ {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"},
+ {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"},
+ {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"},
+ {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"},
+ {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"},
+ {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"},
+ {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"},
+ {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"},
+ {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"},
+ {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"},
+ {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"},
+ {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"},
+ {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"},
+ {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"},
+ {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"},
+ {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"},
+ {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"},
+ {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"},
+ {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"},
+ {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"},
+ {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"},
+ {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"},
+ {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"},
+ {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"},
+ {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"},
+ {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"},
+]
-[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.dependencies]
+pycparser = "*"
[[package]]
name = "cfgv"
-version = "3.3.1"
+version = "3.4.0"
description = "Validate configuration and produce human readable error messages."
-category = "dev"
optional = false
-python-versions = ">=3.6.1"
+python-versions = ">=3.8"
+files = [
+ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"},
+ {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"},
+]
+
+[[package]]
+name = "chardet"
+version = "5.2.0"
+description = "Universal encoding detector for Python 3"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"},
+ {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"},
+]
[[package]]
name = "cleo"
-version = "1.0.0a5"
+version = "2.1.0"
description = "Cleo allows you to create beautiful and testable command-line interfaces."
-category = "dev"
optional = false
python-versions = ">=3.7,<4.0"
+files = [
+ {file = "cleo-2.1.0-py3-none-any.whl", hash = "sha256:4a31bd4dd45695a64ee3c4758f583f134267c2bc518d8ae9a29cf237d009b07e"},
+ {file = "cleo-2.1.0.tar.gz", hash = "sha256:0b2c880b5d13660a7ea651001fb4acb527696c01f15c9ee650f377aa543fd523"},
+]
[package.dependencies]
-crashtest = ">=0.3.1,<0.4.0"
-pylev = ">=1.3.0,<2.0.0"
+crashtest = ">=0.4.1,<0.5.0"
+rapidfuzz = ">=3.0.0,<4.0.0"
[[package]]
name = "click"
-version = "8.1.3"
+version = "8.1.7"
description = "Composable command line interface toolkit"
-category = "dev"
optional = false
python-versions = ">=3.7"
+files = [
+ {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
+ {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
+]
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
-importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
[[package]]
name = "colorama"
-version = "0.4.5"
+version = "0.4.6"
description = "Cross-platform colored terminal text."
-category = "dev"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-
-[[package]]
-name = "coverage"
-version = "6.4.2"
-description = "Code coverage measurement for Python"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-
-[package.dependencies]
-tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
-
-[package.extras]
-toml = ["tomli"]
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+files = [
+ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
+]
[[package]]
name = "crashtest"
-version = "0.3.1"
+version = "0.4.1"
description = "Manage Python errors with ease"
-category = "dev"
optional = false
-python-versions = ">=3.6,<4.0"
+python-versions = ">=3.7,<4.0"
+files = [
+ {file = "crashtest-0.4.1-py3-none-any.whl", hash = "sha256:8d23eac5fa660409f57472e3851dab7ac18aba459a8d19cbbba86d3d5aecd2a5"},
+ {file = "crashtest-0.4.1.tar.gz", hash = "sha256:80d7b1f316ebfbd429f648076d6275c877ba30ba48979de4191714a75266f0ce"},
+]
[[package]]
name = "distlib"
-version = "0.3.5"
+version = "0.3.8"
description = "Distribution utilities"
-category = "dev"
optional = false
python-versions = "*"
+files = [
+ {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"},
+ {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"},
+]
+
+[[package]]
+name = "exceptiongroup"
+version = "1.2.0"
+description = "Backport of PEP 654 (exception groups)"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"},
+ {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"},
+]
+
+[package.extras]
+test = ["pytest (>=6)"]
[[package]]
name = "filelock"
-version = "3.7.1"
+version = "3.13.1"
description = "A platform independent file lock."
-category = "dev"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
+files = [
+ {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"},
+ {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"},
+]
[package.extras]
-docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"]
-testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"]
+docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"]
+testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"]
+typing = ["typing-extensions (>=4.8)"]
[[package]]
name = "ghp-import"
version = "2.1.0"
description = "Copy your docs directly to the gh-pages branch."
-category = "dev"
optional = false
python-versions = "*"
+files = [
+ {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"},
+ {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"},
+]
[package.dependencies]
python-dateutil = ">=2.8.1"
@@ -165,76 +265,76 @@ dev = ["flake8", "markdown", "twine", "wheel"]
[[package]]
name = "identify"
-version = "2.5.3"
+version = "2.5.33"
description = "File identification library for Python"
-category = "dev"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
+files = [
+ {file = "identify-2.5.33-py2.py3-none-any.whl", hash = "sha256:d40ce5fcd762817627670da8a7d8d8e65f24342d14539c59488dc603bf662e34"},
+ {file = "identify-2.5.33.tar.gz", hash = "sha256:161558f9fe4559e1557e1bff323e8631f6a0e4837f7497767c1782832f16b62d"},
+]
[package.extras]
license = ["ukkonen"]
[[package]]
name = "importlib-metadata"
-version = "4.12.0"
+version = "7.0.0"
description = "Read metadata from Python packages"
-category = "dev"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
+files = [
+ {file = "importlib_metadata-7.0.0-py3-none-any.whl", hash = "sha256:d97503976bb81f40a193d41ee6570868479c69d5068651eb039c40d850c59d67"},
+ {file = "importlib_metadata-7.0.0.tar.gz", hash = "sha256:7fc841f8b8332803464e5dc1c63a2e59121f46ca186c0e2e182e80bf8c1319f7"},
+]
[package.dependencies]
-typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
zipp = ">=0.5"
[package.extras]
-docs = ["jaraco.packaging (>=9)", "rst.linker (>=1.9)", "sphinx"]
+docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
perf = ["ipython"]
-testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"]
+testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"]
[[package]]
name = "importlib-resources"
-version = "5.9.0"
+version = "6.1.1"
description = "Read resources from Python packages"
-category = "main"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
+files = [
+ {file = "importlib_resources-6.1.1-py3-none-any.whl", hash = "sha256:e8bf90d8213b486f428c9c39714b920041cb02c184686a3dee24905aaa8105d6"},
+ {file = "importlib_resources-6.1.1.tar.gz", hash = "sha256:3893a00122eafde6894c59914446a512f728a0c1a45f9bb9b63721b6bacf0b4a"},
+]
[package.dependencies]
zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""}
[package.extras]
-docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"]
-testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
+docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
+testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff", "zipp (>=3.17)"]
[[package]]
name = "iniconfig"
-version = "1.1.1"
-description = "iniconfig: brain-dead simple config-ini parsing"
-category = "dev"
-optional = false
-python-versions = "*"
-
-[[package]]
-name = "isort"
-version = "5.10.1"
-description = "A Python utility / library to sort Python imports."
-category = "dev"
+version = "2.0.0"
+description = "brain-dead simple config-ini parsing"
optional = false
-python-versions = ">=3.6.1,<4.0"
-
-[package.extras]
-colors = ["colorama (>=0.4.3,<0.5.0)"]
-pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
-plugins = ["setuptools"]
-requirements_deprecated_finder = ["pip-api", "pipreqs"]
+python-versions = ">=3.7"
+files = [
+ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
+ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
+]
[[package]]
name = "jinja2"
version = "3.1.2"
description = "A very fast and expressive template engine."
-category = "dev"
optional = false
python-versions = ">=3.7"
+files = [
+ {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
+ {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
+]
[package.dependencies]
MarkupSafe = ">=2.0"
@@ -244,152 +344,288 @@ i18n = ["Babel (>=2.7)"]
[[package]]
name = "markdown"
-version = "3.4.1"
-description = "Python implementation of Markdown."
-category = "dev"
+version = "3.5.1"
+description = "Python implementation of John Gruber's Markdown."
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
+files = [
+ {file = "Markdown-3.5.1-py3-none-any.whl", hash = "sha256:5874b47d4ee3f0b14d764324d2c94c03ea66bee56f2d929da9f2508d65e722dc"},
+ {file = "Markdown-3.5.1.tar.gz", hash = "sha256:b65d7beb248dc22f2e8a31fb706d93798093c308dc1aba295aedeb9d41a813bd"},
+]
[package.dependencies]
importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""}
[package.extras]
+docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"]
testing = ["coverage", "pyyaml"]
[[package]]
name = "markdown-include"
-version = "0.5.1"
-description = "This is an extension to Python-Markdown which provides an \"include\" function, similar to that found in LaTeX (and also the C pre-processor and Fortran). I originally wrote it for my FORD Fortran auto-documentation generator."
-category = "dev"
+version = "0.8.1"
+description = "A Python-Markdown extension which provides an 'include' function"
optional = false
-python-versions = "*"
+python-versions = ">=3.7"
+files = [
+ {file = "markdown-include-0.8.1.tar.gz", hash = "sha256:1d0623e0fc2757c38d35df53752768356162284259d259c486b4ab6285cdbbe3"},
+ {file = "markdown_include-0.8.1-py3-none-any.whl", hash = "sha256:32f0635b9cfef46997b307e2430022852529f7a5b87c0075c504283e7cc7db53"},
+]
[package.dependencies]
-markdown = "*"
+markdown = ">=3.0"
+
+[package.extras]
+tests = ["pytest"]
[[package]]
name = "markupsafe"
-version = "2.1.1"
+version = "2.1.3"
description = "Safely add untrusted strings to HTML/XML markup."
-category = "dev"
optional = false
python-versions = ">=3.7"
+files = [
+ {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"},
+ {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"},
+ {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"},
+ {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"},
+ {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"},
+ {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"},
+ {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"},
+ {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"},
+ {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"},
+ {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"},
+ {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"},
+ {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"},
+ {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"},
+ {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"},
+ {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"},
+ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"},
+ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"},
+ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"},
+ {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"},
+ {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"},
+ {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"},
+ {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"},
+ {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"},
+ {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"},
+ {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"},
+ {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"},
+ {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"},
+ {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"},
+ {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"},
+ {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"},
+ {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"},
+ {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"},
+ {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"},
+ {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"},
+ {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"},
+ {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"},
+ {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"},
+ {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"},
+ {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"},
+ {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"},
+ {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"},
+ {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"},
+ {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"},
+ {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"},
+ {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"},
+ {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"},
+ {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"},
+ {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"},
+ {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"},
+ {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"},
+]
+
+[[package]]
+name = "maturin"
+version = "1.4.0"
+description = "Build and publish crates with pyo3, rust-cpython and cffi bindings as well as rust binaries as python packages"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "maturin-1.4.0-py3-none-linux_armv6l.whl", hash = "sha256:b84bee85620e1b7b662a7af71289f7f6c23df8269e42c0f76882676dfc9c733f"},
+ {file = "maturin-1.4.0-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:076970a73da7fa3648204a584cd347b899c1ea67f8124b212bccd06728e63ed9"},
+ {file = "maturin-1.4.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f8eded83abdb30b2b6ae6d32c80b8192bdd8bcfec0ebfacee6ac02434aa499d6"},
+ {file = "maturin-1.4.0-py3-none-manylinux_2_12_i686.manylinux2010_i686.musllinux_1_1_i686.whl", hash = "sha256:ff95a4494d9e57b6e74d4d7f8a9a2ee8ed29bd7f0e61855656ad959a432c0efc"},
+ {file = "maturin-1.4.0-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:16239a7648ef17976585353e381840c18e650d352576ed9545abca407d65e534"},
+ {file = "maturin-1.4.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:77428c043d585f038f4b056c4d617e00a8027b49598ab6d065b8f6b9b9b8d144"},
+ {file = "maturin-1.4.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:b4b2f006db1e92687c814576029157dcc2d97b5750fd35fd4f3aabb97e36444f"},
+ {file = "maturin-1.4.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:ffe4e967080ceb83c156e73a37d3974b30cad01c376a86dc39a76a0c6bccf9b0"},
+ {file = "maturin-1.4.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:01473dc30aed8f2cee3572b3e99e3ea75bf09c84b028bf6077f7643a189699c8"},
+ {file = "maturin-1.4.0-py3-none-win32.whl", hash = "sha256:e669ba5984c15e29b8545b295ba6738974180b44f47f5d9e75569a5ce6b8add5"},
+ {file = "maturin-1.4.0-py3-none-win_amd64.whl", hash = "sha256:e2c1b157397ef3721b9c2f3f24d9a5a60bd84322aac13b4dd0704a80448741b0"},
+ {file = "maturin-1.4.0-py3-none-win_arm64.whl", hash = "sha256:2979175a7eee837dc3a6931980b37ddc86b9ced54d600856668fc074ca2530ef"},
+ {file = "maturin-1.4.0.tar.gz", hash = "sha256:ed12e1768094a7adeafc3a74ebdb8dc2201fa64c4e7e31f14cfc70378bf93790"},
+]
+
+[package.dependencies]
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+
+[package.extras]
+patchelf = ["patchelf"]
+zig = ["ziglang (>=0.10.0,<0.11.0)"]
[[package]]
name = "mergedeep"
version = "1.3.4"
description = "A deep merge function for 🐍."
-category = "dev"
optional = false
python-versions = ">=3.6"
-
-[[package]]
-name = "meson"
-version = "0.63.2"
-description = "A high performance build system"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-
-[package.extras]
-ninja = ["ninja (>=1.8.2)"]
-progress = ["tqdm"]
-typing = ["mypy", "typing-extensions"]
+files = [
+ {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"},
+ {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"},
+]
[[package]]
name = "mkdocs"
-version = "1.3.0"
+version = "1.5.3"
description = "Project documentation with Markdown."
-category = "dev"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
+files = [
+ {file = "mkdocs-1.5.3-py3-none-any.whl", hash = "sha256:3b3a78e736b31158d64dbb2f8ba29bd46a379d0c6e324c2246c3bc3d2189cfc1"},
+ {file = "mkdocs-1.5.3.tar.gz", hash = "sha256:eb7c99214dcb945313ba30426c2451b735992c73c2e10838f76d09e39ff4d0e2"},
+]
[package.dependencies]
-click = ">=3.3"
+click = ">=7.0"
+colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""}
ghp-import = ">=1.0"
-importlib-metadata = ">=4.3"
-Jinja2 = ">=2.10.2"
-Markdown = ">=3.2.1"
+importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""}
+jinja2 = ">=2.11.1"
+markdown = ">=3.2.1"
+markupsafe = ">=2.0.1"
mergedeep = ">=1.3.4"
packaging = ">=20.5"
-PyYAML = ">=3.10"
+pathspec = ">=0.11.1"
+platformdirs = ">=2.2.0"
+pyyaml = ">=5.1"
pyyaml-env-tag = ">=0.1"
watchdog = ">=2.0"
[package.extras]
i18n = ["babel (>=2.9.0)"]
+min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pathspec (==0.11.1)", "platformdirs (==2.2.0)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"]
[[package]]
-name = "mypy-extensions"
-version = "0.4.3"
-description = "Experimental type system extensions for programs checked with the mypy typechecker."
-category = "dev"
+name = "mypy"
+version = "1.7.1"
+description = "Optional static typing for Python"
optional = false
-python-versions = "*"
+python-versions = ">=3.8"
+files = [
+ {file = "mypy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12cce78e329838d70a204293e7b29af9faa3ab14899aec397798a4b41be7f340"},
+ {file = "mypy-1.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1484b8fa2c10adf4474f016e09d7a159602f3239075c7bf9f1627f5acf40ad49"},
+ {file = "mypy-1.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31902408f4bf54108bbfb2e35369877c01c95adc6192958684473658c322c8a5"},
+ {file = "mypy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f2c2521a8e4d6d769e3234350ba7b65ff5d527137cdcde13ff4d99114b0c8e7d"},
+ {file = "mypy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:fcd2572dd4519e8a6642b733cd3a8cfc1ef94bafd0c1ceed9c94fe736cb65b6a"},
+ {file = "mypy-1.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b901927f16224d0d143b925ce9a4e6b3a758010673eeded9b748f250cf4e8f7"},
+ {file = "mypy-1.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2f7f6985d05a4e3ce8255396df363046c28bea790e40617654e91ed580ca7c51"},
+ {file = "mypy-1.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:944bdc21ebd620eafefc090cdf83158393ec2b1391578359776c00de00e8907a"},
+ {file = "mypy-1.7.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9c7ac372232c928fff0645d85f273a726970c014749b924ce5710d7d89763a28"},
+ {file = "mypy-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:f6efc9bd72258f89a3816e3a98c09d36f079c223aa345c659622f056b760ab42"},
+ {file = "mypy-1.7.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6dbdec441c60699288adf051f51a5d512b0d818526d1dcfff5a41f8cd8b4aaf1"},
+ {file = "mypy-1.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4fc3d14ee80cd22367caaaf6e014494415bf440980a3045bf5045b525680ac33"},
+ {file = "mypy-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c6e4464ed5f01dc44dc9821caf67b60a4e5c3b04278286a85c067010653a0eb"},
+ {file = "mypy-1.7.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d9b338c19fa2412f76e17525c1b4f2c687a55b156320acb588df79f2e6fa9fea"},
+ {file = "mypy-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:204e0d6de5fd2317394a4eff62065614c4892d5a4d1a7ee55b765d7a3d9e3f82"},
+ {file = "mypy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:84860e06ba363d9c0eeabd45ac0fde4b903ad7aa4f93cd8b648385a888e23200"},
+ {file = "mypy-1.7.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8c5091ebd294f7628eb25ea554852a52058ac81472c921150e3a61cdd68f75a7"},
+ {file = "mypy-1.7.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40716d1f821b89838589e5b3106ebbc23636ffdef5abc31f7cd0266db936067e"},
+ {file = "mypy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cf3f0c5ac72139797953bd50bc6c95ac13075e62dbfcc923571180bebb662e9"},
+ {file = "mypy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:78e25b2fd6cbb55ddfb8058417df193f0129cad5f4ee75d1502248e588d9e0d7"},
+ {file = "mypy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:75c4d2a6effd015786c87774e04331b6da863fc3fc4e8adfc3b40aa55ab516fe"},
+ {file = "mypy-1.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2643d145af5292ee956aa0a83c2ce1038a3bdb26e033dadeb2f7066fb0c9abce"},
+ {file = "mypy-1.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75aa828610b67462ffe3057d4d8a4112105ed211596b750b53cbfe182f44777a"},
+ {file = "mypy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ee5d62d28b854eb61889cde4e1dbc10fbaa5560cb39780c3995f6737f7e82120"},
+ {file = "mypy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:72cf32ce7dd3562373f78bd751f73c96cfb441de147cc2448a92c1a308bd0ca6"},
+ {file = "mypy-1.7.1-py3-none-any.whl", hash = "sha256:f7c5d642db47376a0cc130f0de6d055056e010debdaf0707cd2b0fc7e7ef30ea"},
+ {file = "mypy-1.7.1.tar.gz", hash = "sha256:fcb6d9afb1b6208b4c712af0dafdc650f518836065df0d4fb1d800f5d6773db2"},
+]
-[[package]]
-name = "ninja"
-version = "1.10.2.3"
-description = "Ninja is a small build system with a focus on speed"
-category = "dev"
-optional = false
-python-versions = "*"
+[package.dependencies]
+mypy-extensions = ">=1.0.0"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+typing-extensions = ">=4.1.0"
[package.extras]
-test = ["codecov (>=2.0.5)", "coverage (>=4.2)", "flake8 (>=3.0.4)", "pytest (>=4.5.0)", "pytest-cov (>=2.7.1)", "pytest-runner (>=5.1)", "pytest-virtualenv (>=1.7.0)", "virtualenv (>=15.0.3)"]
+dmypy = ["psutil (>=4.0)"]
+install-types = ["pip"]
+mypyc = ["setuptools (>=50)"]
+reports = ["lxml"]
+
+[[package]]
+name = "mypy-extensions"
+version = "1.0.0"
+description = "Type system extensions for programs checked with the mypy type checker."
+optional = false
+python-versions = ">=3.5"
+files = [
+ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
+ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
+]
[[package]]
name = "nodeenv"
-version = "1.7.0"
+version = "1.8.0"
description = "Node.js virtual environment builder"
-category = "dev"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*"
+files = [
+ {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"},
+ {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"},
+]
[package.dependencies]
setuptools = "*"
[[package]]
name = "packaging"
-version = "21.3"
+version = "23.2"
description = "Core utilities for Python packages"
-category = "dev"
optional = false
-python-versions = ">=3.6"
-
-[package.dependencies]
-pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
+python-versions = ">=3.7"
+files = [
+ {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
+ {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
+]
[[package]]
name = "pathspec"
-version = "0.9.0"
+version = "0.12.1"
description = "Utility library for gitignore style pattern matching of file paths."
-category = "dev"
optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
+python-versions = ">=3.8"
+files = [
+ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
+ {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
+]
[[package]]
name = "platformdirs"
-version = "2.5.2"
-description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
-category = "dev"
+version = "4.1.0"
+description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
+files = [
+ {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"},
+ {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"},
+]
[package.extras]
-docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)"]
-test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
+docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"]
+test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"]
[[package]]
name = "pluggy"
-version = "1.0.0"
+version = "1.3.0"
description = "plugin and hook calling mechanisms for python"
-category = "dev"
optional = false
-python-versions = ">=3.6"
-
-[package.dependencies]
-importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
+python-versions = ">=3.8"
+files = [
+ {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"},
+ {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"},
+]
[package.extras]
dev = ["pre-commit", "tox"]
@@ -397,776 +633,612 @@ testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pre-commit"
-version = "2.20.0"
+version = "3.5.0"
description = "A framework for managing and maintaining multi-language pre-commit hooks."
-category = "dev"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
+files = [
+ {file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"},
+ {file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"},
+]
[package.dependencies]
cfgv = ">=2.0.0"
identify = ">=1.0.0"
-importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
nodeenv = ">=0.11.1"
pyyaml = ">=5.1"
-toml = "*"
-virtualenv = ">=20.0.8"
+virtualenv = ">=20.10.0"
[[package]]
-name = "py"
-version = "1.11.0"
-description = "library with cross-python path, ini-parsing, io, code, log facilities"
-category = "dev"
+name = "py-cpuinfo"
+version = "9.0.0"
+description = "Get CPU info with pure Python"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+python-versions = "*"
+files = [
+ {file = "py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690"},
+ {file = "py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5"},
+]
[[package]]
-name = "pygments"
-version = "2.12.0"
-description = "Pygments is a syntax highlighting package written in Python."
-category = "dev"
+name = "pycparser"
+version = "2.21"
+description = "C parser in Python"
optional = false
-python-versions = ">=3.6"
+python-versions = "*"
+files = [
+ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
+ {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
+]
[[package]]
-name = "pylev"
-version = "1.4.0"
-description = "A pure Python Levenshtein implementation that's not freaking GPL'd."
-category = "dev"
+name = "pygments"
+version = "2.17.2"
+description = "Pygments is a syntax highlighting package written in Python."
optional = false
-python-versions = "*"
+python-versions = ">=3.7"
+files = [
+ {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"},
+ {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"},
+]
+
+[package.extras]
+plugins = ["importlib-metadata"]
+windows-terminal = ["colorama (>=0.4.6)"]
[[package]]
name = "pymdown-extensions"
-version = "6.3"
+version = "10.5"
description = "Extension pack for Python Markdown."
-category = "dev"
optional = false
-python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
+python-versions = ">=3.8"
+files = [
+ {file = "pymdown_extensions-10.5-py3-none-any.whl", hash = "sha256:1f0ca8bb5beff091315f793ee17683bc1390731f6ac4c5eb01e27464b80fe879"},
+ {file = "pymdown_extensions-10.5.tar.gz", hash = "sha256:1b60f1e462adbec5a1ed79dac91f666c9c0d241fa294de1989f29d20096cfd0b"},
+]
[package.dependencies]
-Markdown = ">=3.2"
+markdown = ">=3.5"
+pyyaml = "*"
+
+[package.extras]
+extra = ["pygments (>=2.12)"]
[[package]]
-name = "pyparsing"
-version = "3.0.9"
-description = "pyparsing module - Classes and methods to define and execute parsing grammars"
-category = "dev"
+name = "pyproject-api"
+version = "1.6.1"
+description = "API to interact with the python pyproject.toml based projects"
optional = false
-python-versions = ">=3.6.8"
+python-versions = ">=3.8"
+files = [
+ {file = "pyproject_api-1.6.1-py3-none-any.whl", hash = "sha256:4c0116d60476b0786c88692cf4e325a9814965e2469c5998b830bba16b183675"},
+ {file = "pyproject_api-1.6.1.tar.gz", hash = "sha256:1817dc018adc0d1ff9ca1ed8c60e1623d5aaca40814b953af14a9cf9a5cae538"},
+]
+
+[package.dependencies]
+packaging = ">=23.1"
+tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""}
[package.extras]
-diagrams = ["jinja2", "railroad-diagrams"]
+docs = ["furo (>=2023.8.19)", "sphinx (<7.2)", "sphinx-autodoc-typehints (>=1.24)"]
+testing = ["covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "setuptools (>=68.1.2)", "wheel (>=0.41.2)"]
[[package]]
name = "pytest"
-version = "7.1.2"
+version = "7.4.3"
description = "pytest: simple powerful testing with Python"
-category = "dev"
optional = false
python-versions = ">=3.7"
+files = [
+ {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"},
+ {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"},
+]
[package.dependencies]
-atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
-attrs = ">=19.2.0"
colorama = {version = "*", markers = "sys_platform == \"win32\""}
-importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
+exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=0.12,<2.0"
-py = ">=1.8.2"
-tomli = ">=1.0.0"
+tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
[package.extras]
-testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
+testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
[[package]]
-name = "pytest-cov"
-version = "3.0.0"
-description = "Pytest plugin for measuring coverage."
-category = "dev"
+name = "pytest-benchmark"
+version = "4.0.0"
+description = "A ``pytest`` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer."
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
+files = [
+ {file = "pytest-benchmark-4.0.0.tar.gz", hash = "sha256:fb0785b83efe599a6a956361c0691ae1dbb5318018561af10f3e915caa0048d1"},
+ {file = "pytest_benchmark-4.0.0-py3-none-any.whl", hash = "sha256:fdb7db64e31c8b277dff9850d2a2556d8b60bcb0ea6524e36e28ffd7c87f71d6"},
+]
+
+[package.dependencies]
+py-cpuinfo = "*"
+pytest = ">=3.8"
+
+[package.extras]
+aspect = ["aspectlib"]
+elasticsearch = ["elasticsearch"]
+histogram = ["pygal", "pygaljs"]
+
+[[package]]
+name = "pytest-codspeed"
+version = "1.2.2"
+description = "Pytest plugin to create CodSpeed benchmarks"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "pytest_codspeed-1.2.2-py3-none-any.whl", hash = "sha256:b8971152556e90900ae9bb8135b268592c9f3c9450974a2468a5e17f21d59ec1"},
+ {file = "pytest_codspeed-1.2.2.tar.gz", hash = "sha256:c59573f571181dc6a5ff423e85e1ac5aeeda18eca89d5116ff4b0897b79c98b5"},
+]
[package.dependencies]
-coverage = {version = ">=5.2.1", extras = ["toml"]}
-pytest = ">=4.6"
+cffi = ">=1.15.1,<1.16.0"
+pytest = ">=3.8"
[package.extras]
-testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
+compatibility = ["pytest-benchmarks (>=3.4.1,<3.5.0)"]
+dev = ["black (>=22.3.0,<22.4.0)", "flake8 (>=5.0.4,<5.1.0)", "hatchling (>=1.11.1,<1.12.0)", "isort (>=5.8.0,<5.9.0)", "mypy (>=0.961,<1.0)", "pytest (>=7.0,<8.0)", "pytest-cov (>=4.0.0,<4.1.0)", "ruff (>=0.0.100,<0.1.0)"]
[[package]]
name = "python-dateutil"
version = "2.8.2"
description = "Extensions to the standard Python datetime module"
-category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+files = [
+ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
+ {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
+]
[package.dependencies]
six = ">=1.5"
[[package]]
name = "pytz"
-version = "2022.1"
+version = "2023.3.post1"
description = "World timezone definitions, modern and historical"
-category = "dev"
optional = false
python-versions = "*"
+files = [
+ {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"},
+ {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"},
+]
[[package]]
name = "pyyaml"
-version = "6.0"
+version = "6.0.1"
description = "YAML parser and emitter for Python"
-category = "dev"
optional = false
python-versions = ">=3.6"
+files = [
+ {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"},
+ {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"},
+ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"},
+ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"},
+ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"},
+ {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"},
+ {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
+ {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
+ {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"},
+ {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"},
+ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"},
+ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"},
+ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"},
+ {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"},
+ {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
+ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
+ {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
+ {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
+ {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
+ {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
+ {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
+ {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"},
+ {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"},
+ {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"},
+ {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"},
+ {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"},
+ {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"},
+ {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"},
+ {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"},
+ {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"},
+ {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"},
+ {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"},
+ {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"},
+ {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"},
+ {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"},
+ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"},
+ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"},
+ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"},
+ {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"},
+ {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"},
+ {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
+ {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"},
+ {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"},
+ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"},
+ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"},
+ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"},
+ {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"},
+ {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"},
+ {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
+ {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
+]
[[package]]
name = "pyyaml-env-tag"
version = "0.1"
description = "A custom YAML tag for referencing environment variables in YAML files. "
-category = "dev"
optional = false
python-versions = ">=3.6"
+files = [
+ {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"},
+ {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"},
+]
[package.dependencies]
pyyaml = "*"
[[package]]
+name = "rapidfuzz"
+version = "3.5.2"
+description = "rapid fuzzy string matching"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "rapidfuzz-3.5.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1a047d6e58833919d742bbc0dfa66d1de4f79e8562ee195007d3eae96635df39"},
+ {file = "rapidfuzz-3.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:22877c027c492b7dc7e3387a576a33ed5aad891104aa90da2e0844c83c5493ef"},
+ {file = "rapidfuzz-3.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e0f448b0eacbcc416feb634e1232a48d1cbde5e60f269c84e4fb0912f7bbb001"},
+ {file = "rapidfuzz-3.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d05146497672f869baf41147d5ec1222788c70e5b8b0cfcd6e95597c75b5b96b"},
+ {file = "rapidfuzz-3.5.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f2df3968738a38d2a0058b5e721753f5d3d602346a1027b0dde31b0476418f3"},
+ {file = "rapidfuzz-3.5.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5afc1fcf1830f9bb87d3b490ba03691081b9948a794ea851befd2643069a30c1"},
+ {file = "rapidfuzz-3.5.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84be69ea65f64fa01e5c4976be9826a5aa949f037508887add42da07420d65d6"},
+ {file = "rapidfuzz-3.5.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8658c1045766e87e0038323aa38b4a9f49b7f366563271f973c8890a98aa24b5"},
+ {file = "rapidfuzz-3.5.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:852b3f93c15fce58b8dc668bd54123713bfdbbb0796ba905ea5df99cfd083132"},
+ {file = "rapidfuzz-3.5.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:12424a06ad9bd0cbf5f7cea1015e78d924a0034a0e75a5a7b39c0703dcd94095"},
+ {file = "rapidfuzz-3.5.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b4e9ded8e80530bd7205a7a2b01802f934a4695ca9e9fbe1ce9644f5e0697864"},
+ {file = "rapidfuzz-3.5.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:affb8fe36157c2dc8a7bc45b6a1875eb03e2c49167a1d52789144bdcb7ab3b8c"},
+ {file = "rapidfuzz-3.5.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1d33a622572d384f4c90b5f7a139328246ab5600141e90032b521c2127bd605"},
+ {file = "rapidfuzz-3.5.2-cp310-cp310-win32.whl", hash = "sha256:2cf9f2ed4a97b388cffd48d534452a564c2491f68f4fd5bc140306f774ceb63a"},
+ {file = "rapidfuzz-3.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:6541ffb70097885f7302cd73e2efd77be99841103023c2f9408551f27f45f7a5"},
+ {file = "rapidfuzz-3.5.2-cp310-cp310-win_arm64.whl", hash = "sha256:1dd2542e5103fb8ca46500a979ae14d1609dcba11d2f9fe01e99eec03420e193"},
+ {file = "rapidfuzz-3.5.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bff7d3127ebc5cd908f3a72f6517f31f5247b84666137556a8fcc5177c560939"},
+ {file = "rapidfuzz-3.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fdfdb3685b631d8efbb6d6d3d86eb631be2b408d9adafcadc11e63e3f9c96dec"},
+ {file = "rapidfuzz-3.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:97b043fe8185ec53bb3ff0e59deb89425c0fc6ece6e118939963aab473505801"},
+ {file = "rapidfuzz-3.5.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a4a7832737f87583f3863dc62e6f56dd4a9fefc5f04a7bdcb4c433a0f36bb1b"},
+ {file = "rapidfuzz-3.5.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d876dba9a11fcf60dcf1562c5a84ef559db14c2ceb41e1ad2d93cd1dc085889"},
+ {file = "rapidfuzz-3.5.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa4c0612893716bbb6595066ca9ecb517c982355abe39ba9d1f4ab834ace91ad"},
+ {file = "rapidfuzz-3.5.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:120316824333e376b88b284724cfd394c6ccfcb9818519eab5d58a502e5533f0"},
+ {file = "rapidfuzz-3.5.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9cdbe8e80cc186d55f748a34393533a052d855357d5398a1ccb71a5021b58e8d"},
+ {file = "rapidfuzz-3.5.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1062425c8358a547ae5ebad148f2e0f02417716a571b803b0c68e4d552e99d32"},
+ {file = "rapidfuzz-3.5.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:66be181965aff13301dd5f9b94b646ce39d99c7fe2fd5de1656f4ca7fafcb38c"},
+ {file = "rapidfuzz-3.5.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:53df7aea3cf301633cfa2b4b2c2d2441a87dfc878ef810e5b4eddcd3e68723ad"},
+ {file = "rapidfuzz-3.5.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:76639dca5eb0afc6424ac5f42d43d3bd342ac710e06f38a8c877d5b96de09589"},
+ {file = "rapidfuzz-3.5.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:27689361c747b5f7b8a26056bc60979875323f1c3dcaaa9e2fec88f03b20a365"},
+ {file = "rapidfuzz-3.5.2-cp311-cp311-win32.whl", hash = "sha256:99c9fc5265566fb94731dc6826f43c5109e797078264e6389a36d47814473692"},
+ {file = "rapidfuzz-3.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:666928ee735562a909d81bd2f63207b3214afd4ca41f790ab3025d066975c814"},
+ {file = "rapidfuzz-3.5.2-cp311-cp311-win_arm64.whl", hash = "sha256:d55de67c48f06b7772541e8d4c062a2679205799ce904236e2836cb04c106442"},
+ {file = "rapidfuzz-3.5.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:04e1e02b182283c43c866e215317735e91d22f5d34e65400121c04d5ed7ed859"},
+ {file = "rapidfuzz-3.5.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:365e544aba3ac13acf1a62cb2e5909ad2ba078d0bfc7d69b1f801dfd673b9782"},
+ {file = "rapidfuzz-3.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b61f77d834f94b0099fa9ed35c189b7829759d4e9c2743697a130dd7ba62259f"},
+ {file = "rapidfuzz-3.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43fb368998b9703fa8c63db292a8ab9e988bf6da0c8a635754be8e69da1e7c1d"},
+ {file = "rapidfuzz-3.5.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25510b5d142c47786dbd27cfd9da7cae5bdea28d458379377a3644d8460a3404"},
+ {file = "rapidfuzz-3.5.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bf3093443751e5a419834162af358d1e31dec75f84747a91dbbc47b2c04fc085"},
+ {file = "rapidfuzz-3.5.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2fbaf546f15a924613f89d609ff66b85b4f4c2307ac14d93b80fe1025b713138"},
+ {file = "rapidfuzz-3.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32d580df0e130ed85400ff77e1c32d965e9bc7be29ac4072ab637f57e26d29fb"},
+ {file = "rapidfuzz-3.5.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:358a0fbc49343de20fee8ebdb33c7fa8f55a9ff93ff42d1ffe097d2caa248f1b"},
+ {file = "rapidfuzz-3.5.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fb379ac0ddfc86c5542a225d194f76ed468b071b6f79ff57c4b72e635605ad7d"},
+ {file = "rapidfuzz-3.5.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7fb21e182dc6d83617e88dea002963d5cf99cf5eabbdbf04094f503d8fe8d723"},
+ {file = "rapidfuzz-3.5.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:c04f9f1310ce414ab00bdcbf26d0906755094bfc59402cb66a7722c6f06d70b2"},
+ {file = "rapidfuzz-3.5.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f6da61cc38c1a95efc5edcedf258759e6dbab73191651a28c5719587f32a56ad"},
+ {file = "rapidfuzz-3.5.2-cp312-cp312-win32.whl", hash = "sha256:f823fd1977071486739f484e27092765d693da6beedaceece54edce1dfeec9b2"},
+ {file = "rapidfuzz-3.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:a8162d81486de85ab1606e48e076431b66d44cf431b2b678e9cae458832e7147"},
+ {file = "rapidfuzz-3.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:dfc63fabb7d8da8483ca836bae7e55766fe39c63253571e103c034ba8ea80950"},
+ {file = "rapidfuzz-3.5.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:df8fae2515a1e4936affccac3e7d506dd904de5ff82bc0b1433b4574a51b9bfb"},
+ {file = "rapidfuzz-3.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dd6384780c2a16097d47588844cd677316a90e0f41ef96ff485b62d58de79dcf"},
+ {file = "rapidfuzz-3.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:467a4d730ae3bade87dba6bd769e837ab97e176968ce20591fe8f7bf819115b1"},
+ {file = "rapidfuzz-3.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54576669c1502b751b534bd76a4aeaaf838ed88b30af5d5c1b7d0a3ca5d4f7b5"},
+ {file = "rapidfuzz-3.5.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abafeb82f85a651a9d6d642a33dc021606bc459c33e250925b25d6b9e7105a2e"},
+ {file = "rapidfuzz-3.5.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73e14617a520c0f1bc15eb78c215383477e5ca70922ecaff1d29c63c060e04ca"},
+ {file = "rapidfuzz-3.5.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7cdf92116e9dfe40da17f921cdbfa0039dde9eb158914fa5f01b1e67a20b19cb"},
+ {file = "rapidfuzz-3.5.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1962d5ccf8602589dbf8e85246a0ee2b4050d82fade1568fb76f8a4419257704"},
+ {file = "rapidfuzz-3.5.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:db45028eae2fda7a24759c69ebeb2a7fbcc1a326606556448ed43ee480237a3c"},
+ {file = "rapidfuzz-3.5.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b685abb8b6d97989f6c69556d7934e0e533aa8822f50b9517ff2da06a1d29f23"},
+ {file = "rapidfuzz-3.5.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:40139552961018216b8cd88f6df4ecbbe984f907a62a5c823ccd907132c29a14"},
+ {file = "rapidfuzz-3.5.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:0fef4705459842ef8f79746d6f6a0b5d2b6a61a145d7d8bbe10b2e756ea337c8"},
+ {file = "rapidfuzz-3.5.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6b2ad5516f7068c7d9cbcda8ac5906c589e99bc427df2e1050282ee2d8bc2d58"},
+ {file = "rapidfuzz-3.5.2-cp38-cp38-win32.whl", hash = "sha256:2da3a24c2f7dfca7f26ba04966b848e3bbeb93e54d899908ff88dfe3e1def9dc"},
+ {file = "rapidfuzz-3.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:e3f2be79d4114d01f383096dbee51b57df141cb8b209c19d0cf65f23a24e75ba"},
+ {file = "rapidfuzz-3.5.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:089a7e96e5032821af5964d8457fcb38877cc321cdd06ad7c5d6e3d852264cb9"},
+ {file = "rapidfuzz-3.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:75d8a52bf8d1aa2ac968ae4b21b83b94fc7e5ea3dfbab34811fc60f32df505b2"},
+ {file = "rapidfuzz-3.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2bacce6bbc0362f0789253424269cc742b1f45e982430387db3abe1d0496e371"},
+ {file = "rapidfuzz-3.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5fd627e604ddc02db2ddb9ddc4a91dd92b7a6d6378fcf30bb37b49229072b89"},
+ {file = "rapidfuzz-3.5.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2e8b369f23f00678f6e673572209a5d3b0832f4991888e3df97af7b8b9decf3"},
+ {file = "rapidfuzz-3.5.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c29958265e4c2b937269e804b8a160c027ee1c2627d6152655008a8b8083630e"},
+ {file = "rapidfuzz-3.5.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:00be97f9219355945c46f37ac9fa447046e6f7930f7c901e5d881120d1695458"},
+ {file = "rapidfuzz-3.5.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ada0d8d57e0f556ef38c24fee71bfe8d0db29c678bff2acd1819fc1b74f331c2"},
+ {file = "rapidfuzz-3.5.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:de89585268ed8ee44e80126814cae63ff6b00d08416481f31b784570ef07ec59"},
+ {file = "rapidfuzz-3.5.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:908ff2de9c442b379143d1da3c886c63119d4eba22986806e2533cee603fe64b"},
+ {file = "rapidfuzz-3.5.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:54f0061028723c026020f5bb20649c22bc8a0d9f5363c283bdc5901d4d3bff01"},
+ {file = "rapidfuzz-3.5.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:b581107ec0c610cdea48b25f52030770be390db4a9a73ca58b8d70fa8a5ec32e"},
+ {file = "rapidfuzz-3.5.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1d5a686ea258931aaa38019204bdc670bbe14b389a230b1363d84d6cf4b9dc38"},
+ {file = "rapidfuzz-3.5.2-cp39-cp39-win32.whl", hash = "sha256:97f811ca7709c6ee8c0b55830f63b3d87086f4abbcbb189b4067e1cd7014db7b"},
+ {file = "rapidfuzz-3.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:58ee34350f8c292dd24a050186c0e18301d80da904ef572cf5fda7be6a954929"},
+ {file = "rapidfuzz-3.5.2-cp39-cp39-win_arm64.whl", hash = "sha256:c5075ce7b9286624cafcf36720ef1cfb2946d75430b87cb4d1f006e82cd71244"},
+ {file = "rapidfuzz-3.5.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:af5221e4f7800db3e84c46b79dba4112e3b3cc2678f808bdff4fcd2487073846"},
+ {file = "rapidfuzz-3.5.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8501d7875b176930e6ed9dbc1bc35adb37ef312f6106bd6bb5c204adb90160ac"},
+ {file = "rapidfuzz-3.5.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e414e1ca40386deda4291aa2d45062fea0fbaa14f95015738f8bb75c4d27f862"},
+ {file = "rapidfuzz-3.5.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2059cd73b7ea779a9307d7a78ed743f0e3d33b88ccdcd84569abd2953cd859f"},
+ {file = "rapidfuzz-3.5.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:58e3e21f6f13a7cca265cce492bc797425bd4cb2025fdd161a9e86a824ad65ce"},
+ {file = "rapidfuzz-3.5.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b847a49377e64e92e11ef3d0a793de75451526c83af015bdafdd5d04de8a058a"},
+ {file = "rapidfuzz-3.5.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a42c7a8c62b29c4810e39da22b42524295fcb793f41c395c2cb07c126b729e83"},
+ {file = "rapidfuzz-3.5.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51b5166be86e09e011e92d9862b1fe64c4c7b9385f443fb535024e646d890460"},
+ {file = "rapidfuzz-3.5.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f808dcb0088a7a496cc9895e66a7b8de55ffea0eb9b547c75dfb216dd5f76ed"},
+ {file = "rapidfuzz-3.5.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d4b05a8f4ab7e7344459394094587b033fe259eea3a8720035e8ba30e79ab39b"},
+ {file = "rapidfuzz-3.5.2.tar.gz", hash = "sha256:9e9b395743e12c36a3167a3a9fd1b4e11d92fb0aa21ec98017ee6df639ed385e"},
+]
+
+[package.extras]
+full = ["numpy"]
+
+[[package]]
name = "setuptools"
-version = "63.4.1"
+version = "69.0.2"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
-category = "dev"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
+files = [
+ {file = "setuptools-69.0.2-py3-none-any.whl", hash = "sha256:1e8fdff6797d3865f37397be788a4e3cba233608e9b509382a2777d25ebde7f2"},
+ {file = "setuptools-69.0.2.tar.gz", hash = "sha256:735896e78a4742605974de002ac60562d286fa8051a7e2299445e8e8fbb01aa6"},
+]
[package.extras]
-docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
-testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
-testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
+docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
+testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
+testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
[[package]]
name = "six"
version = "1.16.0"
description = "Python 2 and 3 compatibility utilities"
-category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+files = [
+ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
+ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
+]
[[package]]
name = "time-machine"
-version = "2.7.1"
+version = "2.13.0"
description = "Travel through time in your tests."
-category = "main"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
+files = [
+ {file = "time_machine-2.13.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:685d98593f13649ad5e7ce3e58efe689feca1badcf618ba397d3ab877ee59326"},
+ {file = "time_machine-2.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ccbce292380ebf63fb9a52e6b03d91677f6a003e0c11f77473efe3913a75f289"},
+ {file = "time_machine-2.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:679cbf9b15bfde1654cf48124128d3fbe52f821fa158a98fcee5fe7e05db1917"},
+ {file = "time_machine-2.13.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a26bdf3462d5f12a4c1009fdbe54366c6ef22c7b6f6808705b51dedaaeba8296"},
+ {file = "time_machine-2.13.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dabb3b155819811b4602f7e9be936e2024e20dc99a90f103e36b45768badf9c3"},
+ {file = "time_machine-2.13.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0db97f92be3efe0ac62fd3f933c91a78438cef13f283b6dfc2ee11123bfd7d8a"},
+ {file = "time_machine-2.13.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:12eed2e9171c85b703d75c985dab2ecad4fe7025b7d2f842596fce1576238ece"},
+ {file = "time_machine-2.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bdfe4a7f033e6783c3e9a7f8d8fc0b115367330762e00a03ff35fedf663994f3"},
+ {file = "time_machine-2.13.0-cp310-cp310-win32.whl", hash = "sha256:3a7a0a49ce50d9c306c4343a7d6a3baa11092d4399a4af4355c615ccc321a9d3"},
+ {file = "time_machine-2.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:1812e48c6c58707db9988445a219a908a710ea065b2cc808d9a50636291f27d4"},
+ {file = "time_machine-2.13.0-cp310-cp310-win_arm64.whl", hash = "sha256:5aee23cd046abf9caeddc982113e81ba9097a01f3972e9560f5ed64e3495f66d"},
+ {file = "time_machine-2.13.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e9a9d150e098be3daee5c9f10859ab1bd14a61abebaed86e6d71f7f18c05b9d7"},
+ {file = "time_machine-2.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2bd4169b808745d219a69094b3cb86006938d45e7293249694e6b7366225a186"},
+ {file = "time_machine-2.13.0-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:8d526cdcaca06a496877cfe61cc6608df2c3a6fce210e076761964ebac7f77cc"},
+ {file = "time_machine-2.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfef4ebfb4f055ce3ebc7b6c1c4d0dbfcffdca0e783ad8c6986c992915a57ed3"},
+ {file = "time_machine-2.13.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9f128db8997c3339f04f7f3946dd9bb2a83d15e0a40d35529774da1e9e501511"},
+ {file = "time_machine-2.13.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21bef5854d49b62e2c33848b5c3e8acf22a3b46af803ef6ff19529949cb7cf9f"},
+ {file = "time_machine-2.13.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:32b71e50b07f86916ac04bd1eefc2bd2c93706b81393748b08394509ee6585dc"},
+ {file = "time_machine-2.13.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ac8ff145c63cd0dcfd9590fe694b5269aacbc130298dc7209b095d101f8cdde"},
+ {file = "time_machine-2.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:19a3b10161c91ca8e0fd79348665cca711fd2eac6ce336ff9e6b447783817f93"},
+ {file = "time_machine-2.13.0-cp311-cp311-win32.whl", hash = "sha256:5f87787d562e42bf1006a87eb689814105b98c4d5545874a281280d0f8b9a2d9"},
+ {file = "time_machine-2.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:62fd14a80b8b71726e07018628daaee0a2e00937625083f96f69ed6b8e3304c0"},
+ {file = "time_machine-2.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:e9935aff447f5400a2665ab10ed2da972591713080e1befe1bb8954e7c0c7806"},
+ {file = "time_machine-2.13.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:34dcdbbd25c1e124e17fe58050452960fd16a11f9d3476aaa87260e28ecca0fd"},
+ {file = "time_machine-2.13.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e58d82fe0e59d6e096ada3281d647a2e7420f7da5453b433b43880e1c2e8e0c5"},
+ {file = "time_machine-2.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71acbc1febbe87532c7355eca3308c073d6e502ee4ce272b5028967847c8e063"},
+ {file = "time_machine-2.13.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dec0ec2135a4e2a59623e40c31d6e8a8ae73305ade2634380e4263d815855750"},
+ {file = "time_machine-2.13.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e3a2611f8788608ebbcb060a5e36b45911bc3b8adc421b1dc29d2c81786ce4d"},
+ {file = "time_machine-2.13.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:42ef5349135626ad6cd889a0a81400137e5c6928502b0817ea9e90bb10702000"},
+ {file = "time_machine-2.13.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6c16d90a597a8c2d3ce22d6be2eb3e3f14786974c11b01886e51b3cf0d5edaf7"},
+ {file = "time_machine-2.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4f2ae8d0e359b216b695f1e7e7256f208c390db0480601a439c5dd1e1e4e16ce"},
+ {file = "time_machine-2.13.0-cp312-cp312-win32.whl", hash = "sha256:f5fa9610f7e73fff42806a2ed8b06d862aa59ce4d178a52181771d6939c3e237"},
+ {file = "time_machine-2.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:02b33a8c19768c94f7ffd6aa6f9f64818e88afce23250016b28583929d20fb12"},
+ {file = "time_machine-2.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:0cc116056a8a2a917a4eec85661dfadd411e0d8faae604ef6a0e19fe5cd57ef1"},
+ {file = "time_machine-2.13.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:de01f33aa53da37530ad97dcd17e9affa25a8df4ab822506bb08101bab0c2673"},
+ {file = "time_machine-2.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:67fa45cd813821e4f5bec0ac0820869e8e37430b15509d3f5fad74ba34b53852"},
+ {file = "time_machine-2.13.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4a2d3db2c3b8e519d5ef436cd405abd33542a7b7761fb05ef5a5f782a8ce0b1"},
+ {file = "time_machine-2.13.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7558622a62243be866a7e7c41da48eacd82c874b015ecf67d18ebf65ca3f7436"},
+ {file = "time_machine-2.13.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab04cf4e56e1ee65bee2adaa26a04695e92eb1ed1ccc65fbdafd0d114399595a"},
+ {file = "time_machine-2.13.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b0c8f24ae611a58782773af34dd356f1f26756272c04be2be7ea73b47e5da37d"},
+ {file = "time_machine-2.13.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4ca20f85a973a4ca8b00cf466cd72c27ccc72372549b138fd48d7e70e5a190ab"},
+ {file = "time_machine-2.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9fad549521c4c13bdb1e889b2855a86ec835780d534ffd8f091c2647863243be"},
+ {file = "time_machine-2.13.0-cp38-cp38-win32.whl", hash = "sha256:20205422fcf2caf9a7488394587df86e5b54fdb315c1152094fbb63eec4e9304"},
+ {file = "time_machine-2.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:2dc76ee55a7d915a55960a726ceaca7b9097f67e4b4e681ef89871bcf98f00be"},
+ {file = "time_machine-2.13.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7693704c0f2f6b9beed912ff609781edf5fcf5d63aff30c92be4093e09d94b8e"},
+ {file = "time_machine-2.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:918f8389de29b4f41317d121f1150176fae2cdb5fa41f68b2aee0b9dc88df5c3"},
+ {file = "time_machine-2.13.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fe3fda5fa73fec74278912e438fce1612a79c36fd0cc323ea3dc2d5ce629f31"},
+ {file = "time_machine-2.13.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5c6245db573863b335d9ca64b3230f623caf0988594ae554c0c794e7f80e3e66"},
+ {file = "time_machine-2.13.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e433827eccd6700a34a2ab28fd9361ff6e4d4923f718d2d1dac6d1dcd9d54da6"},
+ {file = "time_machine-2.13.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:924377d398b1c48e519ad86a71903f9f36117f69e68242c99fb762a2465f5ad2"},
+ {file = "time_machine-2.13.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:66fb3877014dca0b9286b0f06fa74062357bd23f2d9d102d10e31e0f8fa9b324"},
+ {file = "time_machine-2.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0c9829b2edfcf6b5d72a6ff330d4380f36a937088314c675531b43d3423dd8af"},
+ {file = "time_machine-2.13.0-cp39-cp39-win32.whl", hash = "sha256:1a22be4df364f49a507af4ac9ea38108a0105f39da3f9c60dce62d6c6ea4ccdc"},
+ {file = "time_machine-2.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:88601de1da06c7cab3d5ed3d5c3801ef683366e769e829e96383fdab6ae2fe42"},
+ {file = "time_machine-2.13.0-cp39-cp39-win_arm64.whl", hash = "sha256:3c87856105dcb25b5bbff031d99f06ef4d1c8380d096222e1bc63b496b5258e6"},
+ {file = "time_machine-2.13.0.tar.gz", hash = "sha256:c23b2408e3adcedec84ea1131e238f0124a5bc0e491f60d1137ad7239b37c01a"},
+]
[package.dependencies]
python-dateutil = "*"
[[package]]
-name = "toml"
-version = "0.10.2"
-description = "Python Library for Tom's Obvious, Minimal Language"
-category = "dev"
-optional = false
-python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
-
-[[package]]
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
-category = "dev"
optional = false
python-versions = ">=3.7"
+files = [
+ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
+ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
+]
[[package]]
name = "tox"
-version = "3.25.1"
+version = "4.11.4"
description = "tox is a generic virtualenv management and test command line tool"
-category = "dev"
optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
+python-versions = ">=3.8"
+files = [
+ {file = "tox-4.11.4-py3-none-any.whl", hash = "sha256:2adb83d68f27116812b69aa36676a8d6a52249cb0d173649de0e7d0c2e3e7229"},
+ {file = "tox-4.11.4.tar.gz", hash = "sha256:73a7240778fabf305aeb05ab8ea26e575e042ab5a18d71d0ed13e343a51d6ce1"},
+]
[package.dependencies]
-colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""}
-filelock = ">=3.0.0"
-importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
-packaging = ">=14"
-pluggy = ">=0.12.0"
-py = ">=1.4.17"
-six = ">=1.14.0"
-toml = ">=0.9.4"
-virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7"
+cachetools = ">=5.3.1"
+chardet = ">=5.2"
+colorama = ">=0.4.6"
+filelock = ">=3.12.3"
+packaging = ">=23.1"
+platformdirs = ">=3.10"
+pluggy = ">=1.3"
+pyproject-api = ">=1.6.1"
+tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""}
+virtualenv = ">=20.24.3"
[package.extras]
-docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"]
-testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pathlib2 (>=2.3.3)", "psutil (>=5.6.1)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)"]
+docs = ["furo (>=2023.8.19)", "sphinx (>=7.2.4)", "sphinx-argparse-cli (>=1.11.1)", "sphinx-autodoc-typehints (>=1.24)", "sphinx-copybutton (>=0.5.2)", "sphinx-inline-tabs (>=2023.4.21)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
+testing = ["build[virtualenv] (>=0.10)", "covdefaults (>=2.3)", "detect-test-pollution (>=1.1.1)", "devpi-process (>=1)", "diff-cover (>=7.7)", "distlib (>=0.3.7)", "flaky (>=3.7)", "hatch-vcs (>=0.3)", "hatchling (>=1.18)", "psutil (>=5.9.5)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-xdist (>=3.3.1)", "re-assert (>=1.1)", "time-machine (>=2.12)", "wheel (>=0.41.2)"]
[[package]]
-name = "typed-ast"
-version = "1.5.4"
-description = "a fork of Python 2 and 3 ast modules with type comment support"
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-
-[[package]]
-name = "types-backports"
-version = "0.1.3"
-description = "Typing stubs for backports"
-category = "dev"
+name = "types-python-dateutil"
+version = "2.8.19.14"
+description = "Typing stubs for python-dateutil"
optional = false
python-versions = "*"
+files = [
+ {file = "types-python-dateutil-2.8.19.14.tar.gz", hash = "sha256:1f4f10ac98bb8b16ade9dbee3518d9ace017821d94b057a425b069f834737f4b"},
+ {file = "types_python_dateutil-2.8.19.14-py3-none-any.whl", hash = "sha256:f977b8de27787639986b4e28963263fd0e5158942b3ecef91b9335c130cb1ce9"},
+]
[[package]]
-name = "types-python-dateutil"
-version = "2.8.19"
-description = "Typing stubs for python-dateutil"
-category = "dev"
+name = "types-pytz"
+version = "2023.3.1.1"
+description = "Typing stubs for pytz"
optional = false
python-versions = "*"
+files = [
+ {file = "types-pytz-2023.3.1.1.tar.gz", hash = "sha256:cc23d0192cd49c8f6bba44ee0c81e4586a8f30204970fc0894d209a6b08dab9a"},
+ {file = "types_pytz-2023.3.1.1-py3-none-any.whl", hash = "sha256:1999a123a3dc0e39a2ef6d19f3f8584211de9e6a77fe7a0259f04a524e90a5cf"},
+]
[[package]]
name = "typing-extensions"
-version = "4.3.0"
-description = "Backported and Experimental Type Hints for Python 3.7+"
-category = "dev"
+version = "4.9.0"
+description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
+files = [
+ {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"},
+ {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"},
+]
[[package]]
name = "tzdata"
-version = "2022.1"
+version = "2023.3"
description = "Provider of IANA time zone data"
-category = "main"
optional = false
python-versions = ">=2"
+files = [
+ {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"},
+ {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"},
+]
[[package]]
name = "virtualenv"
-version = "20.16.3"
+version = "20.25.0"
description = "Virtual Python Environment builder"
-category = "dev"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
+files = [
+ {file = "virtualenv-20.25.0-py3-none-any.whl", hash = "sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3"},
+ {file = "virtualenv-20.25.0.tar.gz", hash = "sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b"},
+]
[package.dependencies]
-distlib = ">=0.3.5,<1"
-filelock = ">=3.4.1,<4"
-importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.8\""}
-platformdirs = ">=2.4,<3"
+distlib = ">=0.3.7,<1"
+filelock = ">=3.12.2,<4"
+platformdirs = ">=3.9.1,<5"
[package.extras]
-docs = ["proselint (>=0.13)", "sphinx (>=5.1.1)", "sphinx-argparse (>=0.3.1)", "sphinx-rtd-theme (>=1)", "towncrier (>=21.9)"]
-testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"]
+docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
+test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"]
[[package]]
name = "watchdog"
-version = "2.1.9"
+version = "3.0.0"
description = "Filesystem events monitoring"
-category = "dev"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
+files = [
+ {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:336adfc6f5cc4e037d52db31194f7581ff744b67382eb6021c868322e32eef41"},
+ {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a70a8dcde91be523c35b2bf96196edc5730edb347e374c7de7cd20c43ed95397"},
+ {file = "watchdog-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:adfdeab2da79ea2f76f87eb42a3ab1966a5313e5a69a0213a3cc06ef692b0e96"},
+ {file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b57a1e730af3156d13b7fdddfc23dea6487fceca29fc75c5a868beed29177ae"},
+ {file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ade88d0d778b1b222adebcc0927428f883db07017618a5e684fd03b83342bd9"},
+ {file = "watchdog-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7e447d172af52ad204d19982739aa2346245cc5ba6f579d16dac4bfec226d2e7"},
+ {file = "watchdog-3.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9fac43a7466eb73e64a9940ac9ed6369baa39b3bf221ae23493a9ec4d0022674"},
+ {file = "watchdog-3.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8ae9cda41fa114e28faf86cb137d751a17ffd0316d1c34ccf2235e8a84365c7f"},
+ {file = "watchdog-3.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25f70b4aa53bd743729c7475d7ec41093a580528b100e9a8c5b5efe8899592fc"},
+ {file = "watchdog-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4f94069eb16657d2c6faada4624c39464f65c05606af50bb7902e036e3219be3"},
+ {file = "watchdog-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7c5f84b5194c24dd573fa6472685b2a27cc5a17fe5f7b6fd40345378ca6812e3"},
+ {file = "watchdog-3.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3aa7f6a12e831ddfe78cdd4f8996af9cf334fd6346531b16cec61c3b3c0d8da0"},
+ {file = "watchdog-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:233b5817932685d39a7896b1090353fc8efc1ef99c9c054e46c8002561252fb8"},
+ {file = "watchdog-3.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:13bbbb462ee42ec3c5723e1205be8ced776f05b100e4737518c67c8325cf6100"},
+ {file = "watchdog-3.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8f3ceecd20d71067c7fd4c9e832d4e22584318983cabc013dbf3f70ea95de346"},
+ {file = "watchdog-3.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c9d8c8ec7efb887333cf71e328e39cffbf771d8f8f95d308ea4125bf5f90ba64"},
+ {file = "watchdog-3.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0e06ab8858a76e1219e68c7573dfeba9dd1c0219476c5a44d5333b01d7e1743a"},
+ {file = "watchdog-3.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:d00e6be486affb5781468457b21a6cbe848c33ef43f9ea4a73b4882e5f188a44"},
+ {file = "watchdog-3.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:c07253088265c363d1ddf4b3cdb808d59a0468ecd017770ed716991620b8f77a"},
+ {file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:5113334cf8cf0ac8cd45e1f8309a603291b614191c9add34d33075727a967709"},
+ {file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:51f90f73b4697bac9c9a78394c3acbbd331ccd3655c11be1a15ae6fe289a8c83"},
+ {file = "watchdog-3.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:ba07e92756c97e3aca0912b5cbc4e5ad802f4557212788e72a72a47ff376950d"},
+ {file = "watchdog-3.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:d429c2430c93b7903914e4db9a966c7f2b068dd2ebdd2fa9b9ce094c7d459f33"},
+ {file = "watchdog-3.0.0-py3-none-win32.whl", hash = "sha256:3ed7c71a9dccfe838c2f0b6314ed0d9b22e77d268c67e015450a29036a81f60f"},
+ {file = "watchdog-3.0.0-py3-none-win_amd64.whl", hash = "sha256:4c9956d27be0bb08fc5f30d9d0179a855436e655f046d288e2bcc11adfae893c"},
+ {file = "watchdog-3.0.0-py3-none-win_ia64.whl", hash = "sha256:5d9f3a10e02d7371cd929b5d8f11e87d4bad890212ed3901f9b4d68767bee759"},
+ {file = "watchdog-3.0.0.tar.gz", hash = "sha256:4d98a320595da7a7c5a18fc48cb633c2e73cda78f93cac2ef42d42bf609a33f9"},
+]
[package.extras]
watchmedo = ["PyYAML (>=3.10)"]
[[package]]
name = "zipp"
-version = "3.8.1"
+version = "3.17.0"
description = "Backport of pathlib-compatible object wrapper for zip files"
-category = "main"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
+files = [
+ {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"},
+ {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"},
+]
[package.extras]
-docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"]
-testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
+docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
+testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"]
-[metadata]
-lock-version = "1.1"
-python-versions = "^3.7"
-content-hash = "d6cae20188419d1a859377f888c81aa3a057dd11104671bc2cc9831a30c1a9c1"
+[extras]
+test = ["time-machine"]
-[metadata.files]
-atomicwrites = [
- {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"},
-]
-attrs = [
- {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"},
- {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"},
-]
-babel = [
- {file = "Babel-2.10.3-py3-none-any.whl", hash = "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb"},
- {file = "Babel-2.10.3.tar.gz", hash = "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51"},
-]
-backports-zoneinfo = [
- {file = "backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"},
- {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"},
- {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546"},
- {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win32.whl", hash = "sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08"},
- {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7"},
- {file = "backports.zoneinfo-0.2.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac"},
- {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf"},
- {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570"},
- {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win32.whl", hash = "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b"},
- {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582"},
- {file = "backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987"},
- {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1"},
- {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9"},
- {file = "backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328"},
- {file = "backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6"},
- {file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"},
-]
-black = [
- {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"},
-]
-cfgv = [
- {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
- {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"},
-]
-cleo = [
- {file = "cleo-1.0.0a5-py3-none-any.whl", hash = "sha256:ff53056589300976e960f75afb792dfbfc9c78dcbb5a448e207a17b643826360"},
- {file = "cleo-1.0.0a5.tar.gz", hash = "sha256:097c9d0e0332fd53cc89fc11eb0a6ba0309e6a3933c08f7b38558555486925d3"},
-]
-click = [
- {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
- {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
-]
-colorama = [
- {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
- {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
-]
-coverage = [
- {file = "coverage-6.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a9032f9b7d38bdf882ac9f66ebde3afb8145f0d4c24b2e600bc4c6304aafb87e"},
- {file = "coverage-6.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e0524adb49c716ca763dbc1d27bedce36b14f33e6b8af6dba56886476b42957c"},
- {file = "coverage-6.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4548be38a1c810d79e097a38107b6bf2ff42151900e47d49635be69943763d8"},
- {file = "coverage-6.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f23876b018dfa5d3e98e96f5644b109090f16a4acb22064e0f06933663005d39"},
- {file = "coverage-6.4.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fe75dcfcb889b6800f072f2af5a331342d63d0c1b3d2bf0f7b4f6c353e8c9c0"},
- {file = "coverage-6.4.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2f8553878a24b00d5ab04b7a92a2af50409247ca5c4b7a2bf4eabe94ed20d3ee"},
- {file = "coverage-6.4.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d774d9e97007b018a651eadc1b3970ed20237395527e22cbeb743d8e73e0563d"},
- {file = "coverage-6.4.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d56f105592188ce7a797b2bd94b4a8cb2e36d5d9b0d8a1d2060ff2a71e6b9bbc"},
- {file = "coverage-6.4.2-cp310-cp310-win32.whl", hash = "sha256:d230d333b0be8042ac34808ad722eabba30036232e7a6fb3e317c49f61c93386"},
- {file = "coverage-6.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:5ef42e1db047ca42827a85e34abe973971c635f83aed49611b7f3ab49d0130f0"},
- {file = "coverage-6.4.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:25b7ec944f114f70803d6529394b64f8749e93cbfac0fe6c5ea1b7e6c14e8a46"},
- {file = "coverage-6.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bb00521ab4f99fdce2d5c05a91bddc0280f0afaee0e0a00425e28e209d4af07"},
- {file = "coverage-6.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dff52b3e7f76ada36f82124703f4953186d9029d00d6287f17c68a75e2e6039"},
- {file = "coverage-6.4.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:147605e1702d996279bb3cc3b164f408698850011210d133a2cb96a73a2f7996"},
- {file = "coverage-6.4.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:422fa44070b42fef9fb8dabd5af03861708cdd6deb69463adc2130b7bf81332f"},
- {file = "coverage-6.4.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8af6c26ba8df6338e57bedbf916d76bdae6308e57fc8f14397f03b5da8622b4e"},
- {file = "coverage-6.4.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5336e0352c0b12c7e72727d50ff02557005f79a0b8dcad9219c7c4940a930083"},
- {file = "coverage-6.4.2-cp37-cp37m-win32.whl", hash = "sha256:0f211df2cba951ffcae210ee00e54921ab42e2b64e0bf2c0befc977377fb09b7"},
- {file = "coverage-6.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a13772c19619118903d65a91f1d5fea84be494d12fd406d06c849b00d31bf120"},
- {file = "coverage-6.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f7bd0ffbcd03dc39490a1f40b2669cc414fae0c4e16b77bb26806a4d0b7d1452"},
- {file = "coverage-6.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0895ea6e6f7f9939166cc835df8fa4599e2d9b759b02d1521b574e13b859ac32"},
- {file = "coverage-6.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4e7ced84a11c10160c0697a6cc0b214a5d7ab21dfec1cd46e89fbf77cc66fae"},
- {file = "coverage-6.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80db4a47a199c4563d4a25919ff29c97c87569130375beca3483b41ad5f698e8"},
- {file = "coverage-6.4.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3def6791adf580d66f025223078dc84c64696a26f174131059ce8e91452584e1"},
- {file = "coverage-6.4.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4f89d8e03c8a3757aae65570d14033e8edf192ee9298303db15955cadcff0c63"},
- {file = "coverage-6.4.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6d0b48aff8e9720bdec315d67723f0babd936a7211dc5df453ddf76f89c59933"},
- {file = "coverage-6.4.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2b20286c2b726f94e766e86a3fddb7b7e37af5d0c635bdfa7e4399bc523563de"},
- {file = "coverage-6.4.2-cp38-cp38-win32.whl", hash = "sha256:d714af0bdba67739598849c9f18efdcc5a0412f4993914a0ec5ce0f1e864d783"},
- {file = "coverage-6.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:5f65e5d3ff2d895dab76b1faca4586b970a99b5d4b24e9aafffc0ce94a6022d6"},
- {file = "coverage-6.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a697977157adc052284a7160569b36a8bbec09db3c3220642e6323b47cec090f"},
- {file = "coverage-6.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c77943ef768276b61c96a3eb854eba55633c7a3fddf0a79f82805f232326d33f"},
- {file = "coverage-6.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54d8d0e073a7f238f0666d3c7c0d37469b2aa43311e4024c925ee14f5d5a1cbe"},
- {file = "coverage-6.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f22325010d8824594820d6ce84fa830838f581a7fd86a9235f0d2ed6deb61e29"},
- {file = "coverage-6.4.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24b04d305ea172ccb21bee5bacd559383cba2c6fcdef85b7701cf2de4188aa55"},
- {file = "coverage-6.4.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:866ebf42b4c5dbafd64455b0a1cd5aa7b4837a894809413b930026c91e18090b"},
- {file = "coverage-6.4.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e36750fbbc422c1c46c9d13b937ab437138b998fe74a635ec88989afb57a3978"},
- {file = "coverage-6.4.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:79419370d6a637cb18553ecb25228893966bd7935a9120fa454e7076f13b627c"},
- {file = "coverage-6.4.2-cp39-cp39-win32.whl", hash = "sha256:b5e28db9199dd3833cc8a07fa6cf429a01227b5d429facb56eccd765050c26cd"},
- {file = "coverage-6.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:edfdabe7aa4f97ed2b9dd5dde52d2bb29cb466993bb9d612ddd10d0085a683cf"},
- {file = "coverage-6.4.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:e2618cb2cf5a7cc8d698306e42ebcacd02fb7ef8cfc18485c59394152c70be97"},
- {file = "coverage-6.4.2.tar.gz", hash = "sha256:6c3ccfe89c36f3e5b9837b9ee507472310164f352c9fe332120b764c9d60adbe"},
-]
-crashtest = [
- {file = "crashtest-0.3.1-py3-none-any.whl", hash = "sha256:300f4b0825f57688b47b6d70c6a31de33512eb2fa1ac614f780939aa0cf91680"},
- {file = "crashtest-0.3.1.tar.gz", hash = "sha256:42ca7b6ce88b6c7433e2ce47ea884e91ec93104a4b754998be498a8e6c3d37dd"},
-]
-distlib = [
- {file = "distlib-0.3.5-py2.py3-none-any.whl", hash = "sha256:b710088c59f06338ca514800ad795a132da19fda270e3ce4affc74abf955a26c"},
- {file = "distlib-0.3.5.tar.gz", hash = "sha256:a7f75737c70be3b25e2bee06288cec4e4c221de18455b2dd037fe2a795cab2fe"},
-]
-filelock = [
- {file = "filelock-3.7.1-py3-none-any.whl", hash = "sha256:37def7b658813cda163b56fc564cdc75e86d338246458c4c28ae84cabefa2404"},
- {file = "filelock-3.7.1.tar.gz", hash = "sha256:3a0fd85166ad9dbab54c9aec96737b744106dc5f15c0b09a6744a445299fcf04"},
-]
-ghp-import = [
- {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"},
- {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"},
-]
-identify = [
- {file = "identify-2.5.3-py2.py3-none-any.whl", hash = "sha256:25851c8c1370effb22aaa3c987b30449e9ff0cece408f810ae6ce408fdd20893"},
- {file = "identify-2.5.3.tar.gz", hash = "sha256:887e7b91a1be152b0d46bbf072130235a8117392b9f1828446079a816a05ef44"},
-]
-importlib-metadata = [
- {file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"},
- {file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"},
-]
-importlib-resources = [
- {file = "importlib_resources-5.9.0-py3-none-any.whl", hash = "sha256:f78a8df21a79bcc30cfd400bdc38f314333de7c0fb619763f6b9dabab8268bb7"},
- {file = "importlib_resources-5.9.0.tar.gz", hash = "sha256:5481e97fb45af8dcf2f798952625591c58fe599d0735d86b10f54de086a61681"},
-]
-iniconfig = [
- {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
- {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
-]
-isort = [
- {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
- {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
-]
-jinja2 = [
- {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
- {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
-]
-markdown = [
- {file = "Markdown-3.4.1-py3-none-any.whl", hash = "sha256:08fb8465cffd03d10b9dd34a5c3fea908e20391a2a90b88d66362cb05beed186"},
- {file = "Markdown-3.4.1.tar.gz", hash = "sha256:3b809086bb6efad416156e00a0da66fe47618a5d6918dd688f53f40c8e4cfeff"},
-]
-markdown-include = [
- {file = "markdown-include-0.5.1.tar.gz", hash = "sha256:72a45461b589489a088753893bc95c5fa5909936186485f4ed55caa57d10250f"},
-]
-markupsafe = [
- {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"},
- {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"},
- {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"},
- {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"},
- {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"},
- {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"},
- {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"},
- {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"},
- {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"},
- {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"},
- {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"},
- {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"},
- {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"},
- {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"},
- {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"},
- {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"},
- {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"},
- {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"},
- {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"},
- {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"},
- {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"},
- {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"},
- {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"},
- {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"},
- {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"},
- {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"},
- {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"},
- {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"},
- {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"},
- {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"},
- {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"},
- {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"},
- {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"},
- {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"},
- {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"},
- {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"},
- {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"},
- {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"},
- {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"},
- {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"},
-]
-mergedeep = [
- {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"},
- {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"},
-]
-meson = [
- {file = "meson-0.63.2-py3-none-any.whl", hash = "sha256:64a83ef257b2962b52c8b07ad9ec536c2de1b72fd9f14bcd9c21fe45730edd46"},
- {file = "meson-0.63.2.tar.gz", hash = "sha256:16222f17ef76be0542c91c07994f9676ae879f46fc21c0c786a21ef2cb518bbf"},
-]
-mkdocs = [
- {file = "mkdocs-1.3.0-py3-none-any.whl", hash = "sha256:26bd2b03d739ac57a3e6eed0b7bcc86168703b719c27b99ad6ca91dc439aacde"},
- {file = "mkdocs-1.3.0.tar.gz", hash = "sha256:b504405b04da38795fec9b2e5e28f6aa3a73bb0960cb6d5d27ead28952bd35ea"},
-]
-mypy-extensions = [
- {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
- {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
-]
-ninja = [
- {file = "ninja-1.10.2.3-py2.py3-none-macosx_10_9_universal2.macosx_10_9_x86_64.macosx_11_0_arm64.macosx_11_0_universal2.whl", hash = "sha256:d5e0275d28997a750a4f445c00bdd357b35cc334c13cdff13edf30e544704fbd"},
- {file = "ninja-1.10.2.3-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ea785bf6a15727040835256577239fa3cf5da0d60e618c307aa5efc31a1f0ce"},
- {file = "ninja-1.10.2.3-py2.py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29570a18d697fc84d361e7e6330f0021f34603ae0fcb0ef67ae781e9814aae8d"},
- {file = "ninja-1.10.2.3-py2.py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:21a1d84d4c7df5881bfd86c25cce4cf7af44ba2b8b255c57bc1c434ec30a2dfc"},
- {file = "ninja-1.10.2.3-py2.py3-none-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9ca8dbece144366d5f575ffc657af03eb11c58251268405bc8519d11cf42f113"},
- {file = "ninja-1.10.2.3-py2.py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:279836285975e3519392c93c26e75755e8a8a7fafec9f4ecbb0293119ee0f9c6"},
- {file = "ninja-1.10.2.3-py2.py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:cc8b31b5509a2129e4d12a35fc21238c157038022560aaf22e49ef0a77039086"},
- {file = "ninja-1.10.2.3-py2.py3-none-musllinux_1_1_i686.whl", hash = "sha256:688167841b088b6802e006f911d911ffa925e078c73e8ef2f88286107d3204f8"},
- {file = "ninja-1.10.2.3-py2.py3-none-musllinux_1_1_ppc64le.whl", hash = "sha256:840a0b042d43a8552c4004966e18271ec726e5996578f28345d9ce78e225b67e"},
- {file = "ninja-1.10.2.3-py2.py3-none-musllinux_1_1_s390x.whl", hash = "sha256:84be6f9ec49f635dc40d4b871319a49fa49b8d55f1d9eae7cd50d8e57ddf7a85"},
- {file = "ninja-1.10.2.3-py2.py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:6bd76a025f26b9ae507cf8b2b01bb25bb0031df54ed685d85fc559c411c86cf4"},
- {file = "ninja-1.10.2.3-py2.py3-none-win32.whl", hash = "sha256:740d61fefb4ca13573704ee8fe89b973d40b8dc2a51aaa4e9e68367233743bb6"},
- {file = "ninja-1.10.2.3-py2.py3-none-win_amd64.whl", hash = "sha256:0560eea57199e41e86ac2c1af0108b63ae77c3ca4d05a9425a750e908135935a"},
- {file = "ninja-1.10.2.3.tar.gz", hash = "sha256:e1b86ad50d4e681a7dbdff05fc23bb52cb773edb90bc428efba33fa027738408"},
-]
-nodeenv = [
- {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"},
- {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"},
-]
-packaging = [
- {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
- {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
-]
-pathspec = [
- {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
- {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
-]
-platformdirs = [
- {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
- {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
-]
-pluggy = [
- {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
- {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
-]
-pre-commit = [
- {file = "pre_commit-2.20.0-py2.py3-none-any.whl", hash = "sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7"},
- {file = "pre_commit-2.20.0.tar.gz", hash = "sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959"},
-]
-py = [
- {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
- {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
-]
-pygments = [
- {file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"},
- {file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"},
-]
-pylev = [
- {file = "pylev-1.4.0-py2.py3-none-any.whl", hash = "sha256:7b2e2aa7b00e05bb3f7650eb506fc89f474f70493271a35c242d9a92188ad3dd"},
- {file = "pylev-1.4.0.tar.gz", hash = "sha256:9e77e941042ad3a4cc305dcdf2b2dec1aec2fbe3dd9015d2698ad02b173006d1"},
-]
-pymdown-extensions = [
- {file = "pymdown-extensions-6.3.tar.gz", hash = "sha256:cb879686a586b22292899771f5e5bc3382808e92aa938f71b550ecdea709419f"},
- {file = "pymdown_extensions-6.3-py2.py3-none-any.whl", hash = "sha256:66fae2683c7a1dac53184f7de57f51f8dad73f9ead2f453e94e85096cb811335"},
-]
-pyparsing = [
- {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
- {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
-]
-pytest = [
- {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"},
- {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"},
-]
-pytest-cov = [
- {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"},
- {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"},
-]
-python-dateutil = [
- {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
- {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
-]
-pytz = [
- {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"},
- {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"},
-]
-pyyaml = [
- {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
- {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
- {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"},
- {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"},
- {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"},
- {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"},
- {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"},
- {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"},
- {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"},
- {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"},
- {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"},
- {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"},
- {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"},
- {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"},
- {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"},
- {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"},
- {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"},
- {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"},
- {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"},
- {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"},
- {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"},
- {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"},
- {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"},
- {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"},
- {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"},
- {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"},
- {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"},
- {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"},
- {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"},
- {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"},
- {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"},
- {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"},
- {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"},
- {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"},
- {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"},
- {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"},
- {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"},
- {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"},
- {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
- {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
-]
-pyyaml-env-tag = [
- {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"},
- {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"},
-]
-setuptools = [
- {file = "setuptools-63.4.1-py3-none-any.whl", hash = "sha256:dc2662692f47d99cb8ae15a784529adeed535bcd7c277fee0beccf961522baf6"},
- {file = "setuptools-63.4.1.tar.gz", hash = "sha256:7c7854ee1429a240090297628dc9f75b35318d193537968e2dc14010ee2f5bca"},
-]
-six = [
- {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
- {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
-]
-time-machine = [
- {file = "time-machine-2.7.1.tar.gz", hash = "sha256:be6c1f0421a77a046db8fae00886fb364f683a86612b71dd5c74b22891590042"},
- {file = "time_machine-2.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ae93d2f761435d192bc80c148438a0c4261979db0610cef08dfe2c8d21ca1c67"},
- {file = "time_machine-2.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:342b431154fbfb1889f8d7aa3d857373a837106bba395a5cc99123f11a7cea03"},
- {file = "time_machine-2.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4011ea76f6ad2f932f00cf9e77a25b575a024d6bc15bcf891a3f9916ceeb6e"},
- {file = "time_machine-2.7.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43ae8192d370a90d2246fca565a55633f592b314264c65c5c9151c361b715fb9"},
- {file = "time_machine-2.7.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cea12d0592ebbe738db952ce6fd272ed90e7bbb095e802f4f2145f8f0e322fa3"},
- {file = "time_machine-2.7.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:732d5fd2d442fa87538b5a6ca623cb205b9b048d2c9aaf79e5cfc7ec7f637848"},
- {file = "time_machine-2.7.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c34e1f49cad2fd41d42c4aabd3d69a32c79d9a8e0779064554843823cd1fb1e4"},
- {file = "time_machine-2.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6af3e81cf663b6d5660953ae59da2bb2ae802452ecbc9907272979ed06253659"},
- {file = "time_machine-2.7.1-cp310-cp310-win32.whl", hash = "sha256:10c2937d3556f4358205dac5c7cd2d33832b8b911f3deff050f59e1fe2be3231"},
- {file = "time_machine-2.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:200974e9bb8a1cb227ce579caafeaeebb0f9de81758c444cbccc0ea464313caf"},
- {file = "time_machine-2.7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d5e2376b7922c9d96921709c7e730498b9c69da889f359a465d0c43117b62da3"},
- {file = "time_machine-2.7.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ad9117abe223cdc7b4a4432e0a0cfebb1b351a091ee996c653e90f27a734fce"},
- {file = "time_machine-2.7.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:626ef686723147468e84da3edcd67ff757a463250fd35f8f6a8e5b899c43b43d"},
- {file = "time_machine-2.7.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9331946ed13acd50bc484f408e26b8eefa67e3dbca41927d2052f2148d3661d"},
- {file = "time_machine-2.7.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3d0612e0323047f29c23732963d9926f1a95e2ce334d86fecd37c803ac240fc6"},
- {file = "time_machine-2.7.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b474499ad0083252240bc5be13f8116cc2ca8a89d1ca4967ed74a7b5f0883f95"},
- {file = "time_machine-2.7.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7df0857709432585b62d2667c0e6e64029b652e2df776b9fb85223c60dce52c7"},
- {file = "time_machine-2.7.1-cp37-cp37m-win32.whl", hash = "sha256:77c8dfe8dc7f45bbfe73494c72f3728d99abec5a020460ad7ffee5247365eba4"},
- {file = "time_machine-2.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:c1fd1c231377ce076f99c8c16999a95510690f8dbd35db0e5fbbc74a17f84b39"},
- {file = "time_machine-2.7.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:462924fb87826882fc7830098e621116599f9259d181a7bbf5a4e49f74ec325b"},
- {file = "time_machine-2.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:46bf3b4a52d43289b23f0015a9d8592ddf621a5058e566c275cb060347d430c1"},
- {file = "time_machine-2.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30afd5b978d8121334c80fa23119d7bd7c9f954169854edf5103e5c8b38358bb"},
- {file = "time_machine-2.7.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:633fb8c47f3cd64690591ca6981e4fdbcaa54c18d8a57a3cdc24638ca98f8216"},
- {file = "time_machine-2.7.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80b6093c3b70d1d1a66b65f18a6e53b233c8dd5d8ffe7ac59e9d048fb1d5e15c"},
- {file = "time_machine-2.7.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e62ed7d78694b7e0a2ab30b3dd52ebf26b03e17d6eda0f231fd77e24307a55a9"},
- {file = "time_machine-2.7.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0eaf024d16482ec211a579fd389cbbd4fedd8a1f0a0c41642508815f880ca3a9"},
- {file = "time_machine-2.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2688091ce0c16151faa80625efb34e3096731fbdee6d5284c48c984bce95c311"},
- {file = "time_machine-2.7.1-cp38-cp38-win32.whl", hash = "sha256:2e54bf0521b6e397fcaa03060feb187bbe5aa63ac51dbb97d5bc59fb0c4725f8"},
- {file = "time_machine-2.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:cee72d9e14d36e4b8da6af1d2d784f14da53f76aeb5066540a38318aa907b551"},
- {file = "time_machine-2.7.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:06322d41d45d86e2dc2520794c95129ff25b8620b33851ed40700c859ebf8c30"},
- {file = "time_machine-2.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:193b14daa3b3cf67e6b55d6e2d63c2eb7c1d3f49017704d4b43963b198656888"},
- {file = "time_machine-2.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1367a89fb857f68cfa723e236cd47febaf201a3a625ad8423110fe0509d5fca8"},
- {file = "time_machine-2.7.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce350f7e8bd51a0bb064180486300283bec5cd1a21a318a8ffe5f7df11735f36"},
- {file = "time_machine-2.7.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68ff623d835760314e279aedc0d19a1dc4dec117c6bca388e1ff077c781256bd"},
- {file = "time_machine-2.7.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:05fecd818d41727d31109a0d039ce07c8311602b45ffc07bffd8ae8b6f266ee5"},
- {file = "time_machine-2.7.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1fe4e604c5effc290c1bbecd3ea98687690d0a88fd98ba93e0246bf19ae2a520"},
- {file = "time_machine-2.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff07a5635d42957f2bd7eb5ca6579f64de368c842e754a4d3414520693b75db9"},
- {file = "time_machine-2.7.1-cp39-cp39-win32.whl", hash = "sha256:8c6314e7e0ffd7af82c8026786d5551aff973e0c86ec1368b0590be9a7620cad"},
- {file = "time_machine-2.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:d50a2620d726788cbde97c58e0f6f61d10337d16d088a1fad789f50a1b5ff4d1"},
-]
-toml = [
- {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
- {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
-]
-tomli = [
- {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
- {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
-]
-tox = [
- {file = "tox-3.25.1-py2.py3-none-any.whl", hash = "sha256:c38e15f4733683a9cc0129fba078633e07eb0961f550a010ada879e95fb32632"},
- {file = "tox-3.25.1.tar.gz", hash = "sha256:c138327815f53bc6da4fe56baec5f25f00622ae69ef3fe4e1e385720e22486f9"},
-]
-typed-ast = [
- {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"},
- {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"},
- {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"},
- {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"},
- {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"},
- {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"},
- {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"},
- {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"},
- {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"},
- {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"},
- {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"},
- {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"},
- {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"},
- {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"},
- {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"},
- {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"},
- {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"},
- {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"},
- {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"},
- {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"},
- {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"},
- {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"},
- {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"},
- {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"},
-]
-types-backports = [
- {file = "types-backports-0.1.3.tar.gz", hash = "sha256:f4b7206c073df88d6200891e3d27506185fd60cda66fb289737b2fa92c0010cf"},
- {file = "types_backports-0.1.3-py2.py3-none-any.whl", hash = "sha256:dafcd61848081503e738a7768872d1dd6c018401b4d2a1cfb608ea87ec9864b9"},
-]
-types-python-dateutil = [
- {file = "types-python-dateutil-2.8.19.tar.gz", hash = "sha256:bfd3eb39c7253aea4ba23b10f69b017d30b013662bb4be4ab48b20bbd763f309"},
- {file = "types_python_dateutil-2.8.19-py3-none-any.whl", hash = "sha256:6284df1e4783d8fc6e587f0317a81333856b872a6669a282f8a325342bce7fa8"},
-]
-typing-extensions = [
- {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"},
- {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"},
-]
-tzdata = [
- {file = "tzdata-2022.1-py2.py3-none-any.whl", hash = "sha256:238e70234214138ed7b4e8a0fab0e5e13872edab3be586ab8198c407620e2ab9"},
- {file = "tzdata-2022.1.tar.gz", hash = "sha256:8b536a8ec63dc0751342b3984193a3118f8fca2afe25752bb9b7fffd398552d3"},
-]
-virtualenv = [
- {file = "virtualenv-20.16.3-py2.py3-none-any.whl", hash = "sha256:4193b7bc8a6cd23e4eb251ac64f29b4398ab2c233531e66e40b19a6b7b0d30c1"},
- {file = "virtualenv-20.16.3.tar.gz", hash = "sha256:d86ea0bb50e06252d79e6c241507cb904fcd66090c3271381372d6221a3970f9"},
-]
-watchdog = [
- {file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a735a990a1095f75ca4f36ea2ef2752c99e6ee997c46b0de507ba40a09bf7330"},
- {file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b17d302850c8d412784d9246cfe8d7e3af6bcd45f958abb2d08a6f8bedf695d"},
- {file = "watchdog-2.1.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ee3e38a6cc050a8830089f79cbec8a3878ec2fe5160cdb2dc8ccb6def8552658"},
- {file = "watchdog-2.1.9-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64a27aed691408a6abd83394b38503e8176f69031ca25d64131d8d640a307591"},
- {file = "watchdog-2.1.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:195fc70c6e41237362ba720e9aaf394f8178bfc7fa68207f112d108edef1af33"},
- {file = "watchdog-2.1.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bfc4d351e6348d6ec51df007432e6fe80adb53fd41183716017026af03427846"},
- {file = "watchdog-2.1.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8250546a98388cbc00c3ee3cc5cf96799b5a595270dfcfa855491a64b86ef8c3"},
- {file = "watchdog-2.1.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:117ffc6ec261639a0209a3252546b12800670d4bf5f84fbd355957a0595fe654"},
- {file = "watchdog-2.1.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:97f9752208f5154e9e7b76acc8c4f5a58801b338de2af14e7e181ee3b28a5d39"},
- {file = "watchdog-2.1.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:247dcf1df956daa24828bfea5a138d0e7a7c98b1a47cf1fa5b0c3c16241fcbb7"},
- {file = "watchdog-2.1.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:226b3c6c468ce72051a4c15a4cc2ef317c32590d82ba0b330403cafd98a62cfd"},
- {file = "watchdog-2.1.9-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d9820fe47c20c13e3c9dd544d3706a2a26c02b2b43c993b62fcd8011bcc0adb3"},
- {file = "watchdog-2.1.9-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:70af927aa1613ded6a68089a9262a009fbdf819f46d09c1a908d4b36e1ba2b2d"},
- {file = "watchdog-2.1.9-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ed80a1628cee19f5cfc6bb74e173f1b4189eb532e705e2a13e3250312a62e0c9"},
- {file = "watchdog-2.1.9-py3-none-manylinux2014_aarch64.whl", hash = "sha256:9f05a5f7c12452f6a27203f76779ae3f46fa30f1dd833037ea8cbc2887c60213"},
- {file = "watchdog-2.1.9-py3-none-manylinux2014_armv7l.whl", hash = "sha256:255bb5758f7e89b1a13c05a5bceccec2219f8995a3a4c4d6968fe1de6a3b2892"},
- {file = "watchdog-2.1.9-py3-none-manylinux2014_i686.whl", hash = "sha256:d3dda00aca282b26194bdd0adec21e4c21e916956d972369359ba63ade616153"},
- {file = "watchdog-2.1.9-py3-none-manylinux2014_ppc64.whl", hash = "sha256:186f6c55abc5e03872ae14c2f294a153ec7292f807af99f57611acc8caa75306"},
- {file = "watchdog-2.1.9-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:083171652584e1b8829581f965b9b7723ca5f9a2cd7e20271edf264cfd7c1412"},
- {file = "watchdog-2.1.9-py3-none-manylinux2014_s390x.whl", hash = "sha256:b530ae007a5f5d50b7fbba96634c7ee21abec70dc3e7f0233339c81943848dc1"},
- {file = "watchdog-2.1.9-py3-none-manylinux2014_x86_64.whl", hash = "sha256:4f4e1c4aa54fb86316a62a87b3378c025e228178d55481d30d857c6c438897d6"},
- {file = "watchdog-2.1.9-py3-none-win32.whl", hash = "sha256:5952135968519e2447a01875a6f5fc8c03190b24d14ee52b0f4b1682259520b1"},
- {file = "watchdog-2.1.9-py3-none-win_amd64.whl", hash = "sha256:7a833211f49143c3d336729b0020ffd1274078e94b0ae42e22f596999f50279c"},
- {file = "watchdog-2.1.9-py3-none-win_ia64.whl", hash = "sha256:ad576a565260d8f99d97f2e64b0f97a48228317095908568a9d5c786c829d428"},
- {file = "watchdog-2.1.9.tar.gz", hash = "sha256:43ce20ebb36a51f21fa376f76d1d4692452b2527ccd601950d69ed36b9e21609"},
-]
-zipp = [
- {file = "zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"},
- {file = "zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"},
-]
+[metadata]
+lock-version = "2.0"
+python-versions = ">=3.8"
+content-hash = "8df704933b8ad4ffb92d760c1215dfe15be3227a5d65d93f3a5cac8a2035565d"
diff --git a/pyproject.toml b/pyproject.toml
index a61cae0..497dcfb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,40 @@
+[project]
+name = "pendulum"
+version = "3.0.0"
+description = "Python datetimes made easy"
+readme = "README.rst"
+requires-python = ">=3.8"
+license = { text = "MIT License" }
+authors = [{ name = "Sébastien Eustace", email = "sebastien@eustace.io>" }]
+keywords = ['datetime', 'date', 'time']
+
+classifiers = [
+ "License :: OSI Approved :: MIT License",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+]
+
+dependencies = [
+ "python-dateutil>=2.6",
+ "tzdata>=2020.1",
+ 'backports.zoneinfo>=0.2.1; python_version < "3.9"',
+ 'time-machine>=2.6.0; implementation_name != "pypy"',
+ 'importlib-resources>=5.9.0; python_version < "3.9"'
+]
+
+[project.urls]
+Homepage = "https://pendulum.eustace.io"
+Documentation = "https://pendulum.eustace.io/docs"
+Repository = "https://github.com/sdispater/pendulum"
+
+
[tool.poetry]
name = "pendulum"
-version = "3.0.0a1"
+version = "3.0.0b1"
description = "Python datetimes made easy"
authors = ["Sébastien Eustace <sebastien@eustace.io>"]
license = "MIT"
@@ -10,90 +44,119 @@ repository = "https://github.com/sdispater/pendulum"
documentation = "https://pendulum.eustace.io/docs"
keywords = ['datetime', 'date', 'time']
-packages = [
- { include = "pendulum" },
- { include = "tests", format = "sdist" },
-]
-include = [
- { path = "meson.build", format = "sdist" },
- { path = "pendulum/py.typed" },
- # C extensions must be included in the wheel distributions
- { path = "pendulum/_extensions/*.so", format = "wheel" },
- { path = "pendulum/_extensions/*.pyd", format = "wheel" },
- { path = "pendulum/parsing/*.so", format = "wheel" },
- { path = "pendulum/parsing/*.pyd", format = "wheel" },
-]
-
[tool.poetry.dependencies]
-python = "^3.7"
-python-dateutil = "^2.6"
-"backports.zoneinfo" = { version = "^0.2.1", python = ">=3.7,<3.9" }
-time-machine = { version = "^2.6.0", markers = "implementation_name != 'pypy'" }
+python = ">=3.8"
+python-dateutil = ">=2.6"
+"backports.zoneinfo" = { version = ">=0.2.1", python = "<3.9" }
+time-machine = { version = ">=2.6.0", markers = "implementation_name != 'pypy'", optional = true }
tzdata = ">=2020.1"
-importlib-resources = { version = "^5.9.0", python = ">=3.7,<3.9" }
+importlib-resources = { version = ">=5.9.0", python = "<3.9" }
[tool.poetry.group.test.dependencies]
pytest = "^7.1.2"
-pytest-cov = "^3.0.0"
pytz = ">=2022.1"
-time-machine = "^2.7.1"
+time-machine = ">=2.6.0"
+pytest-benchmark = "^4.0.0"
[tool.poetry.group.doc.dependencies]
mkdocs = "^1.0"
-pymdown-extensions = "^6.0"
+pymdown-extensions = ">=6,<11"
pygments = "^2.2"
-markdown-include = "^0.5.1"
+markdown-include = "^0.8.1"
[tool.poetry.group.lint.dependencies]
-black = { version = "^22.6.0", markers = "implementation_name != 'pypy'" }
-isort = "^5.10.1"
-pre-commit = "^2.20.0"
-types-backports = "^0.1.3"
+pre-commit = "^3.0.0"
+
+[tool.poetry.group.typing.dependencies]
+mypy = "^1.3.0"
types-python-dateutil = "^2.8.19"
+types-pytz = ">=2022.7.1.2"
[tool.poetry.group.dev.dependencies]
babel = "^2.10.3"
-cleo = "^1.0.0a5"
-tox = "^3.25.1"
+cleo = { version = "^2.0.1", python = ">=3.8,<4.0" }
+tox = "^4.0.0"
-[tool.poetry.group.build]
-optional = true
+[tool.poetry.group.benchmark.dependencies]
+pytest-codspeed = "^1.2.2"
[tool.poetry.group.build.dependencies]
-meson = "^0.63.2"
-ninja = "^1.10.2.3"
-
-[tool.poetry.build]
-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",
+maturin = ">=1.0,<2.0"
+
+[tool.poetry.extras]
+test = ["time-machine"]
+
+[tool.maturin]
+module-name = "pendulum._pendulum"
+
+
+[tool.ruff]
+fix = true
+unfixable = [
+ "ERA", # do not autoremove commented out code
+]
+target-version = "py38"
+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
]
-filter_files = true
-known_first_party = "pendulum"
-known_third_party = [
+ignore = [
+ "B904", # use 'raise ... from err'
+ "B905", # use explicit 'strict=' parameter with 'zip()'
+ "N818", # Exception name should be named with an Error suffix
+ "RUF001",
+]
+extend-exclude = [
+ # External to the project's coding standards:
+ "docs/*",
+ # Machine-generated, too many false-positives
+ "src/pendulum/locales/*",
+ # ruff disagrees with black when it comes to formatting
+ "*.pyi",
+]
+
+[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"]
+"clock" = ["RUF012"]
[tool.mypy]
strict = true
-files = "pendulum, tests"
+files = "src, tests"
show_error_codes = true
pretty = true
+warn_unused_ignores = true
+exclude = [
+ "^build\\.py$"
+]
# The following whitelist is used to allow for incremental adoption
# of Mypy. Modules should be removed from this whitelist as and when
@@ -103,9 +166,6 @@ pretty = true
[[tool.mypy.overrides]]
module = [
"pendulum.mixins.default",
- "tests.conftest",
- "tests.test_helpers",
- "tests.test_main",
"tests.test_parsing",
"tests.date.test_add",
"tests.date.test_behavior",
@@ -179,5 +239,5 @@ omit = [
]
[build-system]
-requires = ["poetry-core>=1.1.0a6", "meson", "ninja"]
-build-backend = "poetry.core.masonry.api"
+requires = ["maturin>=1.0,<2.0"]
+build-backend = "maturin"
diff --git a/rust/.cargo/config.toml b/rust/.cargo/config.toml
new file mode 100644
index 0000000..f0ba8af
--- /dev/null
+++ b/rust/.cargo/config.toml
@@ -0,0 +1,15 @@
+[build]
+rustflags = []
+
+# see https://pyo3.rs/main/building_and_distribution.html#macos
+[target.x86_64-apple-darwin]
+rustflags = [
+ "-C", "link-arg=-undefined",
+ "-C", "link-arg=dynamic_lookup",
+]
+
+[target.aarch64-apple-darwin]
+rustflags = [
+ "-C", "link-arg=-undefined",
+ "-C", "link-arg=dynamic_lookup",
+]
diff --git a/rust/Cargo.lock b/rust/Cargo.lock
new file mode 100644
index 0000000..bcfe643
--- /dev/null
+++ b/rust/Cargo.lock
@@ -0,0 +1,318 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "_pendulum"
+version = "3.0.0-beta-1"
+dependencies = [
+ "mimalloc",
+ "pyo3",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "cc"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "indoc"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306"
+
+[[package]]
+name = "libc"
+version = "0.2.139"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
+
+[[package]]
+name = "libmimalloc-sys"
+version = "0.1.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3979b5c37ece694f1f5e51e7ecc871fdb0f517ed04ee45f88d15d6d553cb9664"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "lock_api"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "mimalloc"
+version = "0.1.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa01922b5ea280a911e323e4d2fd24b7fe5cc4042e0d2cda3c40775cdc4bdc9c"
+dependencies = [
+ "libmimalloc-sys",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-sys",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "pyo3"
+version = "0.19.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffb88ae05f306b4bfcde40ac4a51dc0b05936a9207a4b75b798c7729c4258a59"
+dependencies = [
+ "cfg-if",
+ "indoc",
+ "libc",
+ "memoffset",
+ "parking_lot",
+ "pyo3-build-config",
+ "pyo3-ffi",
+ "pyo3-macros",
+ "unindent",
+]
+
+[[package]]
+name = "pyo3-build-config"
+version = "0.19.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "554db24f0b3c180a9c0b1268f91287ab3f17c162e15b54caaae5a6b3773396b0"
+dependencies = [
+ "once_cell",
+ "python3-dll-a",
+ "target-lexicon",
+]
+
+[[package]]
+name = "pyo3-ffi"
+version = "0.19.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "922ede8759e8600ad4da3195ae41259654b9c55da4f7eec84a0ccc7d067a70a4"
+dependencies = [
+ "libc",
+ "pyo3-build-config",
+]
+
+[[package]]
+name = "pyo3-macros"
+version = "0.19.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a5caec6a1dd355964a841fcbeeb1b89fe4146c87295573f94228911af3cc5a2"
+dependencies = [
+ "proc-macro2",
+ "pyo3-macros-backend",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pyo3-macros-backend"
+version = "0.19.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0b78ccbb160db1556cdb6fd96c50334c5d4ec44dc5e0a968d0a1208fa0efa8b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "python3-dll-a"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5f07cd4412be8fa09a721d40007c483981bbe072cd6a21f2e83e04ec8f8343f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "smallvec"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "target-lexicon"
+version = "0.12.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ae9980cab1db3fceee2f6c6f643d5d8de2997c58ee8d25fb0cc8a9e9e7348e5"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
+
+[[package]]
+name = "unindent"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c"
+
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
new file mode 100644
index 0000000..0d76a89
--- /dev/null
+++ b/rust/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "_pendulum"
+version = "3.0.0"
+edition = "2021"
+
+[lib]
+name = "_pendulum"
+crate-type = ["cdylib", "rlib"]
+
+[profile.release]
+lto = "fat"
+codegen-units = 1
+strip = true
+overflow-checks = false
+
+[dependencies]
+pyo3 = { version = "0.19.0", features = ["extension-module", "generate-import-lib"] }
+mimalloc = { version = "0.1.39", optional = true, default-features = false }
+
+[features]
+extension-module = ["pyo3/extension-module"]
+default = ["mimalloc"]
diff --git a/rust/src/constants.rs b/rust/src/constants.rs
new file mode 100644
index 0000000..3fea9c0
--- /dev/null
+++ b/rust/src/constants.rs
@@ -0,0 +1,56 @@
+pub const EPOCH_YEAR: u32 = 1970;
+
+pub const DAYS_PER_N_YEAR: u32 = 365;
+pub const DAYS_PER_L_YEAR: u32 = 366;
+
+pub const SECS_PER_MIN: u32 = 60;
+pub const SECS_PER_HOUR: u32 = SECS_PER_MIN * 60;
+pub const SECS_PER_DAY: u32 = SECS_PER_HOUR * 24;
+
+// 400-year chunks always have 146097 days (20871 weeks).
+pub const DAYS_PER_400_YEARS: u32 = 146_097;
+pub const SECS_PER_400_YEARS: u64 = DAYS_PER_400_YEARS as u64 * SECS_PER_DAY as u64;
+
+// The number of seconds in an aligned 100-year chunk, for those that
+// do not begin with a leap year and those that do respectively.
+pub const SECS_PER_100_YEARS: [u64; 2] = [
+ (76 * DAYS_PER_N_YEAR as u64 + 24 * DAYS_PER_L_YEAR as u64) * SECS_PER_DAY as u64,
+ (75 * DAYS_PER_N_YEAR as u64 + 25 * DAYS_PER_L_YEAR as u64) * SECS_PER_DAY as u64,
+];
+
+// The number of seconds in an aligned 4-year chunk, for those that
+// do not begin with a leap year and those that do respectively.
+#[allow(clippy::erasing_op)]
+pub const SECS_PER_4_YEARS: [u32; 2] = [
+ (4 * DAYS_PER_N_YEAR + 0 * DAYS_PER_L_YEAR) * SECS_PER_DAY,
+ (3 * DAYS_PER_N_YEAR + DAYS_PER_L_YEAR) * SECS_PER_DAY,
+];
+
+// The number of seconds in non-leap and leap years respectively.
+pub const SECS_PER_YEAR: [u32; 2] = [
+ DAYS_PER_N_YEAR * SECS_PER_DAY,
+ DAYS_PER_L_YEAR * SECS_PER_DAY,
+];
+
+// The month lengths in non-leap and leap years respectively.
+pub const DAYS_PER_MONTHS: [[i32; 13]; 2] = [
+ [-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
+ [-1, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
+];
+
+// The day offsets of the beginning of each (1-based) month in non-leap
+// and leap years respectively.
+// For example, in a leap year there are 335 days before December.
+pub const MONTHS_OFFSETS: [[i32; 14]; 2] = [
+ [
+ -1, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365,
+ ],
+ [
+ -1, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366,
+ ],
+];
+
+pub const DAY_OF_WEEK_TABLE: [u32; 12] = [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4];
+
+pub const TM_JANUARY: usize = 0;
+pub const TM_DECEMBER: usize = 11;
diff --git a/rust/src/helpers.rs b/rust/src/helpers.rs
new file mode 100644
index 0000000..364075a
--- /dev/null
+++ b/rust/src/helpers.rs
@@ -0,0 +1,122 @@
+use crate::constants::{
+ DAYS_PER_L_YEAR, DAYS_PER_N_YEAR, DAY_OF_WEEK_TABLE, EPOCH_YEAR, MONTHS_OFFSETS,
+ SECS_PER_100_YEARS, SECS_PER_400_YEARS, SECS_PER_4_YEARS, SECS_PER_DAY, SECS_PER_HOUR,
+ SECS_PER_MIN, SECS_PER_YEAR, TM_DECEMBER, TM_JANUARY,
+};
+
+fn p(year: i32) -> i32 {
+ year + year / 4 - year / 100 + year / 400
+}
+
+pub fn is_leap(year: i32) -> bool {
+ year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
+}
+
+pub fn is_long_year(year: i32) -> bool {
+ (p(year) % 7 == 4) || (p(year - 1) % 7 == 3)
+}
+
+pub fn days_in_year(year: i32) -> u32 {
+ if is_leap(year) {
+ return DAYS_PER_L_YEAR;
+ }
+
+ DAYS_PER_N_YEAR
+}
+
+pub fn week_day(year: i32, month: u32, day: u32) -> u32 {
+ let y: i32 = year - i32::from(month < 3);
+
+ let w: i32 = (p(y) + DAY_OF_WEEK_TABLE[(month - 1) as usize] as i32 + day as i32) % 7;
+
+ if w == 0 {
+ return 7;
+ }
+
+ w.unsigned_abs()
+}
+
+pub fn day_number(year: i32, month: u8, day: u8) -> i32 {
+ let m = i32::from((month + 9) % 12);
+ let y = year - m / 10;
+
+ 365 * y + y / 4 - y / 100 + y / 400 + (m * 306 + 5) / 10 + (i32::from(day) - 1)
+}
+
+pub fn local_time(
+ unix_time: f64,
+ utc_offset: isize,
+ microsecond: usize,
+) -> (usize, usize, usize, usize, usize, usize, usize) {
+ let mut year: usize = EPOCH_YEAR as usize;
+ let mut seconds: isize = unix_time.floor() as isize;
+
+ // Shift to a base year that is 400-year aligned.
+ if seconds >= 0 {
+ seconds -= (10957 * SECS_PER_DAY as usize) as isize;
+ year += 30; // == 2000
+ } else {
+ seconds += ((146_097 - 10957) * SECS_PER_DAY as usize) as isize;
+ year -= 370; // == 1600
+ }
+
+ seconds += utc_offset;
+
+ // Handle years in chunks of 400/100/4/1
+ year += 400 * (seconds / SECS_PER_400_YEARS as isize) as usize;
+ seconds %= SECS_PER_400_YEARS as isize;
+ if seconds < 0 {
+ seconds += SECS_PER_400_YEARS as isize;
+ year -= 400;
+ }
+
+ let mut leap_year = 1; // 4-century aligned
+ let mut sec_per_100years = SECS_PER_100_YEARS[leap_year] as isize;
+
+ while seconds >= sec_per_100years {
+ seconds -= sec_per_100years;
+ year += 100;
+ leap_year = 0; // 1-century, non 4-century aligned
+ sec_per_100years = SECS_PER_100_YEARS[leap_year] as isize;
+ }
+
+ let mut sec_per_4years = SECS_PER_4_YEARS[leap_year] as isize;
+ while seconds >= sec_per_4years {
+ seconds -= sec_per_4years;
+ year += 4;
+ leap_year = 1; // 4-year, non century aligned
+ sec_per_4years = SECS_PER_4_YEARS[leap_year] as isize;
+ }
+
+ let mut sec_per_year = SECS_PER_YEAR[leap_year] as isize;
+ while seconds >= sec_per_year {
+ seconds -= sec_per_year;
+ year += 1;
+ leap_year = 0; // non 4-year aligned
+ sec_per_year = SECS_PER_YEAR[leap_year] as isize;
+ }
+
+ // Handle months and days
+ let mut month = TM_DECEMBER + 1;
+ let mut day: usize = (seconds / (SECS_PER_DAY as isize) + 1) as usize;
+ seconds %= SECS_PER_DAY as isize;
+
+ let mut month_offset: usize;
+ while month != (TM_JANUARY + 1) {
+ month_offset = MONTHS_OFFSETS[leap_year][month] as usize;
+ if day > month_offset {
+ day -= month_offset;
+ break;
+ }
+
+ month -= 1;
+ }
+
+ // Handle hours, minutes and seconds
+ let hour: usize = (seconds / SECS_PER_HOUR as isize) as usize;
+ seconds %= SECS_PER_HOUR as isize;
+ let minute: usize = (seconds / SECS_PER_MIN as isize) as usize;
+ let second: usize = (seconds % SECS_PER_MIN as isize) as usize;
+
+ (year, month, day, hour, minute, second, microsecond)
+}
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
new file mode 100644
index 0000000..bd0f1f6
--- /dev/null
+++ b/rust/src/lib.rs
@@ -0,0 +1,12 @@
+extern crate core;
+
+#[cfg(feature = "mimalloc")]
+#[global_allocator]
+static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
+
+mod constants;
+mod helpers;
+mod parsing;
+mod python;
+
+pub use python::_pendulum;
diff --git a/rust/src/parsing.rs b/rust/src/parsing.rs
new file mode 100644
index 0000000..757a3e3
--- /dev/null
+++ b/rust/src/parsing.rs
@@ -0,0 +1,905 @@
+use core::str;
+use std::{fmt, str::CharIndices};
+
+use crate::{
+ constants::MONTHS_OFFSETS,
+ helpers::{days_in_year, is_leap, is_long_year, week_day},
+};
+
+#[derive(Debug, Clone)]
+pub struct ParseError {
+ index: usize,
+ message: String,
+}
+
+impl fmt::Display for ParseError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{} (Position: {})", self.message, self.index)
+ }
+}
+
+pub struct ParsedDateTime {
+ pub year: u32,
+ pub month: u32,
+ pub day: u32,
+ pub hour: u32,
+ pub minute: u32,
+ pub second: u32,
+ pub microsecond: u32,
+ pub offset: Option<i32>,
+ pub has_offset: bool,
+ pub tzname: Option<String>,
+ pub has_date: bool,
+ pub has_time: bool,
+ pub extended_date_format: bool,
+ pub time_is_midnight: bool,
+}
+
+impl ParsedDateTime {
+ pub fn new() -> ParsedDateTime {
+ ParsedDateTime {
+ year: 0,
+ month: 1,
+ day: 1,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ microsecond: 0,
+ offset: None,
+ has_offset: false,
+ tzname: None,
+ has_date: false,
+ has_time: false,
+ extended_date_format: false,
+ time_is_midnight: false,
+ }
+ }
+}
+
+pub struct ParsedDuration {
+ pub years: u32,
+ pub months: u32,
+ pub weeks: u32,
+ pub days: u32,
+ pub hours: u32,
+ pub minutes: u32,
+ pub seconds: u32,
+ pub microseconds: u32,
+}
+
+impl ParsedDuration {
+ pub fn new() -> ParsedDuration {
+ ParsedDuration {
+ years: 0,
+ months: 0,
+ weeks: 0,
+ days: 0,
+ hours: 0,
+ minutes: 0,
+ seconds: 0,
+ microseconds: 0,
+ }
+ }
+}
+
+pub struct Parsed {
+ pub datetime: Option<ParsedDateTime>,
+ pub duration: Option<ParsedDuration>,
+ pub second_datetime: Option<ParsedDateTime>,
+}
+
+impl Parsed {
+ pub fn new() -> Parsed {
+ Parsed {
+ datetime: None,
+ duration: None,
+ second_datetime: None,
+ }
+ }
+}
+
+pub struct Parser<'a> {
+ /// Input to parse.
+ src: &'a str,
+ /// Iterator used for getting characters from `src`.
+ chars: CharIndices<'a>,
+ /// Current byte offset into `src`.
+ idx: usize,
+ /// Current character
+ current: char,
+}
+
+impl<'a> Parser<'a> {
+ /// Creates a new parser from a &str.
+ pub fn new(input: &'a str) -> Parser<'a> {
+ let mut p = Parser {
+ src: input,
+ chars: input.char_indices(),
+ idx: 0,
+ current: '\0',
+ };
+ p.inc();
+ p
+ }
+
+ /// Increments the parser if the end of the input has not been reached.
+ /// Returns whether or not it was able to advance.
+ fn inc(&mut self) -> Option<char> {
+ if let Some((i, ch)) = self.chars.next() {
+ self.idx = i;
+ self.current = ch;
+ Some(ch)
+ } else {
+ self.idx = self.src.len();
+ self.current = '\0';
+ None
+ }
+ }
+
+ fn parse_error(&mut self, message: String) -> ParseError {
+ ParseError {
+ index: self.idx,
+ message,
+ }
+ }
+
+ fn unexpected_character_error(
+ &mut self,
+ field_name: &str,
+ expected_character_count: usize,
+ ) -> ParseError {
+ if self.end() {
+ return self.parse_error(format!(
+ "Unexpected end of string while parsing {}. Expected {} more character{}.",
+ field_name,
+ expected_character_count,
+ if expected_character_count == 1 {
+ ""
+ } else {
+ "s"
+ }
+ ));
+ }
+
+ self.parse_error(format!(
+ "Invalid character while parsing {}: {}.",
+ field_name, self.current,
+ ))
+ }
+
+ /// Returns true if the parser has reached the end of the input.
+ fn end(&self) -> bool {
+ self.idx >= self.src.len()
+ }
+
+ fn parse_integer(&mut self, length: usize, field_name: &str) -> Result<u32, ParseError> {
+ let mut value: u32 = 0;
+
+ for i in 0..length {
+ if self.end() {
+ return Err(self.parse_error(format!(
+ "Unexpected end of string while parsing \"{}\". Expected {} more character{}",
+ field_name,
+ length - i,
+ if (length - i) != 1 { "s" } else { "" }
+ )));
+ }
+
+ if let Some(digit) = self.current.to_digit(10) {
+ value = 10 * value + digit;
+ self.inc();
+ } else {
+ return Err(self.unexpected_character_error(field_name, length - i));
+ }
+ }
+
+ Ok(value)
+ }
+
+ pub fn parse(&mut self) -> Result<Parsed, ParseError> {
+ let mut parsed = Parsed::new();
+
+ if self.current == 'P' {
+ // Duration (and possibly time interval)
+ self.parse_duration(&mut parsed)?;
+ } else {
+ self.parse_datetime(&mut parsed)?;
+ }
+
+ Ok(parsed)
+ }
+
+ fn parse_datetime(&mut self, parsed: &mut Parsed) -> Result<(), ParseError> {
+ let mut datetime = ParsedDateTime::new();
+
+ if self.current == 'T' {
+ self.parse_time(&mut datetime, false)?;
+
+ if !self.end() {
+ return Err(self.parse_error("Unconverted data remains".to_string()));
+ }
+
+ match &parsed.datetime {
+ Some(_) => {
+ parsed.second_datetime = Some(datetime);
+ }
+ None => match &parsed.duration {
+ Some(_) => {
+ parsed.second_datetime = Some(datetime);
+ }
+ None => {
+ parsed.datetime = Some(datetime);
+ }
+ },
+ }
+
+ return Ok(());
+ }
+
+ datetime.year = self.parse_integer(2, "year")?;
+
+ if self.current == ':' {
+ // Time in extended format
+ datetime.hour = datetime.year;
+ datetime.year = 0;
+ datetime.extended_date_format = true;
+ self.parse_time(&mut datetime, true)?;
+
+ if !self.end() {
+ return Err(self.parse_error("Unconverted data remains".to_string()));
+ }
+
+ match &parsed.datetime {
+ Some(_) => {
+ parsed.second_datetime = Some(datetime);
+ }
+ None => match &parsed.duration {
+ Some(_) => {
+ parsed.second_datetime = Some(datetime);
+ }
+ None => {
+ parsed.datetime = Some(datetime);
+ }
+ },
+ }
+
+ return Ok(());
+ }
+
+ datetime.has_date = true;
+ datetime.year = datetime.year * 100 + self.parse_integer(2, "year")?;
+
+ if self.current == '-' {
+ self.inc();
+ datetime.extended_date_format = true;
+
+ if self.current == 'W' {
+ // ISO week and day in extended format (i.e. Www-D)
+ self.inc();
+
+ let iso_week = self.parse_integer(2, "iso week")?;
+ let mut iso_day: u32 = 1;
+
+ if !self.end() && self.current != ' ' && self.current != 'T' {
+ // Optional day
+ if self.current != '-' {
+ return Err(self.parse_error(format!(
+ "Invalid character \"{}\" while parsing {}",
+ self.current, "date separator"
+ )));
+ }
+
+ self.inc();
+
+ iso_day = self.parse_integer(1, "iso day")?;
+ }
+
+ let (year, month, day) = self.iso_to_ymd(datetime.year, iso_week, iso_day)?;
+
+ datetime.year = year;
+ datetime.month = month;
+ datetime.day = day;
+ } else {
+ /*
+ Month and day in extended format (MM-DD) or ordinal date (DDD)
+ We'll assume first that the next part is a month and adjust if not.
+ */
+ datetime.month = self.parse_integer(2, "month")?;
+
+ if !self.end() && self.current != ' ' && self.current != 'T' {
+ if self.current == '-' {
+ // Optional day
+ self.inc();
+ datetime.day = self.parse_integer(2, "day")?;
+ } else {
+ // Ordinal day
+ let ordinal_day =
+ (datetime.month * 10 + self.parse_integer(1, "ordinal day")?) as i32;
+
+ let (year, month, day) =
+ self.ordinal_to_ymd(datetime.year, ordinal_day, false)?;
+
+ datetime.year = year;
+ datetime.month = month;
+ datetime.day = day;
+ }
+ } else {
+ datetime.day = 1;
+ }
+ }
+ } else if self.current == 'W' {
+ // Compact ISO week and day (WwwD)
+ self.inc();
+
+ let iso_week = self.parse_integer(2, "iso week")?;
+ let mut iso_day: u32 = 1;
+
+ if !self.end() && self.current != ' ' && self.current != 'T' {
+ iso_day = self.parse_integer(1, "iso day")?;
+ }
+
+ match self.iso_to_ymd(datetime.year, iso_week, iso_day) {
+ Ok((year, month, day)) => {
+ datetime.year = year;
+ datetime.month = month;
+ datetime.day = day;
+ }
+ Err(error) => return Err(error),
+ }
+ } else {
+ /*
+ Month and day in compact format (MMDD) or ordinal date (DDD)
+ We'll assume first that the next part is a month and adjust if not.
+ */
+ datetime.month = self.parse_integer(2, "month")?;
+ let mut ordinal_day = self.parse_integer(1, "ordinal day")? as i32;
+
+ if self.end() || self.current == ' ' || self.current == 'T' {
+ // Ordinal day
+ ordinal_day += datetime.month as i32 * 10;
+
+ let (year, month, day) = self.ordinal_to_ymd(datetime.year, ordinal_day, false)?;
+
+ datetime.year = year;
+ datetime.month = month;
+ datetime.day = day;
+ } else {
+ // Day
+ datetime.day = ordinal_day as u32 * 10 + self.parse_integer(1, "day")?;
+ }
+ }
+
+ if !self.end() {
+ self.parse_time(&mut datetime, false)?;
+ }
+
+ if !self.end() {
+ if self.current == '/' && parsed.datetime.is_none() && parsed.duration.is_none() {
+ // Interval
+ parsed.datetime = Some(datetime);
+
+ self.inc();
+
+ if self.current == 'P' {
+ // Duration
+ self.parse_duration(parsed)?;
+ } else {
+ self.parse_datetime(parsed)?;
+ }
+
+ return Ok(());
+ }
+
+ return Err(self.parse_error("Unconverted data remains".to_string()));
+ }
+
+ match &parsed.datetime {
+ Some(_) => {
+ parsed.second_datetime = Some(datetime);
+ }
+ None => match &parsed.duration {
+ Some(_) => {
+ parsed.second_datetime = Some(datetime);
+ }
+ None => {
+ parsed.datetime = Some(datetime);
+ }
+ },
+ }
+
+ Ok(())
+ }
+
+ fn parse_time(
+ &mut self,
+ datetime: &mut ParsedDateTime,
+ skip_hour: bool,
+ ) -> Result<(), ParseError> {
+ // TODO: Add support for decimal units
+
+ // Date/Time separator
+ if self.current != 'T' && self.current != ' ' && !skip_hour {
+ return Err(self.parse_error(format!(
+ "Invalid character \"{}\" while parsing {}",
+ self.current, "date and time separator (\"T\" or \" \")"
+ )));
+ }
+
+ datetime.has_time = true;
+
+ if !skip_hour {
+ self.inc();
+
+ // Hour
+ datetime.hour = self.parse_integer(2, "hour")?;
+ }
+
+ if !self.end() && self.current != 'Z' && self.current != '+' && self.current != '-' {
+ // Optional minute and second
+ if self.current == ':' {
+ // Minute and second in extended format (mm:ss)
+ self.inc();
+
+ // Minute
+ datetime.minute = self.parse_integer(2, "minute")?;
+
+ if !self.end() && self.current != 'Z' && self.current != '+' && self.current != '-'
+ {
+ // Optional second
+ if self.current != ':' {
+ return Err(self.parse_error(format!(
+ "Invalid character \"{}\" while parsing {}",
+ self.current, "time separator (\":\")"
+ )));
+ }
+
+ self.inc();
+
+ // Second
+ datetime.second = self.parse_integer(2, "second")?;
+
+ if self.current == '.' || self.current == ',' {
+ // Optional fractional second
+ self.inc();
+
+ datetime.microsecond = 0;
+ let mut i: u8 = 0;
+
+ while i < 6 {
+ if let Some(digit) = self.current.to_digit(10) {
+ datetime.microsecond = datetime.microsecond * 10 + digit;
+ } else if i == 0 {
+ // One digit minimum is required
+ return Err(self.unexpected_character_error("subsecond", 1));
+ } else {
+ break;
+ }
+
+ self.inc();
+ i += 1;
+ }
+
+ // Drop extraneous digits
+ while self.current.is_ascii_digit() {
+ self.inc();
+ }
+
+ // Expand missing microsecond
+ while i < 6 {
+ datetime.microsecond *= 10;
+ i += 1;
+ }
+ }
+
+ if !datetime.extended_date_format {
+ return Err(self.parse_error("Cannot combine \"basic\" date format with \"extended\" time format (Should be either `YYYY-MM-DDThh:mm:ss` or `YYYYMMDDThhmmss`).".to_string()));
+ }
+ }
+ } else {
+ // Minute and second in compact format (mmss)
+
+ // Minute
+ datetime.minute = self.parse_integer(2, "minute")?;
+
+ if !self.end() && self.current != 'Z' && self.current != '+' && self.current != '-'
+ {
+ // Optional second
+
+ datetime.second = self.parse_integer(2, "second")?;
+
+ if self.current == '.' || self.current == ',' {
+ // Optional fractional second
+ self.inc();
+
+ datetime.microsecond = 0;
+ let mut i: u8 = 0;
+
+ while i < 6 {
+ if let Some(digit) = self.current.to_digit(10) {
+ datetime.microsecond = datetime.microsecond * 10 + digit;
+ } else if i == 0 {
+ // One digit minimum is required
+ return Err(self.unexpected_character_error("subsecond", 1));
+ } else {
+ break;
+ }
+
+ self.inc();
+ i += 1;
+ }
+
+ // Drop extraneous digits
+ while self.current.is_ascii_digit() {
+ self.inc();
+ }
+
+ // Expand missing microsecond
+ while i < 6 {
+ datetime.microsecond *= 10;
+ i += 1;
+ }
+ }
+ }
+
+ if datetime.extended_date_format {
+ return Err(self.parse_error("Cannot combine \"extended\" date format with \"basic\" time format (Should be either `YYYY-MM-DDThh:mm:ss` or `YYYYMMDDThhmmss`).".to_string()));
+ }
+ }
+ }
+
+ if datetime.hour == 24
+ && datetime.minute == 0
+ && datetime.second == 0
+ && datetime.microsecond == 0
+ {
+ // Special case for 24:00:00, which is valid for ISO 8601.
+ // This is equivalent to 00:00:00 the next day.
+ // We will store the information for now.
+ datetime.time_is_midnight = true;
+ }
+
+ if self.current == 'Z' {
+ // UTC
+ datetime.offset = Some(0);
+ datetime.tzname = Some("UTC".to_string());
+ self.inc();
+ } else if matches!(self.current, '+' | '-') {
+ // Optional timezone offset
+ let tzsign = if self.current == '+' { 1 } else { -1 };
+ self.inc();
+ // Offset hour
+ let tzhour = self.parse_integer(2, "timezone hour")? as i32;
+ if self.current == ':' {
+ // Optional separator
+ self.inc();
+ }
+ let mut tzminute = if self.end() {
+ 0
+ } else {
+ // Optional minute
+ self.parse_integer(2, "timezone minute")? as i32
+ };
+ tzminute += tzhour * 60;
+ tzminute *= tzsign;
+ if tzminute > 24 * 60 {
+ return Err(self.parse_error("Timezone offset is too large".to_string()));
+ }
+ datetime.offset = Some(tzminute * 60);
+ }
+
+ Ok(())
+ }
+
+ fn parse_duration(&mut self, parsed: &mut Parsed) -> Result<(), ParseError> {
+ // Removing P operator
+ self.inc();
+
+ let mut duration: ParsedDuration = ParsedDuration::new();
+ let mut got_t: bool = false;
+ let mut last_had_fraction = false;
+
+ loop {
+ match self.current {
+ 'T' => {
+ if got_t {
+ return Err(
+ self.parse_error("Repeated time declaration in duration".to_string())
+ );
+ }
+
+ got_t = true;
+ }
+ _c => {
+ let (value, op_fraction) = self.parse_duration_number_frac()?;
+ if last_had_fraction {
+ return Err(self.parse_error("Invalid duration fraction".to_string()));
+ }
+
+ if op_fraction.is_some() {
+ last_had_fraction = true;
+ }
+
+ if got_t {
+ match self.current {
+ 'H' => {
+ if duration.minutes != 0
+ || duration.seconds != 0
+ || duration.microseconds != 0
+ {
+ return Err(
+ self.parse_error("Duration units out of order".to_string())
+ );
+ }
+
+ duration.hours += value;
+
+ if let Some(fraction) = op_fraction {
+ let extra_minutes = fraction * 60_f64;
+ let extra_full_minutes: f64 = extra_minutes.trunc();
+ duration.minutes += extra_full_minutes as u32;
+ let extra_seconds =
+ ((extra_minutes - extra_full_minutes) * 60.0).round();
+ let extra_full_seconds = extra_seconds.trunc();
+ duration.seconds += extra_full_seconds as u32;
+ let micro_extra = ((extra_seconds - extra_full_seconds)
+ * 1_000_000.0)
+ .round()
+ as u32;
+ duration.microseconds += micro_extra;
+ }
+ }
+ 'M' => {
+ if duration.seconds != 0 || duration.microseconds != 0 {
+ return Err(
+ self.parse_error("Duration units out of order".to_string())
+ );
+ }
+
+ duration.minutes += value;
+
+ if let Some(fraction) = op_fraction {
+ let extra_seconds = fraction * 60_f64;
+ let extra_full_seconds = extra_seconds.trunc();
+ duration.seconds += extra_full_seconds as u32;
+ let micro_extra = ((extra_seconds - extra_full_seconds)
+ * 1_000_000.0)
+ .round()
+ as u32;
+ duration.microseconds += micro_extra;
+ }
+ }
+ 'S' => {
+ duration.seconds = value;
+
+ if let Some(fraction) = op_fraction {
+ duration.microseconds +=
+ (fraction * 1_000_000.0).round() as u32;
+ }
+ }
+ _ => {
+ return Err(
+ self.parse_error("Invalid duration time unit".to_string())
+ )
+ }
+ }
+ } else {
+ match self.current {
+ 'Y' => {
+ if last_had_fraction {
+ return Err(self.parse_error(
+ "Fractional years in duration are not supported"
+ .to_string(),
+ ));
+ }
+
+ if duration.months != 0 || duration.days != 0 {
+ return Err(
+ self.parse_error("Duration units out of order".to_string())
+ );
+ }
+
+ duration.years = value;
+ }
+ 'M' => {
+ if last_had_fraction {
+ return Err(self.parse_error(
+ "Fractional months in duration are not supported"
+ .to_string(),
+ ));
+ }
+
+ if duration.days != 0 {
+ return Err(
+ self.parse_error("Duration units out of order".to_string())
+ );
+ }
+
+ duration.months = value;
+ }
+ 'W' => {
+ if duration.years != 0 || duration.months != 0 {
+ return Err(self.parse_error(
+ "Basic format durations cannot have weeks".to_string(),
+ ));
+ }
+
+ duration.weeks = value;
+
+ if let Some(fraction) = op_fraction {
+ let extra_days = fraction * 7_f64;
+ let extra_full_days = extra_days.trunc();
+ duration.days += extra_full_days as u32;
+ let extra_hours = (extra_days - extra_full_days) * 24.0;
+ let extra_full_hours = extra_hours.trunc();
+ duration.hours += extra_full_hours as u32;
+ let extra_minutes =
+ ((extra_hours - extra_full_hours) * 60.0).round();
+ let extra_full_minutes: f64 = extra_minutes.trunc();
+ duration.minutes += extra_full_minutes as u32;
+ let extra_seconds =
+ ((extra_minutes - extra_full_minutes) * 60.0).round();
+ let extra_full_seconds = extra_seconds.trunc();
+ duration.seconds += extra_full_seconds as u32;
+ let micro_extra = ((extra_seconds - extra_full_seconds)
+ * 1_000_000.0)
+ .round()
+ as u32;
+ duration.microseconds += micro_extra;
+ }
+ }
+ 'D' => {
+ if duration.weeks != 0 {
+ return Err(self.parse_error(
+ "Week format durations cannot have days".to_string(),
+ ));
+ }
+
+ duration.days += value;
+ if let Some(fraction) = op_fraction {
+ let extra_hours = fraction * 24.0;
+ let extra_full_hours = extra_hours.trunc();
+ duration.hours += extra_full_hours as u32;
+ let extra_minutes =
+ ((extra_hours - extra_full_hours) * 60.0).round();
+ let extra_full_minutes: f64 = extra_minutes.trunc();
+ duration.minutes += extra_full_minutes as u32;
+ let extra_seconds =
+ ((extra_minutes - extra_full_minutes) * 60.0).round();
+ let extra_full_seconds = extra_seconds.trunc();
+ duration.seconds += extra_full_seconds as u32;
+ let micro_extra = ((extra_seconds - extra_full_seconds)
+ * 1_000_000.0)
+ .round()
+ as u32;
+ duration.microseconds += micro_extra;
+ }
+ }
+ _ => {
+ return Err(
+ self.parse_error("Invalid duration time unit".to_string())
+ )
+ }
+ }
+ }
+ }
+ }
+ self.inc();
+
+ if self.end() {
+ break;
+ }
+ }
+
+ parsed.duration = Some(duration);
+
+ Ok(())
+ }
+
+ fn parse_duration_number_frac(&mut self) -> Result<(u32, Option<f64>), ParseError> {
+ let value = self.parse_duration_number()?;
+ let fraction = matches!(self.current, '.' | ',').then(|| {
+ let mut decimal = 0_f64;
+ let mut denominator = 1_f64;
+
+ while let Some(digit) = self.inc().and_then(|ch| ch.to_digit(10)) {
+ decimal *= 10.0;
+ decimal += f64::from(digit);
+ denominator *= 10.0;
+ }
+
+ decimal / denominator
+ });
+
+ Ok((value, fraction))
+ }
+
+ fn parse_duration_number(&mut self) -> Result<u32, ParseError> {
+ let Some(mut value) = self.current.to_digit(10) else {
+ return Err(self.parse_error("Invalid number in duration".to_string()));
+ };
+
+ while let Some(digit) = self.inc().and_then(|ch| ch.to_digit(10)) {
+ value *= 10;
+ value += digit;
+ }
+
+ Ok(value)
+ }
+
+ fn iso_to_ymd(
+ &mut self,
+ iso_year: u32,
+ iso_week: u32,
+ iso_day: u32,
+ ) -> Result<(u32, u32, u32), ParseError> {
+ if iso_week > 53 || iso_week > 52 && !is_long_year(iso_year as i32) {
+ return Err(ParseError {
+ index: self.idx,
+ message: format!(
+ "Invalid ISO date: week {iso_week} out of range for year {iso_year}"
+ ),
+ });
+ }
+
+ if iso_day > 7 {
+ return Err(ParseError {
+ index: self.idx,
+ message: "Invalid ISO date: week day is invalid".to_string(),
+ });
+ }
+
+ let ordinal: i32 =
+ iso_week as i32 * 7 + iso_day as i32 - (week_day(iso_year as i32, 1, 4) as i32 + 3);
+
+ self.ordinal_to_ymd(iso_year, ordinal, true)
+ }
+
+ fn ordinal_to_ymd(
+ &mut self,
+ year: u32,
+ ordinal: i32,
+ allow_out_of_bounds: bool,
+ ) -> Result<(u32, u32, u32), ParseError> {
+ let mut ord: i32 = ordinal;
+ let mut y: u32 = year;
+ let mut leap: usize = usize::from(is_leap(y as i32));
+
+ if ord < 1 {
+ if !allow_out_of_bounds {
+ return Err(self.parse_error(format!(
+ "Invalid ordinal day: {ordinal} is too small for year {year}"
+ )));
+ }
+ // Previous year
+ ord += days_in_year((year - 1) as i32) as i32;
+ y -= 1;
+ leap = usize::from(is_leap(y as i32));
+ }
+
+ if ord > days_in_year(y as i32) as i32 {
+ if !allow_out_of_bounds {
+ return Err(self.parse_error(format!(
+ "Invalid ordinal day: {ordinal} is too large for year {year}"
+ )));
+ }
+
+ // Next year
+ ord -= days_in_year(y as i32) as i32;
+ y += 1;
+ leap = usize::from(is_leap(y as i32));
+ }
+
+ for i in 1..14 {
+ if ord < MONTHS_OFFSETS[leap][i] {
+ let day = ord as u32 - MONTHS_OFFSETS[leap][i - 1] as u32;
+ let month = (i - 1) as u32;
+
+ return Ok((y, month, day));
+ }
+ }
+
+ Err(self.parse_error(format!(
+ "Invalid ordinal day: {ordinal} is too large for year {year}"
+ )))
+ }
+}
diff --git a/rust/src/python/helpers.rs b/rust/src/python/helpers.rs
new file mode 100644
index 0000000..4a53e59
--- /dev/null
+++ b/rust/src/python/helpers.rs
@@ -0,0 +1,388 @@
+use std::cmp::Ordering;
+
+use pyo3::{
+ intern,
+ prelude::*,
+ types::{PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyString, PyTimeAccess},
+ PyTypeInfo,
+};
+
+use crate::{
+ constants::{DAYS_PER_MONTHS, SECS_PER_DAY, SECS_PER_HOUR, SECS_PER_MIN},
+ helpers,
+};
+
+use crate::python::types::PreciseDiff;
+
+struct DateTimeInfo<'py> {
+ pub year: i32,
+ pub month: i32,
+ pub day: i32,
+ pub hour: i32,
+ pub minute: i32,
+ pub second: i32,
+ pub microsecond: i32,
+ pub total_seconds: i32,
+ pub offset: i32,
+ pub tz: &'py str,
+ pub is_datetime: bool,
+}
+
+impl PartialEq for DateTimeInfo<'_> {
+ fn eq(&self, other: &Self) -> bool {
+ (
+ self.year,
+ self.month,
+ self.day,
+ self.hour,
+ self.minute,
+ self.second,
+ self.microsecond,
+ )
+ .eq(&(
+ other.year,
+ other.month,
+ other.day,
+ other.hour,
+ other.minute,
+ other.second,
+ other.microsecond,
+ ))
+ }
+}
+
+impl PartialOrd for DateTimeInfo<'_> {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ (
+ self.year,
+ self.month,
+ self.day,
+ self.hour,
+ self.minute,
+ self.second,
+ self.microsecond,
+ )
+ .partial_cmp(&(
+ other.year,
+ other.month,
+ other.day,
+ other.hour,
+ other.minute,
+ other.second,
+ other.microsecond,
+ ))
+ }
+}
+
+pub fn get_tz_name<'py>(py: Python, dt: &'py PyAny) -> PyResult<&'py str> {
+ let tz: &str = "";
+
+ if !PyDateTime::is_type_of(dt) {
+ return Ok(tz);
+ }
+
+ let tzinfo = dt.getattr("tzinfo");
+
+ match tzinfo {
+ Err(_) => Ok(tz),
+ Ok(tzinfo) => {
+ if tzinfo.is_none() {
+ return Ok(tz);
+ }
+ if tzinfo.hasattr(intern!(py, "key")).unwrap_or(false) {
+ // zoneinfo timezone
+ let tzname: &PyString = tzinfo
+ .getattr(intern!(py, "key"))
+ .unwrap()
+ .downcast()
+ .unwrap();
+
+ return tzname.to_str();
+ } else if tzinfo.hasattr(intern!(py, "name")).unwrap_or(false) {
+ // Pendulum timezone
+ let tzname: &PyString = tzinfo
+ .getattr(intern!(py, "name"))
+ .unwrap()
+ .downcast()
+ .unwrap();
+
+ return tzname.to_str();
+ } else if tzinfo.hasattr(intern!(py, "zone")).unwrap_or(false) {
+ // pytz timezone
+ let tzname: &PyString = tzinfo
+ .getattr(intern!(py, "zone"))
+ .unwrap()
+ .downcast()
+ .unwrap();
+
+ return tzname.to_str();
+ }
+
+ Ok(tz)
+ }
+ }
+}
+
+pub fn get_offset(dt: &PyAny) -> PyResult<i32> {
+ if !PyDateTime::is_type_of(dt) {
+ return Ok(0);
+ }
+
+ let tzinfo = dt.getattr("tzinfo")?;
+
+ if tzinfo.is_none() {
+ return Ok(0);
+ }
+
+ let offset: &PyDelta = tzinfo.call_method1("utcoffset", (dt,))?.downcast()?;
+
+ Ok(offset.get_days() * SECS_PER_DAY as i32 + offset.get_seconds())
+}
+
+#[pyfunction]
+pub fn is_leap(year: i32) -> PyResult<bool> {
+ Ok(helpers::is_leap(year))
+}
+
+#[pyfunction]
+pub fn is_long_year(year: i32) -> PyResult<bool> {
+ Ok(helpers::is_long_year(year))
+}
+
+#[pyfunction]
+pub fn week_day(year: i32, month: u32, day: u32) -> PyResult<u32> {
+ Ok(helpers::week_day(year, month, day))
+}
+
+#[pyfunction]
+pub fn days_in_year(year: i32) -> PyResult<u32> {
+ Ok(helpers::days_in_year(year))
+}
+
+#[pyfunction]
+pub fn local_time(
+ unix_time: f64,
+ utc_offset: isize,
+ microsecond: usize,
+) -> PyResult<(usize, usize, usize, usize, usize, usize, usize)> {
+ Ok(helpers::local_time(unix_time, utc_offset, microsecond))
+}
+
+#[pyfunction]
+pub fn precise_diff<'py>(py: Python, dt1: &'py PyAny, dt2: &'py PyAny) -> PyResult<PreciseDiff> {
+ let mut sign = 1;
+ let mut dtinfo1 = DateTimeInfo {
+ year: dt1.downcast::<PyDate>()?.get_year(),
+ month: i32::from(dt1.downcast::<PyDate>()?.get_month()),
+ day: i32::from(dt1.downcast::<PyDate>()?.get_day()),
+ hour: 0,
+ minute: 0,
+ second: 0,
+ microsecond: 0,
+ total_seconds: 0,
+ tz: get_tz_name(py, dt1)?,
+ offset: get_offset(dt1)?,
+ is_datetime: PyDateTime::is_type_of(dt1),
+ };
+ let mut dtinfo2 = DateTimeInfo {
+ year: dt2.downcast::<PyDate>()?.get_year(),
+ month: i32::from(dt2.downcast::<PyDate>()?.get_month()),
+ day: i32::from(dt2.downcast::<PyDate>()?.get_day()),
+ hour: 0,
+ minute: 0,
+ second: 0,
+ microsecond: 0,
+ total_seconds: 0,
+ tz: get_tz_name(py, dt2)?,
+ offset: get_offset(dt2)?,
+ is_datetime: PyDateTime::is_type_of(dt2),
+ };
+ let in_same_tz: bool = dtinfo1.tz == dtinfo2.tz && !dtinfo1.tz.is_empty();
+ let mut total_days = helpers::day_number(dtinfo2.year, dtinfo2.month as u8, dtinfo2.day as u8)
+ - helpers::day_number(dtinfo1.year, dtinfo1.month as u8, dtinfo1.day as u8);
+
+ if dtinfo1.is_datetime {
+ let dt1dt: &PyDateTime = dt1.downcast()?;
+
+ dtinfo1.hour = i32::from(dt1dt.get_hour());
+ dtinfo1.minute = i32::from(dt1dt.get_minute());
+ dtinfo1.second = i32::from(dt1dt.get_second());
+ dtinfo1.microsecond = dt1dt.get_microsecond() as i32;
+
+ if !in_same_tz && dtinfo1.offset != 0 || total_days == 0 {
+ dtinfo1.hour -= dtinfo1.offset / SECS_PER_HOUR as i32;
+ dtinfo1.offset %= SECS_PER_HOUR as i32;
+ dtinfo1.minute -= dtinfo1.offset / SECS_PER_MIN as i32;
+ dtinfo1.offset %= SECS_PER_MIN as i32;
+ dtinfo1.second -= dtinfo1.offset;
+
+ if dtinfo1.second < 0 {
+ dtinfo1.second += 60;
+ dtinfo1.minute -= 1;
+ } else if dtinfo1.second > 60 {
+ dtinfo1.second -= 60;
+ dtinfo1.minute += 1;
+ }
+
+ if dtinfo1.minute < 0 {
+ dtinfo1.minute += 60;
+ dtinfo1.hour -= 1;
+ } else if dtinfo1.minute > 60 {
+ dtinfo1.minute -= 60;
+ dtinfo1.hour += 1;
+ }
+
+ if dtinfo1.hour < 0 {
+ dtinfo1.hour += 24;
+ dtinfo1.day -= 1;
+ } else if dtinfo1.hour > 24 {
+ dtinfo1.hour -= 24;
+ dtinfo1.day += 1;
+ }
+ }
+
+ dtinfo1.total_seconds = dtinfo1.hour * SECS_PER_HOUR as i32
+ + dtinfo1.minute * SECS_PER_MIN as i32
+ + dtinfo1.second;
+ }
+
+ if dtinfo2.is_datetime {
+ let dt2dt: &PyDateTime = dt2.downcast()?;
+
+ dtinfo2.hour = i32::from(dt2dt.get_hour());
+ dtinfo2.minute = i32::from(dt2dt.get_minute());
+ dtinfo2.second = i32::from(dt2dt.get_second());
+ dtinfo2.microsecond = dt2dt.get_microsecond() as i32;
+
+ if !in_same_tz && dtinfo2.offset != 0 || total_days == 0 {
+ dtinfo2.hour -= dtinfo2.offset / SECS_PER_HOUR as i32;
+ dtinfo2.offset %= SECS_PER_HOUR as i32;
+ dtinfo2.minute -= dtinfo2.offset / SECS_PER_MIN as i32;
+ dtinfo2.offset %= SECS_PER_MIN as i32;
+ dtinfo2.second -= dtinfo2.offset;
+
+ if dtinfo2.second < 0 {
+ dtinfo2.second += 60;
+ dtinfo2.minute -= 1;
+ } else if dtinfo2.second > 60 {
+ dtinfo2.second -= 60;
+ dtinfo2.minute += 1;
+ }
+
+ if dtinfo2.minute < 0 {
+ dtinfo2.minute += 60;
+ dtinfo2.hour -= 1;
+ } else if dtinfo2.minute > 60 {
+ dtinfo2.minute -= 60;
+ dtinfo2.hour += 1;
+ }
+
+ if dtinfo2.hour < 0 {
+ dtinfo2.hour += 24;
+ dtinfo2.day -= 1;
+ } else if dtinfo2.hour > 24 {
+ dtinfo2.hour -= 24;
+ dtinfo2.day += 1;
+ }
+ }
+
+ dtinfo2.total_seconds = dtinfo2.hour * SECS_PER_HOUR as i32
+ + dtinfo2.minute * SECS_PER_MIN as i32
+ + dtinfo2.second;
+ }
+
+ if dtinfo1 > dtinfo2 {
+ sign = -1;
+ (dtinfo1, dtinfo2) = (dtinfo2, dtinfo1);
+
+ total_days = -total_days;
+ }
+
+ let mut year_diff = dtinfo2.year - dtinfo1.year;
+ let mut month_diff = dtinfo2.month - dtinfo1.month;
+ let mut day_diff = dtinfo2.day - dtinfo1.day;
+ let mut hour_diff = dtinfo2.hour - dtinfo1.hour;
+ let mut minute_diff = dtinfo2.minute - dtinfo1.minute;
+ let mut second_diff = dtinfo2.second - dtinfo1.second;
+ let mut microsecond_diff = dtinfo2.microsecond - dtinfo1.microsecond;
+
+ if microsecond_diff < 0 {
+ microsecond_diff += 1_000_000;
+ second_diff -= 1;
+ }
+
+ if second_diff < 0 {
+ second_diff += 60;
+ minute_diff -= 1;
+ }
+
+ if minute_diff < 0 {
+ minute_diff += 60;
+ hour_diff -= 1;
+ }
+
+ if hour_diff < 0 {
+ hour_diff += 24;
+ day_diff -= 1;
+ }
+
+ if day_diff < 0 {
+ // If we have a difference in days,
+ // we have to check if they represent months
+ let mut year = dtinfo2.year;
+ let mut month = dtinfo2.month;
+
+ if month == 1 {
+ month = 12;
+ year -= 1;
+ } else {
+ month -= 1;
+ }
+
+ let leap = helpers::is_leap(year);
+
+ let days_in_last_month = DAYS_PER_MONTHS[usize::from(leap)][month as usize];
+ let days_in_month =
+ DAYS_PER_MONTHS[usize::from(helpers::is_leap(dtinfo2.year))][dtinfo2.month as usize];
+
+ match day_diff.cmp(&(days_in_month - days_in_last_month)) {
+ Ordering::Less => {
+ // We don't have a full month, we calculate days
+ if days_in_last_month < dtinfo1.day {
+ day_diff += dtinfo1.day;
+ } else {
+ day_diff += days_in_last_month;
+ }
+ }
+ Ordering::Equal => {
+ // We have exactly a full month
+ // We remove the days difference
+ // and add one to the months difference
+ day_diff = 0;
+ month_diff += 1;
+ }
+ Ordering::Greater => {
+ // We have a full month
+ day_diff += days_in_last_month;
+ }
+ }
+
+ month_diff -= 1;
+ }
+
+ if month_diff < 0 {
+ month_diff += 12;
+ year_diff -= 1;
+ }
+
+ Ok(PreciseDiff {
+ years: year_diff * sign,
+ months: month_diff * sign,
+ days: day_diff * sign,
+ hours: hour_diff * sign,
+ minutes: minute_diff * sign,
+ seconds: second_diff * sign,
+ microseconds: microsecond_diff * sign,
+ total_days: total_days * sign,
+ })
+}
diff --git a/rust/src/python/mod.rs b/rust/src/python/mod.rs
new file mode 100644
index 0000000..8d3cd41
--- /dev/null
+++ b/rust/src/python/mod.rs
@@ -0,0 +1,27 @@
+use pyo3::prelude::*;
+
+mod helpers;
+mod parsing;
+mod types;
+
+use helpers::{days_in_year, is_leap, is_long_year, local_time, precise_diff, week_day};
+use parsing::parse_iso8601;
+use types::{Duration, PreciseDiff};
+
+#[pymodule]
+pub fn _pendulum(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
+ m.add_function(wrap_pyfunction!(days_in_year, m)?)?;
+ m.add_function(wrap_pyfunction!(is_leap, m)?)?;
+ m.add_function(wrap_pyfunction!(is_long_year, m)?)?;
+ m.add_function(wrap_pyfunction!(local_time, m)?)?;
+ m.add_function(wrap_pyfunction!(week_day, m)?)?;
+ m.add_function(wrap_pyfunction!(parse_iso8601, m)?)?;
+ m.add_function(wrap_pyfunction!(precise_diff, m)?)?;
+ m.add_class::<Duration>()?;
+ m.add_class::<PreciseDiff>()?;
+
+ #[cfg(not(feature = "mimalloc"))]
+ m.setattr("__pendulum_default_allocator__", true)?; // uses setattr so this is not in __all__
+
+ Ok(())
+}
diff --git a/rust/src/python/parsing.rs b/rust/src/python/parsing.rs
new file mode 100644
index 0000000..48fa64c
--- /dev/null
+++ b/rust/src/python/parsing.rs
@@ -0,0 +1,117 @@
+use pyo3::exceptions;
+use pyo3::prelude::*;
+use pyo3::types::PyDate;
+use pyo3::types::PyDateTime;
+use pyo3::types::PyTime;
+
+use crate::parsing::Parser;
+use crate::python::types::{Duration, FixedTimezone};
+
+#[pyfunction]
+pub fn parse_iso8601(py: Python, input: &str) -> PyResult<PyObject> {
+ let parsed = Parser::new(input).parse();
+
+ match parsed {
+ Ok(parsed) => match (parsed.datetime, parsed.duration, parsed.second_datetime) {
+ (Some(datetime), None, None) => match (datetime.has_date, datetime.has_time) {
+ (true, true) => match datetime.offset {
+ Some(offset) => {
+ let dt = PyDateTime::new(
+ py,
+ datetime.year as i32,
+ datetime.month as u8,
+ datetime.day as u8,
+ datetime.hour as u8,
+ datetime.minute as u8,
+ datetime.second as u8,
+ datetime.microsecond,
+ Some(
+ Py::new(py, FixedTimezone::new(offset, datetime.tzname))?
+ .to_object(py)
+ .extract(py)?,
+ ),
+ )?;
+
+ Ok(dt.to_object(py))
+ }
+ None => {
+ let dt = PyDateTime::new(
+ py,
+ datetime.year as i32,
+ datetime.month as u8,
+ datetime.day as u8,
+ datetime.hour as u8,
+ datetime.minute as u8,
+ datetime.second as u8,
+ datetime.microsecond,
+ None,
+ )?;
+
+ Ok(dt.to_object(py))
+ }
+ },
+ (true, false) => {
+ let dt = PyDate::new(
+ py,
+ datetime.year as i32,
+ datetime.month as u8,
+ datetime.day as u8,
+ )?;
+
+ Ok(dt.to_object(py))
+ }
+ (false, true) => match datetime.offset {
+ Some(offset) => {
+ let dt = PyTime::new(
+ py,
+ datetime.hour as u8,
+ datetime.minute as u8,
+ datetime.second as u8,
+ datetime.microsecond,
+ Some(
+ Py::new(py, FixedTimezone::new(offset, datetime.tzname))?
+ .to_object(py)
+ .extract(py)?,
+ ),
+ )?;
+
+ Ok(dt.to_object(py))
+ }
+ None => {
+ let dt = PyTime::new(
+ py,
+ datetime.hour as u8,
+ datetime.minute as u8,
+ datetime.second as u8,
+ datetime.microsecond,
+ None,
+ )?;
+
+ Ok(dt.to_object(py))
+ }
+ },
+ (_, _) => Err(exceptions::PyValueError::new_err(
+ "Parsing error".to_string(),
+ )),
+ },
+ (None, Some(duration), None) => Ok(Py::new(
+ py,
+ Duration::new(
+ Some(duration.years),
+ Some(duration.months),
+ Some(duration.weeks),
+ Some(duration.days),
+ Some(duration.hours),
+ Some(duration.minutes),
+ Some(duration.seconds),
+ Some(duration.microseconds),
+ ),
+ )?
+ .to_object(py)),
+ (_, _, _) => Err(exceptions::PyValueError::new_err(
+ "Not yet implemented".to_string(),
+ )),
+ },
+ Err(error) => Err(exceptions::PyValueError::new_err(error.to_string())),
+ }
+}
diff --git a/rust/src/python/types/duration.rs b/rust/src/python/types/duration.rs
new file mode 100644
index 0000000..fc18f4e
--- /dev/null
+++ b/rust/src/python/types/duration.rs
@@ -0,0 +1,59 @@
+use pyo3::prelude::*;
+
+#[pyclass(module = "_pendulum")]
+pub struct Duration {
+ #[pyo3(get, set)]
+ pub years: u32,
+ #[pyo3(get, set)]
+ pub months: u32,
+ #[pyo3(get, set)]
+ pub weeks: u32,
+ #[pyo3(get, set)]
+ pub days: u32,
+ #[pyo3(get, set)]
+ pub hours: u32,
+ #[pyo3(get, set)]
+ pub minutes: u32,
+ #[pyo3(get, set)]
+ pub seconds: u32,
+ #[pyo3(get, set)]
+ pub microseconds: u32,
+}
+
+#[pymethods]
+impl Duration {
+ #[new]
+ #[pyo3(signature = (years=0, months=0, weeks=0, days=0, hours=0, minutes=0, seconds=0, microseconds=0))]
+ #[allow(clippy::too_many_arguments)]
+ pub fn new(
+ years: Option<u32>,
+ months: Option<u32>,
+ weeks: Option<u32>,
+ days: Option<u32>,
+ hours: Option<u32>,
+ minutes: Option<u32>,
+ seconds: Option<u32>,
+ microseconds: Option<u32>,
+ ) -> Self {
+ Self {
+ years: years.unwrap_or(0),
+ months: months.unwrap_or(0),
+ weeks: weeks.unwrap_or(0),
+ days: days.unwrap_or(0),
+ hours: hours.unwrap_or(0),
+ minutes: minutes.unwrap_or(0),
+ seconds: seconds.unwrap_or(0),
+ microseconds: microseconds.unwrap_or(0),
+ }
+ }
+
+ #[getter]
+ fn remaining_days(&self) -> PyResult<u32> {
+ Ok(self.days)
+ }
+
+ #[getter]
+ fn remaining_seconds(&self) -> PyResult<u32> {
+ Ok(self.seconds)
+ }
+}
diff --git a/rust/src/python/types/interval.rs b/rust/src/python/types/interval.rs
new file mode 100644
index 0000000..7137493
--- /dev/null
+++ b/rust/src/python/types/interval.rs
@@ -0,0 +1,46 @@
+use pyo3::prelude::*;
+
+use pyo3::types::PyDelta;
+
+#[pyclass(extends=PyDelta)]
+#[derive(Default)]
+pub struct Interval {
+ pub days: i32,
+ pub seconds: i32,
+ pub microseconds: i32,
+}
+
+#[pymethods]
+impl Interval {
+ #[new]
+ #[pyo3(signature = (days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0))]
+ pub fn new(
+ py: Python,
+ days: Option<i32>,
+ seconds: Option<i32>,
+ microseconds: Option<i32>,
+ milliseconds: Option<i32>,
+ minutes: Option<i32>,
+ hours: Option<i32>,
+ weeks: Option<i32>,
+ ) -> PyResult<Self> {
+ println!("{} days", 31);
+ PyDelta::new(
+ py,
+ days.unwrap_or(0),
+ seconds.unwrap_or(0),
+ microseconds.unwrap_or(0),
+ true,
+ )?;
+
+ let f = Ok(Self {
+ days: days.unwrap_or(0),
+ seconds: seconds.unwrap_or(0),
+ microseconds: microseconds.unwrap_or(0),
+ });
+
+ println!("{} days", 31);
+
+ f
+ }
+}
diff --git a/rust/src/python/types/mod.rs b/rust/src/python/types/mod.rs
new file mode 100644
index 0000000..cba11df
--- /dev/null
+++ b/rust/src/python/types/mod.rs
@@ -0,0 +1,7 @@
+mod duration;
+mod precise_diff;
+mod timezone;
+
+pub use duration::Duration;
+pub use precise_diff::PreciseDiff;
+pub use timezone::FixedTimezone;
diff --git a/rust/src/python/types/precise_diff.rs b/rust/src/python/types/precise_diff.rs
new file mode 100644
index 0000000..64ca3a6
--- /dev/null
+++ b/rust/src/python/types/precise_diff.rs
@@ -0,0 +1,53 @@
+use pyo3::prelude::*;
+
+#[pyclass(module = "_pendulum")]
+pub struct PreciseDiff {
+ #[pyo3(get, set)]
+ pub years: i32,
+ #[pyo3(get, set)]
+ pub months: i32,
+ #[pyo3(get, set)]
+ pub days: i32,
+ #[pyo3(get, set)]
+ pub hours: i32,
+ #[pyo3(get, set)]
+ pub minutes: i32,
+ #[pyo3(get, set)]
+ pub seconds: i32,
+ #[pyo3(get, set)]
+ pub microseconds: i32,
+ #[pyo3(get, set)]
+ pub total_days: i32,
+}
+
+#[pymethods]
+impl PreciseDiff {
+ #[new]
+ #[pyo3(signature = (years=0, months=0, days=0, hours=0, minutes=0, seconds=0, microseconds=0, total_days=0))]
+ #[allow(clippy::too_many_arguments)]
+ pub fn new(
+ years: Option<i32>,
+ months: Option<i32>,
+ days: Option<i32>,
+ hours: Option<i32>,
+ minutes: Option<i32>,
+ seconds: Option<i32>,
+ microseconds: Option<i32>,
+ total_days: Option<i32>,
+ ) -> Self {
+ Self {
+ years: years.unwrap_or(0),
+ months: months.unwrap_or(0),
+ days: days.unwrap_or(0),
+ hours: hours.unwrap_or(0),
+ minutes: minutes.unwrap_or(0),
+ seconds: seconds.unwrap_or(0),
+ microseconds: microseconds.unwrap_or(0),
+ total_days: total_days.unwrap_or(0),
+ }
+ }
+
+ fn __repr__(&self) -> String {
+ format!("PreciseDiff(years={}, months={}, days={}, hours={}, minutes={}, seconds={}, microseconds={}, total_days={})", self.years, self.months, self.days, self.hours, self.minutes, self.seconds, self.microseconds, self.total_days)
+ }
+}
diff --git a/rust/src/python/types/timezone.rs b/rust/src/python/types/timezone.rs
new file mode 100644
index 0000000..1a8bbad
--- /dev/null
+++ b/rust/src/python/types/timezone.rs
@@ -0,0 +1,52 @@
+use pyo3::prelude::*;
+use pyo3::types::{PyDelta, PyDict, PyTzInfo};
+
+#[pyclass(module = "_pendulum", extends = PyTzInfo)]
+#[derive(Clone)]
+pub struct FixedTimezone {
+ offset: i32,
+ name: Option<String>,
+}
+
+#[pymethods]
+impl FixedTimezone {
+ #[new]
+ pub fn new(offset: i32, name: Option<String>) -> Self {
+ Self { offset, name }
+ }
+
+ fn utcoffset<'p>(&self, py: Python<'p>, _dt: &PyAny) -> PyResult<&'p PyDelta> {
+ PyDelta::new(py, 0, self.offset, 0, true)
+ }
+
+ fn tzname(&self, _dt: &PyAny) -> String {
+ self.__str__()
+ }
+
+ fn dst<'p>(&self, py: Python<'p>, _dt: &PyAny) -> PyResult<&'p PyDelta> {
+ PyDelta::new(py, 0, 0, 0, true)
+ }
+
+ fn __repr__(&self) -> String {
+ format!(
+ "FixedTimezone({}, name=\"{}\")",
+ self.offset,
+ self.__str__()
+ )
+ }
+
+ fn __str__(&self) -> String {
+ if let Some(n) = &self.name {
+ n.clone()
+ } else {
+ let sign = if self.offset < 0 { "-" } else { "+" };
+ let minutes = self.offset.abs() / 60;
+ let (hour, minute) = (minutes / 60, minutes % 60);
+ format!("{sign}{hour:.2}:{minute:.2}")
+ }
+ }
+
+ fn __deepcopy__(&self, py: Python, _memo: &PyDict) -> PyResult<Py<Self>> {
+ Py::new(py, self.clone())
+ }
+}
diff --git a/pendulum/__init__.py b/src/pendulum/__init__.py
index 4491244..3863b76 100644
--- a/pendulum/__init__.py
+++ b/src/pendulum/__init__.py
@@ -4,27 +4,22 @@ import datetime as _datetime
from typing import Union
from typing import cast
+from typing import overload
from pendulum.__version__ import __version__
from pendulum.constants import DAYS_PER_WEEK
-from pendulum.constants import FRIDAY
from pendulum.constants import HOURS_PER_DAY
from pendulum.constants import MINUTES_PER_HOUR
-from pendulum.constants import MONDAY
from pendulum.constants import MONTHS_PER_YEAR
-from pendulum.constants import SATURDAY
from pendulum.constants import SECONDS_PER_DAY
from pendulum.constants import SECONDS_PER_HOUR
from pendulum.constants import SECONDS_PER_MINUTE
-from pendulum.constants import SUNDAY
-from pendulum.constants import THURSDAY
-from pendulum.constants import TUESDAY
-from pendulum.constants import WEDNESDAY
from pendulum.constants import WEEKS_PER_YEAR
from pendulum.constants import YEARS_PER_CENTURY
from pendulum.constants import YEARS_PER_DECADE
from pendulum.date import Date
from pendulum.datetime import DateTime
+from pendulum.day import WeekDay
from pendulum.duration import Duration
from pendulum.formatting import Formatter
from pendulum.helpers import format_diff
@@ -38,22 +33,59 @@ from pendulum.parser import parse
from pendulum.testing.traveller import Traveller
from pendulum.time import Time
from pendulum.tz import UTC
+from pendulum.tz import fixed_timezone
from pendulum.tz import local_timezone
from pendulum.tz import set_local_timezone
from pendulum.tz import test_local_timezone
-from pendulum.tz import timezone
from pendulum.tz import timezones
from pendulum.tz.timezone import FixedTimezone
from pendulum.tz.timezone import Timezone
+
+MONDAY = WeekDay.MONDAY
+TUESDAY = WeekDay.TUESDAY
+WEDNESDAY = WeekDay.WEDNESDAY
+THURSDAY = WeekDay.THURSDAY
+FRIDAY = WeekDay.FRIDAY
+SATURDAY = WeekDay.SATURDAY
+SUNDAY = WeekDay.SUNDAY
+
_TEST_NOW: DateTime | None = None
_LOCALE = "en"
-_WEEK_STARTS_AT = MONDAY
-_WEEK_ENDS_AT = SUNDAY
+_WEEK_STARTS_AT: WeekDay = WeekDay.MONDAY
+_WEEK_ENDS_AT: WeekDay = WeekDay.SUNDAY
_formatter = Formatter()
+@overload
+def timezone(name: int) -> FixedTimezone:
+ ...
+
+
+@overload
+def timezone(name: str) -> Timezone:
+ ...
+
+
+@overload
+def timezone(name: str | int) -> Timezone | FixedTimezone:
+ ...
+
+
+def timezone(name: str | int) -> Timezone | FixedTimezone:
+ """
+ Return a Timezone instance given its name.
+ """
+ if isinstance(name, int):
+ return fixed_timezone(name)
+
+ if name.lower() == "utc":
+ return UTC
+
+ return Timezone(name)
+
+
def _safe_timezone(
obj: str | float | _datetime.tzinfo | Timezone | FixedTimezone | None,
dt: _datetime.datetime | None = None,
@@ -73,10 +105,10 @@ def _safe_timezone(
elif isinstance(obj, _datetime.tzinfo):
# zoneinfo
if hasattr(obj, "key"):
- obj = obj.key # type: ignore
+ 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:
@@ -169,34 +201,47 @@ def time(hour: int, minute: int = 0, second: int = 0, microsecond: int = 0) -> T
return Time(hour, minute, second, microsecond)
+@overload
def instance(
- dt: _datetime.datetime,
+ obj: _datetime.datetime,
tz: str | Timezone | FixedTimezone | _datetime.tzinfo | None = UTC,
) -> DateTime:
+ ...
+
+
+@overload
+def instance(
+ obj: _datetime.date,
+ tz: str | Timezone | FixedTimezone | _datetime.tzinfo | None = UTC,
+) -> Date:
+ ...
+
+
+@overload
+def instance(
+ obj: _datetime.time,
+ tz: str | Timezone | FixedTimezone | _datetime.tzinfo | None = UTC,
+) -> Time:
+ ...
+
+
+def instance(
+ obj: _datetime.datetime | _datetime.date | _datetime.time,
+ tz: str | Timezone | FixedTimezone | _datetime.tzinfo | None = UTC,
+) -> DateTime | Date | Time:
"""
- Create a DateTime instance from a datetime one.
+ Create a DateTime/Date/Time instance from a datetime/date/time native one.
"""
- if not isinstance(dt, _datetime.datetime):
- raise ValueError("instance() only accepts datetime objects.")
-
- if isinstance(dt, DateTime):
- return dt
+ if isinstance(obj, (DateTime, Date, Time)):
+ return obj
- tz = dt.tzinfo or tz
+ if isinstance(obj, _datetime.date) and not isinstance(obj, _datetime.datetime):
+ return date(obj.year, obj.month, obj.day)
- if tz is not None:
- tz = _safe_timezone(tz, dt=dt)
+ if isinstance(obj, _datetime.time):
+ return Time.instance(obj, tz=tz)
- return datetime(
- dt.year,
- dt.month,
- dt.day,
- dt.hour,
- dt.minute,
- dt.second,
- dt.microsecond,
- tz=cast(Union[str, int, Timezone, FixedTimezone, None], tz),
- )
+ return DateTime.instance(obj, tz=tz)
def now(tz: str | Timezone | None = None) -> DateTime:
@@ -215,14 +260,14 @@ def today(tz: str | Timezone = "local") -> DateTime:
def tomorrow(tz: str | Timezone = "local") -> DateTime:
"""
- Create a DateTime instance for today.
+ Create a DateTime instance for tomorrow.
"""
return today(tz).add(days=1)
def yesterday(tz: str | Timezone = "local") -> DateTime:
"""
- Create a DateTime instance for today.
+ Create a DateTime instance for yesterday.
"""
return today(tz).subtract(days=1)
@@ -305,19 +350,12 @@ travel_back = _traveller.travel_back
__all__ = [
"__version__",
"DAYS_PER_WEEK",
- "FRIDAY",
"HOURS_PER_DAY",
"MINUTES_PER_HOUR",
- "MONDAY",
"MONTHS_PER_YEAR",
- "SATURDAY",
"SECONDS_PER_DAY",
"SECONDS_PER_HOUR",
"SECONDS_PER_MINUTE",
- "SUNDAY",
- "THURSDAY",
- "TUESDAY",
- "WEDNESDAY",
"WEEKS_PER_YEAR",
"YEARS_PER_CENTURY",
"YEARS_PER_DECADE",
@@ -325,6 +363,7 @@ __all__ = [
"DateTime",
"Duration",
"Formatter",
+ "WeekDay",
"date",
"datetime",
"duration",
diff --git a/src/pendulum/__version__.py b/src/pendulum/__version__.py
new file mode 100644
index 0000000..d6e60fe
--- /dev/null
+++ b/src/pendulum/__version__.py
@@ -0,0 +1,4 @@
+from __future__ import annotations
+
+
+__version__ = "3.0.0"
diff --git a/pendulum/_extensions/helpers.py b/src/pendulum/_helpers.py
index 01066a3..5f7bd03 100644
--- a/pendulum/_extensions/helpers.py
+++ b/src/pendulum/_helpers.py
@@ -3,7 +3,7 @@ from __future__ import annotations
import datetime
import math
-from collections import namedtuple
+from typing import NamedTuple
from typing import cast
from pendulum.constants import DAY_OF_WEEK_TABLE
@@ -25,12 +25,7 @@ from pendulum.tz.timezone import Timezone
from pendulum.utils._compat import zoneinfo
-class PreciseDiff(
- namedtuple(
- "PreciseDiff",
- "years months days " "hours minutes seconds microseconds " "total_days",
- )
-):
+class PreciseDiff(NamedTuple):
years: int
months: int
days: int
@@ -89,28 +84,6 @@ def days_in_year(year: int) -> int:
return DAYS_PER_N_YEAR
-def timestamp(dt: datetime.datetime) -> int:
- year = dt.year
-
- result = (year - 1970) * 365 + MONTHS_OFFSETS[0][dt.month]
- result += (year - 1968) // 4
- result -= (year - 1900) // 100
- result += (year - 1600) // 400
-
- if is_leap(year) and dt.month < 3:
- result -= 1
-
- result += dt.day - 1
- result *= 24
- result += dt.hour
- result *= 60
- result += dt.minute
- result *= 60
- result += dt.second
-
- return result
-
-
def local_time(
unix_time: int, utc_offset: int, microseconds: int
) -> tuple[int, int, int, int, int, int, int]:
@@ -119,7 +92,7 @@ def local_time(
for a particular transition type.
"""
year = EPOCH_YEAR
- seconds = int(math.floor(unix_time))
+ seconds = math.floor(unix_time)
# Shift to a base year that is 400-year aligned.
if seconds >= 0:
@@ -174,10 +147,8 @@ def local_time(
month -= 1
# Handle hours, minutes, seconds and microseconds
- hour = seconds // SECS_PER_HOUR
- seconds %= SECS_PER_HOUR
- minute = seconds // SECS_PER_MIN
- second = seconds % SECS_PER_MIN
+ hour, seconds = divmod(seconds, SECS_PER_HOUR)
+ minute, second = divmod(seconds, SECS_PER_MIN)
return year, month, day, hour, minute, second, microseconds
@@ -353,12 +324,12 @@ def _get_tzinfo_name(tzinfo: datetime.tzinfo | None) -> str | None:
if hasattr(tzinfo, "key"):
# zoneinfo timezone
- return cast(str, cast(zoneinfo.ZoneInfo, tzinfo).key)
+ return cast(zoneinfo.ZoneInfo, tzinfo).key
elif hasattr(tzinfo, "name"):
# Pendulum timezone
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/_extensions/_helpers.pyi b/src/pendulum/_pendulum.pyi
index 99a5397..74d7d83 100644
--- a/pendulum/_extensions/_helpers.pyi
+++ b/src/pendulum/_pendulum.pyi
@@ -1,22 +1,23 @@
from __future__ import annotations
-from collections import namedtuple
from datetime import date
from datetime import datetime
+from datetime import time
+from typing import NamedTuple
-def days_in_year(year: int) -> int: ...
-def is_leap(year: int) -> bool: ...
-def is_long_year(year: int) -> bool: ...
-def local_time(
- unix_time: int, utc_offset: int, microseconds: int
-) -> tuple[int, int, int, int, int, int, int]: ...
+class Duration:
+ years: int = 0
+ months: int = 0
+ weeks: int = 0
+ days: int = 0
+ remaining_days: int = 0
+ hours: int = 0
+ minutes: int = 0
+ seconds: int = 0
+ remaining_seconds: int = 0
+ microseconds: int = 0
-class PreciseDiff(
- namedtuple(
- "PreciseDiff",
- "years months days " "hours minutes seconds microseconds " "total_days",
- )
-):
+class PreciseDiff(NamedTuple):
years: int
months: int
days: int
@@ -26,6 +27,14 @@ class PreciseDiff(
microseconds: int
total_days: int
+def parse_iso8601(
+ text: str,
+) -> datetime | date | time | Duration: ...
+def days_in_year(year: int) -> int: ...
+def is_leap(year: int) -> bool: ...
+def is_long_year(year: int) -> bool: ...
+def local_time(
+ unix_time: int, utc_offset: int, microseconds: int
+) -> tuple[int, int, int, int, int, int, int]: ...
def precise_diff(d1: datetime | date, d2: datetime | date) -> PreciseDiff: ...
-def timestamp(dt: datetime) -> int: ...
def week_day(year: int, month: int, day: int) -> int: ...
diff --git a/pendulum/constants.py b/src/pendulum/constants.py
index a3d2a18..51eb059 100644
--- a/pendulum/constants.py
+++ b/src/pendulum/constants.py
@@ -1,13 +1,6 @@
# The day constants
from __future__ import annotations
-SUNDAY = 0
-MONDAY = 1
-TUESDAY = 2
-WEDNESDAY = 3
-THURSDAY = 4
-FRIDAY = 5
-SATURDAY = 6
# Number of X in Y.
YEARS_PER_CENTURY = 100
diff --git a/pendulum/date.py b/src/pendulum/date.py
index 4e2655b..e7b862c 100644
--- a/pendulum/date.py
+++ b/src/pendulum/date.py
@@ -1,3 +1,5 @@
+# The following is only needed because of Python 3.7
+# mypy: no-warn-unused-ignores
from __future__ import annotations
import calendar
@@ -6,55 +8,52 @@ import math
from datetime import date
from datetime import datetime
from datetime import timedelta
+from typing import TYPE_CHECKING
+from typing import ClassVar
from typing import NoReturn
from typing import cast
from typing import overload
import pendulum
-from pendulum.constants import FRIDAY
-from pendulum.constants import MONDAY
from pendulum.constants import MONTHS_PER_YEAR
-from pendulum.constants import SATURDAY
-from pendulum.constants import SUNDAY
-from pendulum.constants import THURSDAY
-from pendulum.constants import TUESDAY
-from pendulum.constants import WEDNESDAY
from pendulum.constants import YEARS_PER_CENTURY
from pendulum.constants import YEARS_PER_DECADE
+from pendulum.day import WeekDay
from pendulum.exceptions import PendulumException
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
+
+
class Date(FormattableMixin, date):
- # Names of days of the week
- _days = {
- SUNDAY: "Sunday",
- MONDAY: "Monday",
- TUESDAY: "Tuesday",
- WEDNESDAY: "Wednesday",
- THURSDAY: "Thursday",
- FRIDAY: "Friday",
- SATURDAY: "Saturday",
- }
-
- _MODIFIERS_VALID_UNITS = ["day", "week", "month", "year", "decade", "century"]
+ _MODIFIERS_VALID_UNITS: ClassVar[list[str]] = [
+ "day",
+ "week",
+ "month",
+ "year",
+ "decade",
+ "century",
+ ]
# Getters/Setters
def set(
self, year: int | None = None, month: int | None = None, day: int | None = None
- ) -> Date:
+ ) -> Self:
return self.replace(year=year, month=month, day=day)
@property
- def day_of_week(self) -> int:
+ def day_of_week(self) -> WeekDay:
"""
Returns the day of the week (0-6).
"""
- return self.isoweekday() % 7
+ return WeekDay(self.weekday())
@property
def day_of_year(self) -> int:
@@ -75,9 +74,7 @@ class Date(FormattableMixin, date):
@property
def week_of_month(self) -> int:
- first_day_of_month = self.replace(day=1)
-
- return self.week_of_year - first_day_of_month.week_of_year + 1
+ return math.ceil((self.day + self.first_of("month").isoweekday() - 1) / 7)
@property
def age(self) -> int:
@@ -85,7 +82,7 @@ class Date(FormattableMixin, date):
@property
def quarter(self) -> int:
- return int(math.ceil(self.month / 3))
+ return math.ceil(self.month / 3)
# String Formatting
@@ -110,7 +107,7 @@ class Date(FormattableMixin, date):
# COMPARISONS
- def closest(self, dt1: date, dt2: date) -> Date:
+ def closest(self, dt1: date, dt2: date) -> Self:
"""
Get the closest date from the instance.
"""
@@ -122,7 +119,7 @@ class Date(FormattableMixin, date):
return dt2
- def farthest(self, dt1: date, dt2: date) -> Date:
+ def farthest(self, dt1: date, dt2: date) -> Self:
"""
Get the farthest date from the instance.
"""
@@ -173,22 +170,23 @@ class Date(FormattableMixin, date):
Compares the date/month values of the two dates.
"""
if dt is None:
- dt = Date.today()
+ dt = self.__class__.today()
instance = self.__class__(dt.year, dt.month, dt.day)
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
def add(
self, years: int = 0, months: int = 0, weeks: int = 0, days: int = 0
- ) -> Date:
+ ) -> Self:
"""
Add duration to the instance.
@@ -209,7 +207,7 @@ class Date(FormattableMixin, date):
def subtract(
self, years: int = 0, months: int = 0, weeks: int = 0, days: int = 0
- ) -> Date:
+ ) -> Self:
"""
Remove duration from the instance.
@@ -220,7 +218,7 @@ class Date(FormattableMixin, date):
"""
return self.add(years=-years, months=-months, weeks=-weeks, days=-days)
- def _add_timedelta(self, delta: timedelta) -> Date:
+ def _add_timedelta(self, delta: timedelta) -> Self:
"""
Add timedelta duration to the instance.
@@ -236,7 +234,7 @@ class Date(FormattableMixin, date):
return self.add(days=delta.days)
- def _subtract_timedelta(self, delta: timedelta) -> Date:
+ def _subtract_timedelta(self, delta: timedelta) -> Self:
"""
Remove timedelta duration from the instance.
@@ -252,25 +250,25 @@ class Date(FormattableMixin, date):
return self.subtract(days=delta.days)
- def __add__(self, other: timedelta) -> Date:
+ def __add__(self, other: timedelta) -> Self:
if not isinstance(other, timedelta):
return NotImplemented
return self._add_timedelta(other)
- @overload
- def __sub__(self, delta: timedelta) -> Date:
+ @overload # type: ignore[override] # this is only needed because of Python 3.7
+ def __sub__(self, __delta: timedelta) -> Self:
...
@overload
- def __sub__(self, dt: datetime) -> NoReturn:
+ def __sub__(self, __dt: datetime) -> NoReturn:
...
@overload
- def __sub__(self, dt: Date) -> Interval:
+ def __sub__(self, __dt: Self) -> Interval:
...
- def __sub__(self, other: timedelta | date) -> Date | Interval:
+ def __sub__(self, other: timedelta | date) -> Self | Interval:
if isinstance(other, timedelta):
return self._subtract_timedelta(other)
@@ -335,7 +333,7 @@ class Date(FormattableMixin, date):
# MODIFIERS
- def start_of(self, unit: str) -> Date:
+ def start_of(self, unit: str) -> Self:
"""
Returns a copy of the instance with the time reset
with the following rules:
@@ -352,9 +350,9 @@ class Date(FormattableMixin, date):
if unit not in self._MODIFIERS_VALID_UNITS:
raise ValueError(f'Invalid unit "{unit}" for start_of()')
- return cast(Date, getattr(self, f"_start_of_{unit}")())
+ return cast("Self", getattr(self, f"_start_of_{unit}")())
- def end_of(self, unit: str) -> Date:
+ def end_of(self, unit: str) -> Self:
"""
Returns a copy of the instance with the time reset
with the following rules:
@@ -370,45 +368,45 @@ class Date(FormattableMixin, date):
if unit not in self._MODIFIERS_VALID_UNITS:
raise ValueError(f'Invalid unit "{unit}" for end_of()')
- return cast(Date, getattr(self, f"_end_of_{unit}")())
+ return cast("Self", getattr(self, f"_end_of_{unit}")())
- def _start_of_day(self) -> Date:
+ def _start_of_day(self) -> Self:
"""
Compatibility method.
"""
return self
- def _end_of_day(self) -> Date:
+ def _end_of_day(self) -> Self:
"""
Compatibility method
"""
return self
- def _start_of_month(self) -> Date:
+ def _start_of_month(self) -> Self:
"""
Reset the date to the first day of the month.
"""
return self.set(self.year, self.month, 1)
- def _end_of_month(self) -> Date:
+ def _end_of_month(self) -> Self:
"""
Reset the date to the last day of the month.
"""
return self.set(self.year, self.month, self.days_in_month)
- def _start_of_year(self) -> Date:
+ def _start_of_year(self) -> Self:
"""
Reset the date to the first day of the year.
"""
return self.set(self.year, 1, 1)
- def _end_of_year(self) -> Date:
+ def _end_of_year(self) -> Self:
"""
Reset the date to the last day of the year.
"""
return self.set(self.year, 12, 31)
- def _start_of_decade(self) -> Date:
+ def _start_of_decade(self) -> Self:
"""
Reset the date to the first day of the decade.
"""
@@ -416,7 +414,7 @@ class Date(FormattableMixin, date):
return self.set(year, 1, 1)
- def _end_of_decade(self) -> Date:
+ def _end_of_decade(self) -> Self:
"""
Reset the date to the last day of the decade.
"""
@@ -424,7 +422,7 @@ class Date(FormattableMixin, date):
return self.set(year, 12, 31)
- def _start_of_century(self) -> Date:
+ def _start_of_century(self) -> Self:
"""
Reset the date to the first day of the century.
"""
@@ -432,7 +430,7 @@ class Date(FormattableMixin, date):
return self.set(year, 1, 1)
- def _end_of_century(self) -> Date:
+ def _end_of_century(self) -> Self:
"""
Reset the date to the last day of the century.
"""
@@ -440,7 +438,7 @@ class Date(FormattableMixin, date):
return self.set(year, 12, 31)
- def _start_of_week(self) -> Date:
+ def _start_of_week(self) -> Self:
"""
Reset the date to the first day of the week.
"""
@@ -451,7 +449,7 @@ class Date(FormattableMixin, date):
return dt.start_of("day")
- def _end_of_week(self) -> Date:
+ def _end_of_week(self) -> Self:
"""
Reset the date to the last day of the week.
"""
@@ -462,7 +460,7 @@ class Date(FormattableMixin, date):
return dt.end_of("day")
- def next(self, day_of_week: int | None = None) -> Date:
+ def next(self, day_of_week: WeekDay | None = None) -> Self:
"""
Modify to the next occurrence of a given day of the week.
If no day_of_week is provided, modify to the next occurrence
@@ -474,7 +472,7 @@ class Date(FormattableMixin, date):
if day_of_week is None:
day_of_week = self.day_of_week
- if day_of_week < SUNDAY or day_of_week > SATURDAY:
+ if day_of_week < WeekDay.MONDAY or day_of_week > WeekDay.SUNDAY:
raise ValueError("Invalid day of week")
dt = self.add(days=1)
@@ -483,7 +481,7 @@ class Date(FormattableMixin, date):
return dt
- def previous(self, day_of_week: int | None = None) -> Date:
+ def previous(self, day_of_week: WeekDay | None = None) -> Self:
"""
Modify to the previous occurrence of a given day of the week.
If no day_of_week is provided, modify to the previous occurrence
@@ -495,7 +493,7 @@ class Date(FormattableMixin, date):
if day_of_week is None:
day_of_week = self.day_of_week
- if day_of_week < SUNDAY or day_of_week > SATURDAY:
+ if day_of_week < WeekDay.MONDAY or day_of_week > WeekDay.SUNDAY:
raise ValueError("Invalid day of week")
dt = self.subtract(days=1)
@@ -504,12 +502,13 @@ class Date(FormattableMixin, date):
return dt
- def first_of(self, unit: str, day_of_week: int | None = None) -> Date:
+ def first_of(self, unit: str, day_of_week: WeekDay | None = None) -> Self:
"""
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.
@@ -519,14 +518,15 @@ class Date(FormattableMixin, date):
if unit not in ["month", "quarter", "year"]:
raise ValueError(f'Invalid unit "{unit}" for first_of()')
- return cast(Date, getattr(self, f"_first_of_{unit}")(day_of_week))
+ return cast("Self", getattr(self, f"_first_of_{unit}")(day_of_week))
- def last_of(self, unit: str, day_of_week: int | None = None) -> Date:
+ def last_of(self, unit: str, day_of_week: WeekDay | None = None) -> Self:
"""
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.
@@ -536,9 +536,9 @@ class Date(FormattableMixin, date):
if unit not in ["month", "quarter", "year"]:
raise ValueError(f'Invalid unit "{unit}" for first_of()')
- return cast(Date, getattr(self, f"_last_of_{unit}")(day_of_week))
+ return cast("Self", getattr(self, f"_last_of_{unit}")(day_of_week))
- def nth_of(self, unit: str, nth: int, day_of_week: int) -> Date:
+ def nth_of(self, unit: str, nth: int, day_of_week: WeekDay) -> Self:
"""
Returns a new instance set to the given occurrence
of a given day of the week in the current unit.
@@ -555,16 +555,16 @@ class Date(FormattableMixin, date):
if unit not in ["month", "quarter", "year"]:
raise ValueError(f'Invalid unit "{unit}" for first_of()')
- dt = cast(Date, getattr(self, f"_nth_of_{unit}")(nth, day_of_week))
+ dt = cast("Self", getattr(self, f"_nth_of_{unit}")(nth, day_of_week))
if not dt:
raise PendulumException(
- f"Unable to find occurence {nth}"
- f" of {self._days[day_of_week]} in {unit}"
+ f"Unable to find occurrence {nth}"
+ f" of {WeekDay(day_of_week).name.capitalize()} in {unit}"
)
return dt
- def _first_of_month(self, day_of_week: int) -> Date:
+ def _first_of_month(self, day_of_week: WeekDay) -> Self:
"""
Modify to the first occurrence of a given day of the week
in the current month. If no day_of_week is provided,
@@ -580,7 +580,7 @@ class Date(FormattableMixin, date):
month = calendar.monthcalendar(dt.year, dt.month)
- calendar_day = (day_of_week - 1) % 7
+ calendar_day = day_of_week
if month[0][calendar_day] > 0:
day_of_month = month[0][calendar_day]
@@ -589,7 +589,7 @@ class Date(FormattableMixin, date):
return dt.set(day=day_of_month)
- def _last_of_month(self, day_of_week: int | None = None) -> Date:
+ def _last_of_month(self, day_of_week: WeekDay | None = None) -> Self:
"""
Modify to the last occurrence of a given day of the week
in the current month. If no day_of_week is provided,
@@ -605,7 +605,7 @@ class Date(FormattableMixin, date):
month = calendar.monthcalendar(dt.year, dt.month)
- calendar_day = (day_of_week - 1) % 7
+ calendar_day = day_of_week
if month[-1][calendar_day] > 0:
day_of_month = month[-1][calendar_day]
@@ -614,7 +614,7 @@ class Date(FormattableMixin, date):
return dt.set(day=day_of_month)
- def _nth_of_month(self, nth: int, day_of_week: int) -> Date | None:
+ def _nth_of_month(self, nth: int, day_of_week: WeekDay) -> Self | None:
"""
Modify to the given occurrence of a given day of the week
in the current month. If the calculated occurrence is outside,
@@ -635,7 +635,7 @@ class Date(FormattableMixin, date):
return None
- def _first_of_quarter(self, day_of_week: int | None = None) -> Date:
+ def _first_of_quarter(self, day_of_week: WeekDay | None = None) -> Self:
"""
Modify to the first occurrence of a given day of the week
in the current quarter. If no day_of_week is provided,
@@ -646,7 +646,7 @@ class Date(FormattableMixin, date):
"month", day_of_week
)
- def _last_of_quarter(self, day_of_week: int | None = None) -> Date:
+ def _last_of_quarter(self, day_of_week: WeekDay | None = None) -> Self:
"""
Modify to the last occurrence of a given day of the week
in the current quarter. If no day_of_week is provided,
@@ -655,7 +655,7 @@ class Date(FormattableMixin, date):
"""
return self.set(self.year, self.quarter * 3, 1).last_of("month", day_of_week)
- def _nth_of_quarter(self, nth: int, day_of_week: int) -> Date | None:
+ def _nth_of_quarter(self, nth: int, day_of_week: WeekDay) -> Self | None:
"""
Modify to the given occurrence of a given day of the week
in the current quarter. If the calculated occurrence is outside,
@@ -678,7 +678,7 @@ class Date(FormattableMixin, date):
return self.set(self.year, dt.month, dt.day)
- def _first_of_year(self, day_of_week: int | None = None) -> Date:
+ def _first_of_year(self, day_of_week: WeekDay | None = None) -> Self:
"""
Modify to the first occurrence of a given day of the week
in the current year. If no day_of_week is provided,
@@ -687,7 +687,7 @@ class Date(FormattableMixin, date):
"""
return self.set(month=1).first_of("month", day_of_week)
- def _last_of_year(self, day_of_week: int | None = None) -> Date:
+ def _last_of_year(self, day_of_week: WeekDay | None = None) -> Self:
"""
Modify to the last occurrence of a given day of the week
in the current year. If no day_of_week is provided,
@@ -696,7 +696,7 @@ class Date(FormattableMixin, date):
"""
return self.set(month=MONTHS_PER_YEAR).last_of("month", day_of_week)
- def _nth_of_year(self, nth: int, day_of_week: int) -> Date | None:
+ def _nth_of_year(self, nth: int, day_of_week: WeekDay) -> Self | None:
"""
Modify to the given occurrence of a given day of the week
in the current year. If the calculated occurrence is outside,
@@ -717,7 +717,7 @@ class Date(FormattableMixin, date):
return self.set(self.year, dt.month, dt.day)
- def average(self, dt: date | None = None) -> Date:
+ def average(self, dt: date | None = None) -> Self:
"""
Modify the current instance to the average
of a given instance (default now) and the current instance.
@@ -730,29 +730,29 @@ class Date(FormattableMixin, date):
# Native methods override
@classmethod
- def today(cls) -> Date:
+ def today(cls) -> Self:
dt = date.today()
return cls(dt.year, dt.month, dt.day)
@classmethod
- def fromtimestamp(cls, t: float) -> Date:
+ def fromtimestamp(cls, t: float) -> Self:
dt = super().fromtimestamp(t)
return cls(dt.year, dt.month, dt.day)
@classmethod
- def fromordinal(cls, n: int) -> Date:
+ def fromordinal(cls, n: int) -> Self:
dt = super().fromordinal(n)
return cls(dt.year, dt.month, dt.day)
def replace(
self,
- year: int | None = None,
- month: int | None = None,
- day: int | None = None,
- ) -> Date:
+ year: SupportsIndex | None = None,
+ month: SupportsIndex | None = None,
+ day: SupportsIndex | None = None,
+ ) -> Self:
year = year if year is not None else self.year
month = month if month is not None else self.month
day = day if day is not None else self.day
diff --git a/pendulum/datetime.py b/src/pendulum/datetime.py
index 52ad3cc..752a9ac 100644
--- a/pendulum/datetime.py
+++ b/src/pendulum/datetime.py
@@ -2,10 +2,12 @@ from __future__ import annotations
import calendar
import datetime
+import traceback
from typing import TYPE_CHECKING
from typing import Any
from typing import Callable
+from typing import ClassVar
from typing import Optional
from typing import cast
from typing import overload
@@ -22,14 +24,13 @@ from pendulum.constants import RFC1036
from pendulum.constants import RFC1123
from pendulum.constants import RFC2822
from pendulum.constants import RSS
-from pendulum.constants import SATURDAY
from pendulum.constants import SECONDS_PER_DAY
from pendulum.constants import SECONDS_PER_MINUTE
-from pendulum.constants import SUNDAY
from pendulum.constants import W3C
from pendulum.constants import YEARS_PER_CENTURY
from pendulum.constants import YEARS_PER_DECADE
from pendulum.date import Date
+from pendulum.day import WeekDay
from pendulum.exceptions import PendulumException
from pendulum.helpers import add_duration
from pendulum.interval import Interval
@@ -38,32 +39,36 @@ from pendulum.tz import UTC
from pendulum.tz import local_timezone
from pendulum.tz.timezone import FixedTimezone
from pendulum.tz.timezone import Timezone
-from pendulum.utils._compat import PY38
+
if TYPE_CHECKING:
- from typing import Literal
+ from typing_extensions import Literal
+ from typing_extensions import Self
+ from typing_extensions import SupportsIndex
class DateTime(datetime.datetime, Date):
- EPOCH: DateTime
+ EPOCH: ClassVar[DateTime]
+ min: ClassVar[DateTime]
+ max: ClassVar[DateTime]
# Formats
- _FORMATS: dict[str, str | Callable[[datetime.datetime], str]] = {
+ _FORMATS: ClassVar[dict[str, str | Callable[[datetime.datetime], str]]] = {
"atom": ATOM,
"cookie": COOKIE,
- "iso8601": lambda dt: dt.isoformat(),
+ "iso8601": lambda dt: dt.isoformat("T"),
"rfc822": RFC822,
"rfc850": RFC850,
"rfc1036": RFC1036,
"rfc1123": RFC1123,
"rfc2822": RFC2822,
- "rfc3339": lambda dt: dt.isoformat(),
+ "rfc3339": lambda dt: dt.isoformat("T"),
"rss": RSS,
"w3c": W3C,
}
- _MODIFIERS_VALID_UNITS: list[str] = [
+ _MODIFIERS_VALID_UNITS: ClassVar[list[str]] = [
"second",
"minute",
"hour",
@@ -80,17 +85,17 @@ class DateTime(datetime.datetime, Date):
@classmethod
def create(
cls,
- year: int,
- month: int,
- day: int,
- hour: int = 0,
- minute: int = 0,
- second: int = 0,
- microsecond: int = 0,
+ year: SupportsIndex,
+ month: SupportsIndex,
+ day: SupportsIndex,
+ hour: SupportsIndex = 0,
+ minute: SupportsIndex = 0,
+ second: SupportsIndex = 0,
+ microsecond: SupportsIndex = 0,
tz: str | float | Timezone | FixedTimezone | None | datetime.tzinfo = UTC,
fold: int = 1,
raise_on_unknown_times: bool = False,
- ) -> DateTime:
+ ) -> Self:
"""
Creates a new DateTime instance from a specific date and time.
"""
@@ -116,20 +121,43 @@ class DateTime(datetime.datetime, Date):
fold=dt.fold,
)
+ @classmethod
+ def instance(
+ cls,
+ dt: datetime.datetime,
+ tz: str | Timezone | FixedTimezone | datetime.tzinfo | None = UTC,
+ ) -> Self:
+ tz = dt.tzinfo or tz
+
+ if tz is not None:
+ tz = pendulum._safe_timezone(tz, dt=dt)
+
+ return cls.create(
+ dt.year,
+ dt.month,
+ dt.day,
+ dt.hour,
+ dt.minute,
+ dt.second,
+ dt.microsecond,
+ tz=tz,
+ fold=dt.fold,
+ )
+
@overload
@classmethod
- def now(cls, tz: datetime.tzinfo | None = None) -> DateTime:
+ def now(cls, tz: datetime.tzinfo | None = None) -> Self:
...
@overload
@classmethod
- def now(cls, tz: str | Timezone | FixedTimezone | None = None) -> DateTime:
+ def now(cls, tz: str | Timezone | FixedTimezone | None = None) -> Self:
...
@classmethod
def now(
cls, tz: str | Timezone | FixedTimezone | datetime.tzinfo | None = None
- ) -> DateTime:
+ ) -> Self:
"""
Get a DateTime instance for the current date and time.
"""
@@ -155,19 +183,19 @@ class DateTime(datetime.datetime, Date):
)
@classmethod
- def utcnow(cls) -> DateTime:
+ def utcnow(cls) -> Self:
"""
Get a DateTime instance for the current date and time in UTC.
"""
return cls.now(UTC)
@classmethod
- def today(cls) -> DateTime:
+ def today(cls) -> Self:
return cls.now()
@classmethod
- def strptime(cls, time: str, fmt: str) -> DateTime:
- return pendulum.instance(datetime.datetime.strptime(time, fmt))
+ def strptime(cls, time: str, fmt: str) -> Self:
+ return cls.instance(datetime.datetime.strptime(time, fmt))
# Getters/Setters
@@ -181,7 +209,7 @@ class DateTime(datetime.datetime, Date):
second: int | None = None,
microsecond: int | None = None,
tz: str | float | Timezone | FixedTimezone | datetime.tzinfo | None = None,
- ) -> DateTime:
+ ) -> Self:
if year is None:
year = self.year
if month is None:
@@ -199,8 +227,8 @@ class DateTime(datetime.datetime, Date):
if tz is None:
tz = self.tz
- return DateTime.create(
- year, month, day, hour, minute, second, microsecond, tz=tz
+ return self.__class__.create(
+ year, month, day, hour, minute, second, microsecond, tz=tz, fold=self.fold
)
@property
@@ -286,7 +314,7 @@ class DateTime(datetime.datetime, Date):
def time(self) -> Time:
return Time(self.hour, self.minute, self.second, self.microsecond)
- def naive(self) -> DateTime:
+ def naive(self) -> Self:
"""
Return the DateTime without timezone information.
"""
@@ -300,7 +328,7 @@ class DateTime(datetime.datetime, Date):
self.microsecond,
)
- def on(self, year: int, month: int, day: int) -> DateTime:
+ def on(self, year: int, month: int, day: int) -> Self:
"""
Returns a new instance with the current date set to a different date.
"""
@@ -308,7 +336,7 @@ class DateTime(datetime.datetime, Date):
def at(
self, hour: int, minute: int = 0, second: int = 0, microsecond: int = 0
- ) -> DateTime:
+ ) -> Self:
"""
Returns a new instance with the current time to a different time.
"""
@@ -316,7 +344,7 @@ class DateTime(datetime.datetime, Date):
hour=hour, minute=minute, second=second, microsecond=microsecond
)
- def in_timezone(self, tz: str | Timezone | FixedTimezone) -> DateTime:
+ def in_timezone(self, tz: str | Timezone | FixedTimezone) -> Self:
"""
Set the instance's timezone from a string or object.
"""
@@ -326,9 +354,9 @@ class DateTime(datetime.datetime, Date):
if not self.timezone:
dt = dt.replace(fold=1)
- return cast(DateTime, tz.convert(dt))
+ return tz.convert(dt)
- def in_tz(self, tz: str | Timezone | FixedTimezone) -> DateTime:
+ def in_tz(self, tz: str | Timezone | FixedTimezone) -> Self:
"""
Set the instance's timezone from a string or object.
"""
@@ -439,7 +467,7 @@ class DateTime(datetime.datetime, Date):
return self.format(fmt_value, locale=locale)
def __str__(self) -> str:
- return self.isoformat("T")
+ return self.isoformat(" ")
def __repr__(self) -> str:
us = ""
@@ -466,19 +494,19 @@ class DateTime(datetime.datetime, Date):
)
# Comparisons
- def closest(self, *dts: datetime.datetime) -> DateTime: # type: ignore[override]
+ def closest(self, *dts: datetime.datetime) -> Self: # type: ignore[override]
"""
- Get the farthest date from the instance.
+ Get the closest date to the instance.
"""
- pdts = [pendulum.instance(x) for x in dts]
+ pdts = [self.instance(x) for x in dts]
return min((abs(self - dt), dt) for dt in pdts)[1]
- def farthest(self, *dts: datetime.datetime) -> DateTime: # type: ignore[override]
+ def farthest(self, *dts: datetime.datetime) -> Self: # type: ignore[override]
"""
Get the farthest date from the instance.
"""
- pdts = [pendulum.instance(x) for x in dts]
+ pdts = [self.instance(x) for x in dts]
return max((abs(self - dt), dt) for dt in pdts)[1]
@@ -510,7 +538,7 @@ class DateTime(datetime.datetime, Date):
Checks if the passed in date is the same day
as the instance current day.
"""
- dt = pendulum.instance(dt)
+ dt = self.instance(dt)
return self.to_date_string() == dt.to_date_string()
@@ -524,7 +552,7 @@ class DateTime(datetime.datetime, Date):
if dt is None:
dt = self.now(self.tz)
- instance = pendulum.instance(dt)
+ instance = self.instance(dt)
return (self.month, self.day) == (instance.month, instance.day)
@@ -540,7 +568,7 @@ class DateTime(datetime.datetime, Date):
minutes: int = 0,
seconds: float = 0,
microseconds: int = 0,
- ) -> DateTime:
+ ) -> Self:
"""
Add a duration to the instance.
@@ -577,7 +605,7 @@ class DateTime(datetime.datetime, Date):
)
if units_of_variable_length or self.tz is None:
- return DateTime.create(
+ return self.__class__.create(
dt.year,
dt.month,
dt.day,
@@ -623,7 +651,7 @@ class DateTime(datetime.datetime, Date):
minutes: int = 0,
seconds: float = 0,
microseconds: int = 0,
- ) -> DateTime:
+ ) -> Self:
"""
Remove duration from the instance.
"""
@@ -641,7 +669,7 @@ class DateTime(datetime.datetime, Date):
# Adding a final underscore to the method name
# to avoid errors for PyPy which already defines
# a _add_timedelta method
- def _add_timedelta_(self, delta: datetime.timedelta) -> DateTime:
+ def _add_timedelta_(self, delta: datetime.timedelta) -> Self:
"""
Add timedelta duration to the instance.
"""
@@ -657,13 +685,11 @@ class DateTime(datetime.datetime, Date):
microseconds=delta.microseconds,
)
elif isinstance(delta, pendulum.Duration):
- return self.add(
- years=delta.years, months=delta.months, seconds=delta._total
- )
+ return self.add(**delta._signature) # type: ignore[attr-defined]
return self.add(seconds=delta.total_seconds())
- def _subtract_timedelta(self, delta: datetime.timedelta) -> DateTime:
+ def _subtract_timedelta(self, delta: datetime.timedelta) -> Self:
"""
Remove timedelta duration from the instance.
"""
@@ -722,7 +748,7 @@ class DateTime(datetime.datetime, Date):
return pendulum.format_diff(diff, is_now, absolute, locale)
# Modifiers
- def start_of(self, unit: str) -> DateTime:
+ def start_of(self, unit: str) -> Self:
"""
Returns a copy of the instance with the time reset
with the following rules:
@@ -740,9 +766,9 @@ class DateTime(datetime.datetime, Date):
if unit not in self._MODIFIERS_VALID_UNITS:
raise ValueError(f'Invalid unit "{unit}" for start_of()')
- return cast(DateTime, getattr(self, f"_start_of_{unit}")())
+ return cast("Self", getattr(self, f"_start_of_{unit}")())
- def end_of(self, unit: str) -> DateTime:
+ def end_of(self, unit: str) -> Self:
"""
Returns a copy of the instance with the time reset
with the following rules:
@@ -760,83 +786,83 @@ class DateTime(datetime.datetime, Date):
if unit not in self._MODIFIERS_VALID_UNITS:
raise ValueError(f'Invalid unit "{unit}" for end_of()')
- return cast(DateTime, getattr(self, f"_end_of_{unit}")())
+ return cast("Self", getattr(self, f"_end_of_{unit}")())
- def _start_of_second(self) -> DateTime:
+ def _start_of_second(self) -> Self:
"""
Reset microseconds to 0.
"""
return self.set(microsecond=0)
- def _end_of_second(self) -> DateTime:
+ def _end_of_second(self) -> Self:
"""
Set microseconds to 999999.
"""
return self.set(microsecond=999999)
- def _start_of_minute(self) -> DateTime:
+ def _start_of_minute(self) -> Self:
"""
Reset seconds and microseconds to 0.
"""
return self.set(second=0, microsecond=0)
- def _end_of_minute(self) -> DateTime:
+ def _end_of_minute(self) -> Self:
"""
Set seconds to 59 and microseconds to 999999.
"""
return self.set(second=59, microsecond=999999)
- def _start_of_hour(self) -> DateTime:
+ def _start_of_hour(self) -> Self:
"""
Reset minutes, seconds and microseconds to 0.
"""
return self.set(minute=0, second=0, microsecond=0)
- def _end_of_hour(self) -> DateTime:
+ def _end_of_hour(self) -> Self:
"""
Set minutes and seconds to 59 and microseconds to 999999.
"""
return self.set(minute=59, second=59, microsecond=999999)
- def _start_of_day(self) -> DateTime:
+ def _start_of_day(self) -> Self:
"""
Reset the time to 00:00:00.
"""
return self.at(0, 0, 0, 0)
- def _end_of_day(self) -> DateTime:
+ def _end_of_day(self) -> Self:
"""
Reset the time to 23:59:59.999999.
"""
return self.at(23, 59, 59, 999999)
- def _start_of_month(self) -> DateTime:
+ def _start_of_month(self) -> Self:
"""
Reset the date to the first day of the month and the time to 00:00:00.
"""
return self.set(self.year, self.month, 1, 0, 0, 0, 0)
- def _end_of_month(self) -> DateTime:
+ def _end_of_month(self) -> Self:
"""
Reset the date to the last day of the month
and the time to 23:59:59.999999.
"""
return self.set(self.year, self.month, self.days_in_month, 23, 59, 59, 999999)
- def _start_of_year(self) -> DateTime:
+ def _start_of_year(self) -> Self:
"""
Reset the date to the first day of the year and the time to 00:00:00.
"""
return self.set(self.year, 1, 1, 0, 0, 0, 0)
- def _end_of_year(self) -> DateTime:
+ def _end_of_year(self) -> Self:
"""
Reset the date to the last day of the year
and the time to 23:59:59.999999.
"""
return self.set(self.year, 12, 31, 23, 59, 59, 999999)
- def _start_of_decade(self) -> DateTime:
+ def _start_of_decade(self) -> Self:
"""
Reset the date to the first day of the decade
and the time to 00:00:00.
@@ -844,7 +870,7 @@ class DateTime(datetime.datetime, Date):
year = self.year - self.year % YEARS_PER_DECADE
return self.set(year, 1, 1, 0, 0, 0, 0)
- def _end_of_decade(self) -> DateTime:
+ def _end_of_decade(self) -> Self:
"""
Reset the date to the last day of the decade
and the time to 23:59:59.999999.
@@ -853,7 +879,7 @@ class DateTime(datetime.datetime, Date):
return self.set(year, 12, 31, 23, 59, 59, 999999)
- def _start_of_century(self) -> DateTime:
+ def _start_of_century(self) -> Self:
"""
Reset the date to the first day of the century
and the time to 00:00:00.
@@ -862,7 +888,7 @@ class DateTime(datetime.datetime, Date):
return self.set(year, 1, 1, 0, 0, 0, 0)
- def _end_of_century(self) -> DateTime:
+ def _end_of_century(self) -> Self:
"""
Reset the date to the last day of the century
and the time to 23:59:59.999999.
@@ -871,7 +897,7 @@ class DateTime(datetime.datetime, Date):
return self.set(year, 12, 31, 23, 59, 59, 999999)
- def _start_of_week(self) -> DateTime:
+ def _start_of_week(self) -> Self:
"""
Reset the date to the first day of the week
and the time to 00:00:00.
@@ -883,7 +909,7 @@ class DateTime(datetime.datetime, Date):
return dt.start_of("day")
- def _end_of_week(self) -> DateTime:
+ def _end_of_week(self) -> Self:
"""
Reset the date to the last day of the week
and the time to 23:59:59.
@@ -895,7 +921,7 @@ class DateTime(datetime.datetime, Date):
return dt.end_of("day")
- def next(self, day_of_week: int | None = None, keep_time: bool = False) -> DateTime:
+ def next(self, day_of_week: WeekDay | None = None, keep_time: bool = False) -> Self:
"""
Modify to the next occurrence of a given day of the week.
If no day_of_week is provided, modify to the next occurrence
@@ -905,13 +931,10 @@ class DateTime(datetime.datetime, Date):
if day_of_week is None:
day_of_week = self.day_of_week
- if day_of_week < SUNDAY or day_of_week > SATURDAY:
+ if day_of_week < WeekDay.MONDAY or day_of_week > WeekDay.SUNDAY:
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:
@@ -920,8 +943,8 @@ class DateTime(datetime.datetime, Date):
return dt
def previous(
- self, day_of_week: int | None = None, keep_time: bool = False
- ) -> DateTime:
+ self, day_of_week: WeekDay | None = None, keep_time: bool = False
+ ) -> Self:
"""
Modify to the previous occurrence of a given day of the week.
If no day_of_week is provided, modify to the previous occurrence
@@ -931,13 +954,10 @@ class DateTime(datetime.datetime, Date):
if day_of_week is None:
day_of_week = self.day_of_week
- if day_of_week < SUNDAY or day_of_week > SATURDAY:
+ if day_of_week < WeekDay.MONDAY or day_of_week > WeekDay.SUNDAY:
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:
@@ -945,35 +965,37 @@ class DateTime(datetime.datetime, Date):
return dt
- def first_of(self, unit: str, day_of_week: int | None = None) -> DateTime:
+ def first_of(self, unit: str, day_of_week: WeekDay | None = None) -> Self:
"""
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.
"""
if unit not in ["month", "quarter", "year"]:
raise ValueError(f'Invalid unit "{unit}" for first_of()')
- return cast(DateTime, getattr(self, f"_first_of_{unit}")(day_of_week))
+ return cast("Self", getattr(self, f"_first_of_{unit}")(day_of_week))
- def last_of(self, unit: str, day_of_week: int | None = None) -> DateTime:
+ def last_of(self, unit: str, day_of_week: WeekDay | None = None) -> Self:
"""
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.
"""
if unit not in ["month", "quarter", "year"]:
raise ValueError(f'Invalid unit "{unit}" for first_of()')
- return cast(DateTime, getattr(self, f"_last_of_{unit}")(day_of_week))
+ return cast("Self", getattr(self, f"_last_of_{unit}")(day_of_week))
- def nth_of(self, unit: str, nth: int, day_of_week: int) -> DateTime:
+ def nth_of(self, unit: str, nth: int, day_of_week: WeekDay) -> Self:
"""
Returns a new instance set to the given occurrence
of a given day of the week in the current unit.
@@ -986,18 +1008,16 @@ class DateTime(datetime.datetime, Date):
if unit not in ["month", "quarter", "year"]:
raise ValueError(f'Invalid unit "{unit}" for first_of()')
- dt = cast(
- Optional[DateTime], getattr(self, f"_nth_of_{unit}")(nth, day_of_week)
- )
+ dt = cast(Optional["Self"], getattr(self, f"_nth_of_{unit}")(nth, day_of_week))
if not dt:
raise PendulumException(
- f"Unable to find occurence {nth}"
- f" of {self._days[day_of_week]} in {unit}"
+ f"Unable to find occurrence {nth}"
+ f" of {WeekDay(day_of_week).name.capitalize()} in {unit}"
)
return dt
- def _first_of_month(self, day_of_week: int | None = None) -> DateTime:
+ def _first_of_month(self, day_of_week: WeekDay | None = None) -> Self:
"""
Modify to the first occurrence of a given day of the week
in the current month. If no day_of_week is provided,
@@ -1011,7 +1031,7 @@ class DateTime(datetime.datetime, Date):
month = calendar.monthcalendar(dt.year, dt.month)
- calendar_day = (day_of_week - 1) % 7
+ calendar_day = day_of_week
if month[0][calendar_day] > 0:
day_of_month = month[0][calendar_day]
@@ -1020,7 +1040,7 @@ class DateTime(datetime.datetime, Date):
return dt.set(day=day_of_month)
- def _last_of_month(self, day_of_week: int | None = None) -> DateTime:
+ def _last_of_month(self, day_of_week: WeekDay | None = None) -> Self:
"""
Modify to the last occurrence of a given day of the week
in the current month. If no day_of_week is provided,
@@ -1034,7 +1054,7 @@ class DateTime(datetime.datetime, Date):
month = calendar.monthcalendar(dt.year, dt.month)
- calendar_day = (day_of_week - 1) % 7
+ calendar_day = day_of_week
if month[-1][calendar_day] > 0:
day_of_month = month[-1][calendar_day]
@@ -1044,8 +1064,8 @@ class DateTime(datetime.datetime, Date):
return dt.set(day=day_of_month)
def _nth_of_month(
- self, nth: int, day_of_week: int | None = None
- ) -> DateTime | None:
+ self, nth: int, day_of_week: WeekDay | None = None
+ ) -> Self | None:
"""
Modify to the given occurrence of a given day of the week
in the current month. If the calculated occurrence is outside,
@@ -1066,7 +1086,7 @@ class DateTime(datetime.datetime, Date):
return None
- def _first_of_quarter(self, day_of_week: int | None = None) -> DateTime:
+ def _first_of_quarter(self, day_of_week: WeekDay | None = None) -> Self:
"""
Modify to the first occurrence of a given day of the week
in the current quarter. If no day_of_week is provided,
@@ -1077,7 +1097,7 @@ class DateTime(datetime.datetime, Date):
"month", day_of_week
)
- def _last_of_quarter(self, day_of_week: int | None = None) -> DateTime:
+ def _last_of_quarter(self, day_of_week: WeekDay | None = None) -> Self:
"""
Modify to the last occurrence of a given day of the week
in the current quarter. If no day_of_week is provided,
@@ -1087,8 +1107,8 @@ class DateTime(datetime.datetime, Date):
return self.on(self.year, self.quarter * 3, 1).last_of("month", day_of_week)
def _nth_of_quarter(
- self, nth: int, day_of_week: int | None = None
- ) -> DateTime | None:
+ self, nth: int, day_of_week: WeekDay | None = None
+ ) -> Self | None:
"""
Modify to the given occurrence of a given day of the week
in the current quarter. If the calculated occurrence is outside,
@@ -1111,7 +1131,7 @@ class DateTime(datetime.datetime, Date):
return self.on(self.year, dt.month, dt.day).start_of("day")
- def _first_of_year(self, day_of_week: int | None = None) -> DateTime:
+ def _first_of_year(self, day_of_week: WeekDay | None = None) -> Self:
"""
Modify to the first occurrence of a given day of the week
in the current year. If no day_of_week is provided,
@@ -1120,7 +1140,7 @@ class DateTime(datetime.datetime, Date):
"""
return self.set(month=1).first_of("month", day_of_week)
- def _last_of_year(self, day_of_week: int | None = None) -> DateTime:
+ def _last_of_year(self, day_of_week: WeekDay | None = None) -> Self:
"""
Modify to the last occurrence of a given day of the week
in the current year. If no day_of_week is provided,
@@ -1129,7 +1149,7 @@ class DateTime(datetime.datetime, Date):
"""
return self.set(month=MONTHS_PER_YEAR).last_of("month", day_of_week)
- def _nth_of_year(self, nth: int, day_of_week: int | None = None) -> DateTime | None:
+ def _nth_of_year(self, nth: int, day_of_week: WeekDay | None = None) -> Self | None:
"""
Modify to the given occurrence of a given day of the week
in the current year. If the calculated occurrence is outside,
@@ -1152,7 +1172,7 @@ class DateTime(datetime.datetime, Date):
def average( # type: ignore[override]
self, dt: datetime.datetime | None = None
- ) -> DateTime:
+ ) -> Self:
"""
Modify the current instance to the average
of a given instance (default now) and the current instance.
@@ -1166,16 +1186,14 @@ class DateTime(datetime.datetime, Date):
)
@overload # type: ignore[override]
- def __sub__(self, other: datetime.timedelta) -> DateTime:
+ def __sub__(self, other: datetime.timedelta) -> Self:
...
@overload
def __sub__(self, other: DateTime) -> Interval:
...
- def __sub__(
- self, other: datetime.datetime | datetime.timedelta
- ) -> DateTime | Interval:
+ def __sub__(self, other: datetime.datetime | datetime.timedelta) -> Self | Interval:
if isinstance(other, datetime.timedelta):
return self._subtract_timedelta(other)
@@ -1194,7 +1212,7 @@ class DateTime(datetime.datetime, Date):
other.microsecond,
)
else:
- other = pendulum.instance(other)
+ other = self.instance(other)
return other.diff(self, False)
@@ -1214,46 +1232,38 @@ class DateTime(datetime.datetime, Date):
other.microsecond,
)
else:
- other = pendulum.instance(other)
+ other = self.instance(other)
return self.diff(other, False)
- def __add__(self, other: datetime.timedelta) -> DateTime:
+ def __add__(self, other: datetime.timedelta) -> Self:
if not isinstance(other, datetime.timedelta):
return NotImplemented
- if PY38:
- # 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]
- if caller == "astimezone":
- return cast(DateTime, super().__add__(other))
+ caller = traceback.extract_stack(limit=2)[0].name
+ if caller == "astimezone":
+ return super().__add__(other)
return self._add_timedelta_(other)
- def __radd__(self, other: datetime.timedelta) -> DateTime:
+ def __radd__(self, other: datetime.timedelta) -> Self:
return self.__add__(other)
# Native methods override
@classmethod
- def fromtimestamp(cls, t: float, tz: datetime.tzinfo | None = None) -> DateTime:
+ def fromtimestamp(cls, t: float, tz: datetime.tzinfo | None = None) -> Self:
tzinfo = pendulum._safe_timezone(tz)
- return pendulum.instance(
- datetime.datetime.fromtimestamp(t, tz=tzinfo), tz=tzinfo
- )
+ return cls.instance(datetime.datetime.fromtimestamp(t, tz=tzinfo), tz=tzinfo)
@classmethod
- def utcfromtimestamp(cls, t: float) -> DateTime:
- return pendulum.instance(datetime.datetime.utcfromtimestamp(t), tz=None)
+ def utcfromtimestamp(cls, t: float) -> Self:
+ return cls.instance(datetime.datetime.utcfromtimestamp(t), tz=None)
@classmethod
- def fromordinal(cls, n: int) -> DateTime:
- return pendulum.instance(datetime.datetime.fromordinal(n), tz=None)
+ def fromordinal(cls, n: int) -> Self:
+ return cls.instance(datetime.datetime.fromordinal(n), tz=None)
@classmethod
def combine(
@@ -1261,10 +1271,10 @@ class DateTime(datetime.datetime, Date):
date: datetime.date,
time: datetime.time,
tzinfo: datetime.tzinfo | None = None,
- ) -> DateTime:
- return pendulum.instance(datetime.datetime.combine(date, time), tz=tzinfo)
+ ) -> Self:
+ return cls.instance(datetime.datetime.combine(date, time), tz=tzinfo)
- def astimezone(self, tz: datetime.tzinfo | None = None) -> DateTime:
+ def astimezone(self, tz: datetime.tzinfo | None = None) -> Self:
dt = super().astimezone(tz)
return self.__class__(
@@ -1281,16 +1291,16 @@ class DateTime(datetime.datetime, Date):
def replace(
self,
- year: int | None = None,
- month: int | None = None,
- day: int | None = None,
- hour: int | None = None,
- minute: int | None = None,
- second: int | None = None,
- microsecond: int | None = None,
+ year: SupportsIndex | None = None,
+ month: SupportsIndex | None = None,
+ day: SupportsIndex | None = None,
+ hour: SupportsIndex | None = None,
+ minute: SupportsIndex | None = None,
+ second: SupportsIndex | None = None,
+ microsecond: SupportsIndex | None = None,
tzinfo: bool | datetime.tzinfo | Literal[True] | None = True,
fold: int | None = None,
- ) -> DateTime:
+ ) -> Self:
if year is None:
year = self.year
if month is None:
@@ -1313,7 +1323,7 @@ class DateTime(datetime.datetime, Date):
if tzinfo is not None:
tzinfo = pendulum._safe_timezone(tzinfo)
- return DateTime.create(
+ return self.__class__.create(
year,
month,
day,
@@ -1325,11 +1335,11 @@ class DateTime(datetime.datetime, Date):
fold=fold,
)
- def __getnewargs__(self) -> tuple[DateTime]:
+ def __getnewargs__(self) -> tuple[Self]:
return (self,)
def _getstate(
- self, protocol: int = 3
+ self, protocol: SupportsIndex = 3
) -> tuple[int, int, int, int, int, int, int, datetime.tzinfo | None]:
return (
self.year,
@@ -1345,17 +1355,32 @@ class DateTime(datetime.datetime, Date):
def __reduce__(
self,
) -> tuple[
- type[DateTime], tuple[int, int, int, int, int, int, int, datetime.tzinfo | None]
+ type[Self],
+ tuple[int, int, int, int, int, int, int, datetime.tzinfo | None],
]:
return self.__reduce_ex__(2)
- def __reduce_ex__( # type: ignore[override]
- self, protocol: int
+ def __reduce_ex__(
+ self, protocol: SupportsIndex
) -> tuple[
- type[DateTime], tuple[int, int, int, int, int, int, int, datetime.tzinfo | None]
+ type[Self],
+ tuple[int, int, int, int, int, int, int, datetime.tzinfo | None],
]:
return self.__class__, self._getstate(protocol)
+ def __deepcopy__(self, _: dict[int, Self]) -> Self:
+ return self.__class__(
+ self.year,
+ self.month,
+ self.day,
+ self.hour,
+ self.minute,
+ self.second,
+ self.microsecond,
+ tzinfo=self.tz,
+ fold=self.fold,
+ )
+
def _cmp(self, other: datetime.datetime, **kwargs: Any) -> int:
# Fix for pypy which compares using this method
# which would lead to infinite recursion if we didn't override
@@ -1374,8 +1399,6 @@ class DateTime(datetime.datetime, Date):
return 0 if dt == other else 1 if dt > other else -1
-DateTime.min: DateTime = DateTime(1, 1, 1, 0, 0, tzinfo=UTC) # type: ignore[misc]
-DateTime.max: DateTime = DateTime( # type: ignore[misc]
- 9999, 12, 31, 23, 59, 59, 999999, tzinfo=UTC
-)
-DateTime.EPOCH: DateTime = DateTime(1970, 1, 1, tzinfo=UTC) # type: ignore[misc]
+DateTime.min = DateTime(1, 1, 1, 0, 0, tzinfo=UTC)
+DateTime.max = DateTime(9999, 12, 31, 23, 59, 59, 999999, tzinfo=UTC)
+DateTime.EPOCH = DateTime(1970, 1, 1, tzinfo=UTC)
diff --git a/src/pendulum/day.py b/src/pendulum/day.py
new file mode 100644
index 0000000..7bfffca
--- /dev/null
+++ b/src/pendulum/day.py
@@ -0,0 +1,13 @@
+from __future__ import annotations
+
+from enum import IntEnum
+
+
+class WeekDay(IntEnum):
+ MONDAY = 0
+ TUESDAY = 1
+ WEDNESDAY = 2
+ THURSDAY = 3
+ FRIDAY = 4
+ SATURDAY = 5
+ SUNDAY = 6
diff --git a/pendulum/duration.py b/src/pendulum/duration.py
index a3a68b1..a4875fc 100644
--- a/pendulum/duration.py
+++ b/src/pendulum/duration.py
@@ -1,6 +1,7 @@
from __future__ import annotations
from datetime import timedelta
+from typing import TYPE_CHECKING
from typing import cast
from typing import overload
@@ -13,6 +14,10 @@ from pendulum.constants import US_PER_SECOND
from pendulum.utils._compat import PYPY
+if TYPE_CHECKING:
+ from typing_extensions import Self
+
+
def _divide_and_round(a: float, b: float) -> int:
"""divide a by b and round result to the nearest integer
@@ -74,7 +79,7 @@ class Duration(timedelta):
weeks: float = 0,
years: float = 0,
months: float = 0,
- ) -> Duration:
+ ) -> Self:
if not isinstance(years, int) or not isinstance(months, int):
raise ValueError("Float year and months are not supported")
@@ -107,6 +112,17 @@ class Duration(timedelta):
self._months = months
self._years = years
+ self._signature = { # type: ignore[attr-defined]
+ "years": years,
+ "months": months,
+ "weeks": weeks,
+ "days": days,
+ "hours": hours,
+ "minutes": minutes,
+ "seconds": seconds,
+ "microseconds": microseconds + milliseconds * 1000,
+ }
+
return self
def total_minutes(self) -> float:
@@ -231,7 +247,7 @@ class Duration(timedelta):
:param locale: The locale to use. Defaults to current locale.
:param separator: The separator to use between each unit
"""
- periods = [
+ intervals = [
("year", self.years),
("month", self.months),
("week", self.weeks),
@@ -247,13 +263,13 @@ class Duration(timedelta):
loaded_locale = pendulum.locale(locale)
parts = []
- for period in periods:
- unit, period_count = period
- if abs(period_count) > 0:
+ for interval in intervals:
+ unit, interval_count = interval
+ if abs(interval_count) > 0:
translation = loaded_locale.translation(
- f"units.{unit}.{loaded_locale.plural(abs(period_count))}"
+ f"units.{unit}.{loaded_locale.plural(abs(interval_count))}"
)
- parts.append(translation.format(period_count))
+ parts.append(translation.format(interval_count))
if not parts:
count: int | str = 0
@@ -313,7 +329,7 @@ class Duration(timedelta):
return rep.replace(", )", ")")
- def __add__(self, other: timedelta) -> Duration:
+ def __add__(self, other: timedelta) -> Self:
if isinstance(other, timedelta):
return self.__class__(seconds=self.total_seconds() + other.total_seconds())
@@ -321,13 +337,13 @@ class Duration(timedelta):
__radd__ = __add__
- def __sub__(self, other: timedelta) -> Duration:
+ def __sub__(self, other: timedelta) -> Self:
if isinstance(other, timedelta):
return self.__class__(seconds=self.total_seconds() - other.total_seconds())
return NotImplemented
- def __neg__(self) -> Duration:
+ def __neg__(self) -> Self:
return self.__class__(
years=-self._years,
months=-self._months,
@@ -340,7 +356,7 @@ class Duration(timedelta):
def _to_microseconds(self) -> int:
return (self._days * (24 * 3600) + self._seconds) * 1000000 + self._microseconds
- def __mul__(self, other: int | float) -> Duration:
+ def __mul__(self, other: int | float) -> Self:
if isinstance(other, int):
return self.__class__(
years=self._years * other,
@@ -363,7 +379,7 @@ class Duration(timedelta):
...
@overload
- def __floordiv__(self, other: int) -> Duration:
+ def __floordiv__(self, other: int) -> Self:
...
def __floordiv__(self, other: int | timedelta) -> int | Duration:
@@ -372,7 +388,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__(
@@ -388,16 +406,18 @@ class Duration(timedelta):
...
@overload
- def __truediv__(self, other: float) -> Duration:
+ def __truediv__(self, other: float) -> Self:
...
- def __truediv__(self, other: int | float | timedelta) -> Duration | float:
+ def __truediv__(self, other: int | float | timedelta) -> Self | float:
if not isinstance(other, (int, float, timedelta)):
return NotImplemented
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__(
@@ -421,9 +441,9 @@ class Duration(timedelta):
__div__ = __floordiv__
- def __mod__(self, other: timedelta) -> Duration:
+ 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)
@@ -431,12 +451,26 @@ 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]
+ )
return q, self.__class__(0, 0, r)
return NotImplemented
+ def __deepcopy__(self, _: dict[int, Self]) -> Self:
+ return self.__class__(
+ days=self.remaining_days,
+ seconds=self.remaining_seconds,
+ microseconds=self.microseconds,
+ minutes=self.minutes,
+ hours=self.hours,
+ years=self.years,
+ months=self.months,
+ )
+
Duration.min = Duration(days=-999999999)
Duration.max = Duration(
@@ -480,12 +514,9 @@ class AbsoluteDuration(Duration):
total = abs(self._total)
self._microseconds = round(total % 1 * 1e6)
- self._seconds = int(total) % SECONDS_PER_DAY
-
- days = int(total) // SECONDS_PER_DAY
+ days, self._seconds = divmod(int(total), SECONDS_PER_DAY)
self._days = abs(days + years * 365 + months * 30)
- self._remaining_days = days % 7
- self._weeks = days // 7
+ self._weeks, self._remaining_days = divmod(days, 7)
self._months = abs(months)
self._years = abs(years)
diff --git a/src/pendulum/exceptions.py b/src/pendulum/exceptions.py
new file mode 100644
index 0000000..1687211
--- /dev/null
+++ b/src/pendulum/exceptions.py
@@ -0,0 +1,13 @@
+from __future__ import annotations
+
+from pendulum.parsing.exceptions import ParserError
+
+
+class PendulumException(Exception):
+ pass
+
+
+__all__ = [
+ "ParserError",
+ "PendulumException",
+]
diff --git a/pendulum/formatting/__init__.py b/src/pendulum/formatting/__init__.py
index 975c409..0c6e725 100644
--- a/pendulum/formatting/__init__.py
+++ b/src/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/src/pendulum/formatting/difference_formatter.py
index dad219d..588c072 100644
--- a/pendulum/formatting/difference_formatter.py
+++ b/src/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/src/pendulum/formatting/formatter.py
index f91d5d9..ee04063 100644
--- a/pendulum/formatting/formatter.py
+++ b/src/pendulum/formatting/formatter.py
@@ -6,6 +6,7 @@ import re
from typing import TYPE_CHECKING
from typing import Any
from typing import Callable
+from typing import ClassVar
from typing import Match
from typing import Sequence
from typing import cast
@@ -14,6 +15,7 @@ import pendulum
from pendulum.locales.locale import Locale
+
if TYPE_CHECKING:
from pendulum import Timezone
@@ -47,7 +49,7 @@ class Formatter:
r"\[([^\[]*)\]|\\(.)|"
"("
"Mo|MM?M?M?"
- "|Do|DDDo|DD?D?D?|ddd?d?|do?"
+ "|Do|DDDo|DD?D?D?|ddd?d?|do?|eo?"
"|E{1,4}"
"|w[o|w]?|W[o|W]?|Qo?"
"|YYYY|YY|Y"
@@ -65,7 +67,9 @@ class Formatter:
_FROM_FORMAT_RE: re.Pattern[str] = re.compile(r"(?<!\\\[)" + _TOKENS + r"(?!\\\])")
- _LOCALIZABLE_TOKENS: dict[str, str | Callable[[Locale], Sequence[str]] | None] = {
+ _LOCALIZABLE_TOKENS: ClassVar[
+ dict[str, str | Callable[[Locale], Sequence[str]] | None]
+ ] = {
"Qo": None,
"MMMM": "months.wide",
"MMM": "months.abbreviated",
@@ -78,6 +82,8 @@ class Formatter:
"ddd": "days.abbreviated",
"dd": "days.short",
"do": None,
+ "e": None,
+ "eo": None,
"Wo": None,
"wo": None,
"A": lambda locale: (
@@ -90,7 +96,7 @@ class Formatter:
),
}
- _TOKENS_RULES: dict[str, Callable[[pendulum.DateTime], str]] = {
+ _TOKENS_RULES: ClassVar[dict[str, Callable[[pendulum.DateTime], str]]] = {
# Year
"YYYY": lambda dt: f"{dt.year:d}",
"YY": lambda dt: f"{dt.year:d}"[2:],
@@ -107,7 +113,7 @@ class Formatter:
"DDDD": lambda dt: f"{dt.day_of_year:03d}",
"DDD": lambda dt: f"{dt.day_of_year:d}",
# Day of Week
- "d": lambda dt: f"{dt.day_of_week:d}",
+ "d": lambda dt: f"{(dt.day_of_week + 1) % 7:d}",
# Day of ISO Week
"E": lambda dt: f"{dt.isoweekday():d}",
# Hour
@@ -136,7 +142,7 @@ class Formatter:
"z": lambda dt: f'{dt.timezone_name or ""}',
}
- _DATE_FORMATS = {
+ _DATE_FORMATS: ClassVar[dict[str, str]] = {
"LTS": "formats.time.full",
"LT": "formats.time.short",
"L": "formats.date.short",
@@ -145,7 +151,7 @@ class Formatter:
"LLLL": "formats.datetime.full",
}
- _DEFAULT_DATE_FORMATS = {
+ _DEFAULT_DATE_FORMATS: ClassVar[dict[str, str]] = {
"LTS": "h:mm:ss A",
"LT": "h:mm A",
"L": "MM/DD/YYYY",
@@ -154,7 +160,7 @@ class Formatter:
"LLLL": "dddd, MMMM D, YYYY h:mm A",
}
- _REGEX_TOKENS: dict[str, str | Sequence[str] | None] = {
+ _REGEX_TOKENS: ClassVar[dict[str, str | Sequence[str] | None]] = {
"Y": _MATCH_SIGNED,
"YY": (_MATCH_1_TO_2, _MATCH_2),
"YYYY": (_MATCH_1_TO_4, _MATCH_4),
@@ -172,6 +178,7 @@ class Formatter:
"ddd": _MATCH_WORD,
"dd": _MATCH_WORD,
"d": _MATCH_1,
+ "e": _MATCH_1,
"E": _MATCH_1,
"Do": None,
"H": _MATCH_1_TO_2,
@@ -195,7 +202,7 @@ class Formatter:
"z": _MATCH_TIMEZONE,
}
- _PARSE_TOKENS: dict[str, Callable[[str], Any]] = {
+ _PARSE_TOKENS: ClassVar[dict[str, Callable[[str], Any]]] = {
"YYYY": lambda year: int(year),
"YY": lambda year: int(year),
"Q": lambda quarter: int(quarter),
@@ -210,8 +217,8 @@ class Formatter:
"dddd": lambda weekday: weekday,
"ddd": lambda weekday: weekday,
"dd": lambda weekday: weekday,
- "d": lambda weekday: int(weekday) % 7,
- "E": lambda weekday: int(weekday),
+ "d": lambda weekday: int(weekday),
+ "E": lambda weekday: int(weekday) - 1,
"HH": lambda hour: int(hour),
"H": lambda hour: int(hour),
"hh": lambda hour: int(hour),
@@ -287,10 +294,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)
@@ -317,14 +321,19 @@ class Formatter:
return cast(str, locale.get("translations.days.short")[dt.day_of_week])
elif token == "ddd":
return cast(
- str, locale.get("translations.days.abbreviated")[dt.day_of_week]
+ str,
+ locale.get("translations.days.abbreviated")[dt.day_of_week],
)
elif token == "dddd":
return cast(str, locale.get("translations.days.wide")[dt.day_of_week])
+ elif token == "e":
+ first_day = cast(int, locale.get("translations.week_data.first_day"))
+
+ return str((dt.day_of_week % 7 - first_day) % 7)
elif token == "Do":
return locale.ordinalize(dt.day)
elif token == "do":
- return locale.ordinalize(dt.day_of_week)
+ return locale.ordinalize((dt.day_of_week + 1) % 7)
elif token == "Mo":
return locale.ordinalize(dt.month)
elif token == "Qo":
@@ -333,6 +342,10 @@ class Formatter:
return locale.ordinalize(dt.week_of_year)
elif token == "DDDo":
return locale.ordinalize(dt.day_of_year)
+ elif token == "eo":
+ first_day = cast(int, locale.get("translations.week_data.first_day"))
+
+ return locale.ordinalize((dt.day_of_week % 7 - first_day) % 7 + 1)
elif token == "A":
key = "translations.day_periods"
if dt.hour >= 12:
@@ -395,9 +408,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)
@@ -448,7 +460,7 @@ class Formatter:
if parsed["quarter"] is not None:
if validated["year"] is not None:
- dt = pendulum.datetime(validated["year"], 1, 1)
+ dt = pendulum.datetime(cast(int, validated["year"]), 1, 1)
else:
dt = now
@@ -476,8 +488,8 @@ class Formatter:
if parsed["day_of_week"] is not None:
dt = pendulum.datetime(
cast(int, validated["year"]),
- validated["month"] or now.month,
- validated["day"] or now.day,
+ cast(int, validated["month"]) or now.month,
+ cast(int, validated["day"]) or now.day,
)
dt = dt.start_of("week").subtract(days=1)
dt = dt.next(parsed["day_of_week"])
@@ -502,9 +514,9 @@ class Formatter:
raise ValueError("Invalid date")
pm = parsed["meridiem"] == "pm"
- validated["hour"] %= 12
+ validated["hour"] %= 12 # type: ignore[operator]
if pm:
- validated["hour"] += 12
+ validated["hour"] += 12 # type: ignore[operator]
if validated["month"] is None:
if parsed["year"] is not None:
@@ -550,7 +562,10 @@ class Formatter:
if "Y" in token:
if token == "YY":
- parsed_token = now.year // 100 * 100 + parsed_token
+ if parsed_token <= 68:
+ parsed_token += 2000
+ else:
+ parsed_token += 1900
parsed["year"] = parsed_token
elif token == "Q":
diff --git a/pendulum/helpers.py b/src/pendulum/helpers.py
index 13b7f22..5d5fe8e 100644
--- a/pendulum/helpers.py
+++ b/src/pendulum/helpers.py
@@ -14,9 +14,11 @@ from typing import overload
import pendulum
from pendulum.constants import DAYS_PER_MONTHS
+from pendulum.day import WeekDay
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,34 +29,31 @@ _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()
- from pendulum._extensions._helpers import PreciseDiff
- from pendulum._extensions._helpers import days_in_year
- from pendulum._extensions._helpers import is_leap
- from pendulum._extensions._helpers import is_long_year
- from pendulum._extensions._helpers import local_time
- from pendulum._extensions._helpers import precise_diff
- from pendulum._extensions._helpers import timestamp
- from pendulum._extensions._helpers import week_day
+ from pendulum._pendulum import PreciseDiff
+ from pendulum._pendulum import days_in_year
+ from pendulum._pendulum import is_leap
+ from pendulum._pendulum import is_long_year
+ from pendulum._pendulum import local_time
+ from pendulum._pendulum import precise_diff
+ from pendulum._pendulum import week_day
except ImportError:
- from pendulum._extensions.helpers import PreciseDiff # type: ignore[misc]
- from pendulum._extensions.helpers import days_in_year
- from pendulum._extensions.helpers import is_leap
- from pendulum._extensions.helpers import is_long_year
- from pendulum._extensions.helpers import local_time
- from pendulum._extensions.helpers import precise_diff # type: ignore[misc]
- from pendulum._extensions.helpers import timestamp
- from pendulum._extensions.helpers import week_day
+ from pendulum._helpers import PreciseDiff # type: ignore[assignment]
+ from pendulum._helpers import days_in_year
+ from pendulum._helpers import is_leap
+ from pendulum._helpers import is_long_year
+ from pendulum._helpers import local_time
+ from pendulum._helpers import precise_diff # type: ignore[assignment]
+ from pendulum._helpers import week_day
difference_formatter = DifferenceFormatter()
@overload
def add_duration(
- dt: datetime,
+ dt: _DT,
years: int = 0,
months: int = 0,
weeks: int = 0,
@@ -63,18 +62,18 @@ def add_duration(
minutes: int = 0,
seconds: float = 0,
microseconds: int = 0,
-) -> datetime:
+) -> _DT:
...
@overload
def add_duration(
- dt: date,
+ dt: _D,
years: int = 0,
months: int = 0,
weeks: int = 0,
days: int = 0,
-) -> date:
+) -> _D:
pass
@@ -190,16 +189,16 @@ def get_locale() -> str:
return pendulum._LOCALE
-def week_starts_at(wday: int) -> None:
- if wday < pendulum.SUNDAY or wday > pendulum.SATURDAY:
- raise ValueError("Invalid week day as start of week.")
+def week_starts_at(wday: WeekDay) -> None:
+ if wday < WeekDay.MONDAY or wday > WeekDay.SUNDAY:
+ raise ValueError("Invalid day of week")
pendulum._WEEK_STARTS_AT = wday
-def week_ends_at(wday: int) -> None:
- if wday < pendulum.SUNDAY or wday > pendulum.SATURDAY:
- raise ValueError("Invalid week day as start of week.")
+def week_ends_at(wday: WeekDay) -> None:
+ if wday < WeekDay.MONDAY or wday > WeekDay.SUNDAY:
+ raise ValueError("Invalid day of week")
pendulum._WEEK_ENDS_AT = wday
@@ -211,7 +210,6 @@ __all__ = [
"is_long_year",
"local_time",
"precise_diff",
- "timestamp",
"week_day",
"add_duration",
"format_diff",
diff --git a/pendulum/interval.py b/src/pendulum/interval.py
index f20042b..19c91a6 100644
--- a/pendulum/interval.py
+++ b/src/pendulum/interval.py
@@ -17,16 +17,18 @@ from pendulum.constants import MONTHS_PER_YEAR
from pendulum.duration import Duration
from pendulum.helpers import precise_diff
+
if TYPE_CHECKING:
- from typing import SupportsIndex
+ 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):
"""
- A period of time between two datetimes.
+ An interval of time between two datetimes.
"""
@overload
@@ -35,7 +37,7 @@ class Interval(Duration):
start: pendulum.DateTime | datetime,
end: pendulum.DateTime | datetime,
absolute: bool = False,
- ) -> Interval:
+ ) -> Self:
...
@overload
@@ -44,7 +46,7 @@ class Interval(Duration):
start: pendulum.Date | date,
end: pendulum.Date | date,
absolute: bool = False,
- ) -> Interval:
+ ) -> Self:
...
def __new__(
@@ -52,14 +54,16 @@ class Interval(Duration):
start: pendulum.DateTime | pendulum.Date | datetime | date,
end: pendulum.DateTime | pendulum.Date | datetime | date,
absolute: bool = False,
- ) -> Interval:
+ ) -> Self:
if (
isinstance(start, datetime)
and not isinstance(end, datetime)
or not isinstance(start, datetime)
and isinstance(end, datetime)
):
- raise ValueError("Both start and end of a Period must have the same type")
+ raise ValueError(
+ "Both start and end of an Interval must have the same type"
+ )
if (
isinstance(start, datetime)
@@ -125,7 +129,7 @@ class Interval(Duration):
delta: timedelta = _end - _start # type: ignore[operator]
- return cast(Interval, super().__new__(cls, seconds=delta.total_seconds()))
+ return super().__new__(cls, seconds=delta.total_seconds())
def __init__(
self,
@@ -232,13 +236,13 @@ class Interval(Duration):
def in_years(self) -> int:
"""
- Gives the duration of the Period in full years.
+ Gives the duration of the Interval in full years.
"""
return self.years
def in_months(self) -> int:
"""
- Gives the duration of the Period in full months.
+ Gives the duration of the Interval in full months.
"""
return self.years * MONTHS_PER_YEAR + self.months
@@ -263,9 +267,9 @@ 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 = [
+ intervals = [
("year", self.years),
("month", self.months),
("week", self.weeks),
@@ -276,13 +280,13 @@ class Interval(Duration):
]
loaded_locale: Locale = Locale.load(locale or pendulum.get_locale())
parts = []
- for period in periods:
- unit, period_count = period
- if abs(period_count) > 0:
+ for interval in intervals:
+ unit, interval_count = interval
+ if abs(interval_count) > 0:
translation = loaded_locale.translation(
- f"units.{unit}.{loaded_locale.plural(abs(period_count))}"
+ f"units.{unit}.{loaded_locale.plural(abs(interval_count))}"
)
- parts.append(translation.format(period_count))
+ parts.append(translation.format(interval_count))
if not parts:
count: str | int = 0
@@ -316,9 +320,9 @@ class Interval(Duration):
i += amount
- def as_interval(self) -> Duration:
+ def as_duration(self) -> Duration:
"""
- Return the Period as a Duration.
+ Return the Interval as a Duration.
"""
return Duration(seconds=self.total_seconds())
@@ -330,23 +334,23 @@ class Interval(Duration):
) -> bool:
return self.start <= item <= self.end
- def __add__(self, other: timedelta) -> Duration:
- return self.as_interval().__add__(other)
+ def __add__(self, other: timedelta) -> Duration: # type: ignore[override]
+ return self.as_duration().__add__(other)
- __radd__ = __add__
+ __radd__ = __add__ # type: ignore[assignment]
- def __sub__(self, other: timedelta) -> Duration:
- return self.as_interval().__sub__(other)
+ def __sub__(self, other: timedelta) -> Duration: # type: ignore[override]
+ return self.as_duration().__sub__(other)
- def __neg__(self) -> Interval:
+ def __neg__(self) -> Self:
return self.__class__(self.end, self.start, self._absolute)
- def __mul__(self, other: int | float) -> Duration:
- return self.as_interval().__mul__(other)
+ def __mul__(self, other: int | float) -> Duration: # type: ignore[override]
+ return self.as_duration().__mul__(other)
- __rmul__ = __mul__
+ __rmul__ = __mul__ # type: ignore[assignment]
- @overload
+ @overload # type: ignore[override]
def __floordiv__(self, other: timedelta) -> int:
...
@@ -355,11 +359,11 @@ class Interval(Duration):
...
def __floordiv__(self, other: int | timedelta) -> int | Duration:
- return self.as_interval().__floordiv__(other)
+ return self.as_duration().__floordiv__(other)
__div__ = __floordiv__ # type: ignore[assignment]
- @overload
+ @overload # type: ignore[override]
def __truediv__(self, other: timedelta) -> float:
...
@@ -368,19 +372,19 @@ class Interval(Duration):
...
def __truediv__(self, other: float | timedelta) -> Duration | float:
- return self.as_interval().__truediv__(other)
+ return self.as_duration().__truediv__(other)
- def __mod__(self, other: timedelta) -> Duration:
- return self.as_interval().__mod__(other)
+ def __mod__(self, other: timedelta) -> Duration: # type: ignore[override]
+ return self.as_duration().__mod__(other)
def __divmod__(self, other: timedelta) -> tuple[int, Duration]:
- return self.as_interval().__divmod__(other)
+ return self.as_duration().__divmod__(other)
- def __abs__(self) -> Interval:
+ def __abs__(self) -> Self:
return self.__class__(self.start, self.end, absolute=True)
def __repr__(self) -> str:
- return f"<Period [{self._start} -> {self._end}]>"
+ return f"<Interval [{self._start} -> {self._end}]>"
def __str__(self) -> str:
return self.__repr__()
@@ -413,7 +417,7 @@ class Interval(Duration):
def __reduce__(
self,
) -> tuple[
- type[Interval],
+ type[Self],
tuple[
pendulum.DateTime | pendulum.Date | datetime | date,
pendulum.DateTime | pendulum.Date | datetime | date,
@@ -425,7 +429,7 @@ class Interval(Duration):
def __reduce_ex__(
self, protocol: SupportsIndex
) -> tuple[
- type[Interval],
+ type[Self],
tuple[
pendulum.DateTime | pendulum.Date | datetime | date,
pendulum.DateTime | pendulum.Date | datetime | date,
@@ -445,4 +449,7 @@ class Interval(Duration):
other._absolute,
)
else:
- return self.as_interval() == other
+ return self.as_duration() == other
+
+ def __ne__(self, other: object) -> bool:
+ return not self.__eq__(other)
diff --git a/pendulum/_extensions/__init__.py b/src/pendulum/locales/__init__.py
index e69de29..e69de29 100644
--- a/pendulum/_extensions/__init__.py
+++ b/src/pendulum/locales/__init__.py
diff --git a/pendulum/locales/__init__.py b/src/pendulum/locales/cs/__init__.py
index e69de29..e69de29 100644
--- a/pendulum/locales/__init__.py
+++ b/src/pendulum/locales/cs/__init__.py
diff --git a/pendulum/locales/cs/custom.py b/src/pendulum/locales/cs/custom.py
index 5f66b69..c909f32 100644
--- a/pendulum/locales/cs/custom.py
+++ b/src/pendulum/locales/cs/custom.py
@@ -1,6 +1,8 @@
"""
cs custom locale file.
"""
+from __future__ import annotations
+
translations = {
"units": {"few_second": "pár vteřin"},
diff --git a/pendulum/locales/cs/locale.py b/src/pendulum/locales/cs/locale.py
index 2c51c78..b44d0f7 100644
--- a/pendulum/locales/cs/locale.py
+++ b/src/pendulum/locales/cs/locale.py
@@ -1,4 +1,6 @@
-from .custom import translations as custom_translations
+from __future__ import annotations
+
+from pendulum.locales.cs.custom import translations as custom_translations
"""
@@ -20,40 +22,40 @@ locale = {
"translations": {
"days": {
"abbreviated": {
- 0: "ne",
- 1: "po",
- 2: "út",
- 3: "st",
- 4: "čt",
- 5: "pá",
- 6: "so",
+ 0: "po",
+ 1: "út",
+ 2: "st",
+ 3: "čt",
+ 4: "pá",
+ 5: "so",
+ 6: "ne",
},
"narrow": {
- 0: "N",
- 1: "P",
- 2: "Ú",
- 3: "S",
- 4: "Č",
- 5: "P",
- 6: "S",
+ 0: "P",
+ 1: "Ú",
+ 2: "S",
+ 3: "Č",
+ 4: "P",
+ 5: "S",
+ 6: "N",
},
"short": {
- 0: "ne",
- 1: "po",
- 2: "út",
- 3: "st",
- 4: "čt",
- 5: "pá",
- 6: "so",
+ 0: "po",
+ 1: "út",
+ 2: "st",
+ 3: "čt",
+ 4: "pá",
+ 5: "so",
+ 6: "ne",
},
"wide": {
- 0: "neděle",
- 1: "pondělí",
- 2: "úterý",
- 3: "středa",
- 4: "čtvrtek",
- 5: "pátek",
- 6: "sobota",
+ 0: "pondělí",
+ 1: "úterý",
+ 2: "středa",
+ 3: "čtvrtek",
+ 4: "pátek",
+ 5: "sobota",
+ 6: "neděle",
},
},
"months": {
@@ -261,6 +263,12 @@ locale = {
"evening1": "večer",
"night1": "v noci",
},
+ "week_data": {
+ "min_days": 1,
+ "first_day": 0,
+ "weekend_start": 5,
+ "weekend_end": 6,
+ },
},
"custom": custom_translations,
}
diff --git a/pendulum/locales/cs/__init__.py b/src/pendulum/locales/da/__init__.py
index e69de29..e69de29 100644
--- a/pendulum/locales/cs/__init__.py
+++ b/src/pendulum/locales/da/__init__.py
diff --git a/pendulum/locales/da/custom.py b/src/pendulum/locales/da/custom.py
index c62ab83..57e68f4 100644
--- a/pendulum/locales/da/custom.py
+++ b/src/pendulum/locales/da/custom.py
@@ -1,6 +1,8 @@
"""
da custom locale file.
"""
+from __future__ import annotations
+
translations = {
# Relative time
diff --git a/pendulum/locales/da/locale.py b/src/pendulum/locales/da/locale.py
index 936af3a..2385de5 100644
--- a/pendulum/locales/da/locale.py
+++ b/src/pendulum/locales/da/locale.py
@@ -1,4 +1,6 @@
-from .custom import translations as custom_translations
+from __future__ import annotations
+
+from pendulum.locales.da.custom import translations as custom_translations
"""
@@ -19,24 +21,24 @@ locale = {
"translations": {
"days": {
"abbreviated": {
- 0: "søn.",
- 1: "man.",
- 2: "tir.",
- 3: "ons.",
- 4: "tor.",
- 5: "fre.",
- 6: "lør.",
+ 0: "man.",
+ 1: "tir.",
+ 2: "ons.",
+ 3: "tor.",
+ 4: "fre.",
+ 5: "lør.",
+ 6: "søn.",
},
- "narrow": {0: "S", 1: "M", 2: "T", 3: "O", 4: "T", 5: "F", 6: "L"},
- "short": {0: "sø", 1: "ma", 2: "ti", 3: "on", 4: "to", 5: "fr", 6: "lø"},
+ "narrow": {0: "M", 1: "T", 2: "O", 3: "T", 4: "F", 5: "L", 6: "S"},
+ "short": {0: "ma", 1: "ti", 2: "on", 3: "to", 4: "fr", 5: "lø", 6: "sø"},
"wide": {
- 0: "søndag",
- 1: "mandag",
- 2: "tirsdag",
- 3: "onsdag",
- 4: "torsdag",
- 5: "fredag",
- 6: "lørdag",
+ 0: "mandag",
+ 1: "tirsdag",
+ 2: "onsdag",
+ 3: "torsdag",
+ 4: "fredag",
+ 5: "lørdag",
+ 6: "søndag",
},
},
"months": {
@@ -142,6 +144,12 @@ locale = {
"evening1": "om aftenen",
"night1": "om natten",
},
+ "week_data": {
+ "min_days": 1,
+ "first_day": 0,
+ "weekend_start": 5,
+ "weekend_end": 6,
+ },
},
"custom": custom_translations,
}
diff --git a/pendulum/locales/da/__init__.py b/src/pendulum/locales/de/__init__.py
index e69de29..e69de29 100644
--- a/pendulum/locales/da/__init__.py
+++ b/src/pendulum/locales/de/__init__.py
diff --git a/pendulum/locales/de/custom.py b/src/pendulum/locales/de/custom.py
index a19a8e1..8ef06cc 100644
--- a/pendulum/locales/de/custom.py
+++ b/src/pendulum/locales/de/custom.py
@@ -1,6 +1,8 @@
"""
de custom locale file.
"""
+from __future__ import annotations
+
translations = {
# Relative time
diff --git a/pendulum/locales/de/locale.py b/src/pendulum/locales/de/locale.py
index 94d2ff1..7781c3f 100644
--- a/pendulum/locales/de/locale.py
+++ b/src/pendulum/locales/de/locale.py
@@ -1,4 +1,6 @@
-from .custom import translations as custom_translations
+from __future__ import annotations
+
+from pendulum.locales.de.custom import translations as custom_translations
"""
@@ -16,32 +18,32 @@ locale = {
"translations": {
"days": {
"abbreviated": {
- 0: "So.",
- 1: "Mo.",
- 2: "Di.",
- 3: "Mi.",
- 4: "Do.",
- 5: "Fr.",
- 6: "Sa.",
+ 0: "Mo.",
+ 1: "Di.",
+ 2: "Mi.",
+ 3: "Do.",
+ 4: "Fr.",
+ 5: "Sa.",
+ 6: "So.",
},
- "narrow": {0: "S", 1: "M", 2: "D", 3: "M", 4: "D", 5: "F", 6: "S"},
+ "narrow": {0: "M", 1: "D", 2: "M", 3: "D", 4: "F", 5: "S", 6: "S"},
"short": {
- 0: "So.",
- 1: "Mo.",
- 2: "Di.",
- 3: "Mi.",
- 4: "Do.",
- 5: "Fr.",
- 6: "Sa.",
+ 0: "Mo.",
+ 1: "Di.",
+ 2: "Mi.",
+ 3: "Do.",
+ 4: "Fr.",
+ 5: "Sa.",
+ 6: "So.",
},
"wide": {
- 0: "Sonntag",
- 1: "Montag",
- 2: "Dienstag",
- 3: "Mittwoch",
- 4: "Donnerstag",
- 5: "Freitag",
- 6: "Samstag",
+ 0: "Montag",
+ 1: "Dienstag",
+ 2: "Mittwoch",
+ 3: "Donnerstag",
+ 4: "Freitag",
+ 5: "Samstag",
+ 6: "Sonntag",
},
},
"months": {
@@ -139,6 +141,12 @@ locale = {
"evening1": "abends",
"night1": "nachts",
},
+ "week_data": {
+ "min_days": 1,
+ "first_day": 0,
+ "weekend_start": 5,
+ "weekend_end": 6,
+ },
},
"custom": custom_translations,
}
diff --git a/pendulum/locales/de/__init__.py b/src/pendulum/locales/en/__init__.py
index e69de29..e69de29 100644
--- a/pendulum/locales/de/__init__.py
+++ b/src/pendulum/locales/en/__init__.py
diff --git a/pendulum/locales/en/custom.py b/src/pendulum/locales/en/custom.py
index a403ad8..65cf467 100644
--- a/pendulum/locales/en/custom.py
+++ b/src/pendulum/locales/en/custom.py
@@ -1,6 +1,8 @@
"""
en custom locale file.
"""
+from __future__ import annotations
+
translations = {
"units": {"few_second": "a few seconds"},
diff --git a/pendulum/locales/en/locale.py b/src/pendulum/locales/en/locale.py
index 00eafc2..4f05c2f 100644
--- a/pendulum/locales/en/locale.py
+++ b/src/pendulum/locales/en/locale.py
@@ -1,4 +1,6 @@
-from .custom import translations as custom_translations
+from __future__ import annotations
+
+from pendulum.locales.en.custom import translations as custom_translations
"""
@@ -31,24 +33,24 @@ locale = {
"translations": {
"days": {
"abbreviated": {
- 0: "Sun",
- 1: "Mon",
- 2: "Tue",
- 3: "Wed",
- 4: "Thu",
- 5: "Fri",
- 6: "Sat",
+ 0: "Mon",
+ 1: "Tue",
+ 2: "Wed",
+ 3: "Thu",
+ 4: "Fri",
+ 5: "Sat",
+ 6: "Sun",
},
- "narrow": {0: "S", 1: "M", 2: "T", 3: "W", 4: "T", 5: "F", 6: "S"},
- "short": {0: "Su", 1: "Mo", 2: "Tu", 3: "We", 4: "Th", 5: "Fr", 6: "Sa"},
+ "narrow": {0: "M", 1: "T", 2: "W", 3: "T", 4: "F", 5: "S", 6: "S"},
+ "short": {0: "Mo", 1: "Tu", 2: "We", 3: "Th", 4: "Fr", 5: "Sa", 6: "Su"},
"wide": {
- 0: "Sunday",
- 1: "Monday",
- 2: "Tuesday",
- 3: "Wednesday",
- 4: "Thursday",
- 5: "Friday",
- 6: "Saturday",
+ 0: "Monday",
+ 1: "Tuesday",
+ 2: "Wednesday",
+ 3: "Thursday",
+ 4: "Friday",
+ 5: "Saturday",
+ 6: "Sunday",
},
},
"months": {
@@ -145,6 +147,12 @@ locale = {
"evening1": "in the evening",
"night1": "at night",
},
+ "week_data": {
+ "min_days": 1,
+ "first_day": 6,
+ "weekend_start": 5,
+ "weekend_end": 6,
+ },
},
"custom": custom_translations,
}
diff --git a/pendulum/locales/en/__init__.py b/src/pendulum/locales/en_gb/__init__.py
index e69de29..e69de29 100644
--- a/pendulum/locales/en/__init__.py
+++ b/src/pendulum/locales/en_gb/__init__.py
diff --git a/src/pendulum/locales/en_gb/custom.py b/src/pendulum/locales/en_gb/custom.py
new file mode 100644
index 0000000..2c77a69
--- /dev/null
+++ b/src/pendulum/locales/en_gb/custom.py
@@ -0,0 +1,25 @@
+"""
+en-gb custom locale file.
+"""
+from __future__ import annotations
+
+
+translations = {
+ "units": {"few_second": "a few seconds"},
+ # Relative time
+ "ago": "{} ago",
+ "from_now": "in {}",
+ "after": "{0} after",
+ "before": "{0} before",
+ # Ordinals
+ "ordinal": {"one": "st", "two": "nd", "few": "rd", "other": "th"},
+ # Date formats
+ "date_formats": {
+ "LTS": "HH:mm:ss",
+ "LT": "HH:mm",
+ "L": "DD/MM/YYYY",
+ "LL": "D MMMM YYYY",
+ "LLL": "D MMMM YYYY HH:mm",
+ "LLLL": "dddd, D MMMM YYYY HH:mm",
+ },
+}
diff --git a/src/pendulum/locales/en_gb/locale.py b/src/pendulum/locales/en_gb/locale.py
new file mode 100644
index 0000000..c25f7d5
--- /dev/null
+++ b/src/pendulum/locales/en_gb/locale.py
@@ -0,0 +1,240 @@
+from __future__ import annotations
+
+from pendulum.locales.en_gb.custom import translations as custom_translations
+
+
+"""
+en-gb locale file.
+
+It has been generated automatically and must not be modified directly.
+"""
+
+
+locale = {
+ "plural": lambda n: "one"
+ if ((n == n and (n == 1)) and (0 == 0 and (0 == 0)))
+ else "other",
+ "ordinal": lambda n: "few"
+ if (
+ ((n % 10) == (n % 10) and ((n % 10) == 3))
+ and (not ((n % 100) == (n % 100) and ((n % 100) == 13)))
+ )
+ else "one"
+ if (
+ ((n % 10) == (n % 10) and ((n % 10) == 1))
+ and (not ((n % 100) == (n % 100) and ((n % 100) == 11)))
+ )
+ else "two"
+ if (
+ ((n % 10) == (n % 10) and ((n % 10) == 2))
+ and (not ((n % 100) == (n % 100) and ((n % 100) == 12)))
+ )
+ else "other",
+ "translations": {
+ "days": {
+ "abbreviated": {
+ 0: "Mon",
+ 1: "Tue",
+ 2: "Wed",
+ 3: "Thu",
+ 4: "Fri",
+ 5: "Sat",
+ 6: "Sun",
+ },
+ "narrow": {
+ 0: "M",
+ 1: "T",
+ 2: "W",
+ 3: "T",
+ 4: "F",
+ 5: "S",
+ 6: "S",
+ },
+ "short": {
+ 0: "Mo",
+ 1: "Tu",
+ 2: "We",
+ 3: "Th",
+ 4: "Fr",
+ 5: "Sa",
+ 6: "Su",
+ },
+ "wide": {
+ 0: "Monday",
+ 1: "Tuesday",
+ 2: "Wednesday",
+ 3: "Thursday",
+ 4: "Friday",
+ 5: "Saturday",
+ 6: "Sunday",
+ },
+ },
+ "months": {
+ "abbreviated": {
+ 1: "Jan",
+ 2: "Feb",
+ 3: "Mar",
+ 4: "Apr",
+ 5: "May",
+ 6: "Jun",
+ 7: "Jul",
+ 8: "Aug",
+ 9: "Sept",
+ 10: "Oct",
+ 11: "Nov",
+ 12: "Dec",
+ },
+ "narrow": {
+ 1: "J",
+ 2: "F",
+ 3: "M",
+ 4: "A",
+ 5: "M",
+ 6: "J",
+ 7: "J",
+ 8: "A",
+ 9: "S",
+ 10: "O",
+ 11: "N",
+ 12: "D",
+ },
+ "wide": {
+ 1: "January",
+ 2: "February",
+ 3: "March",
+ 4: "April",
+ 5: "May",
+ 6: "June",
+ 7: "July",
+ 8: "August",
+ 9: "September",
+ 10: "October",
+ 11: "November",
+ 12: "December",
+ },
+ },
+ "units": {
+ "year": {
+ "one": "{0} year",
+ "other": "{0} years",
+ },
+ "month": {
+ "one": "{0} month",
+ "other": "{0} months",
+ },
+ "week": {
+ "one": "{0} week",
+ "other": "{0} weeks",
+ },
+ "day": {
+ "one": "{0} day",
+ "other": "{0} days",
+ },
+ "hour": {
+ "one": "{0} hour",
+ "other": "{0} hours",
+ },
+ "minute": {
+ "one": "{0} minute",
+ "other": "{0} minutes",
+ },
+ "second": {
+ "one": "{0} second",
+ "other": "{0} seconds",
+ },
+ "microsecond": {
+ "one": "{0} microsecond",
+ "other": "{0} microseconds",
+ },
+ },
+ "relative": {
+ "year": {
+ "future": {
+ "other": "in {0} years",
+ "one": "in {0} year",
+ },
+ "past": {
+ "other": "{0} years ago",
+ "one": "{0} year ago",
+ },
+ },
+ "month": {
+ "future": {
+ "other": "in {0} months",
+ "one": "in {0} month",
+ },
+ "past": {
+ "other": "{0} months ago",
+ "one": "{0} month ago",
+ },
+ },
+ "week": {
+ "future": {
+ "other": "in {0} weeks",
+ "one": "in {0} week",
+ },
+ "past": {
+ "other": "{0} weeks ago",
+ "one": "{0} week ago",
+ },
+ },
+ "day": {
+ "future": {
+ "other": "in {0} days",
+ "one": "in {0} day",
+ },
+ "past": {
+ "other": "{0} days ago",
+ "one": "{0} day ago",
+ },
+ },
+ "hour": {
+ "future": {
+ "other": "in {0} hours",
+ "one": "in {0} hour",
+ },
+ "past": {
+ "other": "{0} hours ago",
+ "one": "{0} hour ago",
+ },
+ },
+ "minute": {
+ "future": {
+ "other": "in {0} minutes",
+ "one": "in {0} minute",
+ },
+ "past": {
+ "other": "{0} minutes ago",
+ "one": "{0} minute ago",
+ },
+ },
+ "second": {
+ "future": {
+ "other": "in {0} seconds",
+ "one": "in {0} second",
+ },
+ "past": {
+ "other": "{0} seconds ago",
+ "one": "{0} second ago",
+ },
+ },
+ },
+ "day_periods": {
+ "midnight": "midnight",
+ "am": "am",
+ "noon": "noon",
+ "pm": "pm",
+ "morning1": "in the morning",
+ "afternoon1": "in the afternoon",
+ "evening1": "in the evening",
+ "night1": "at night",
+ },
+ "week_data": {
+ "min_days": 4,
+ "first_day": 0,
+ "weekend_start": 5,
+ "weekend_end": 6,
+ },
+ },
+ "custom": custom_translations,
+}
diff --git a/pendulum/locales/es/__init__.py b/src/pendulum/locales/en_us/__init__.py
index e69de29..e69de29 100644
--- a/pendulum/locales/es/__init__.py
+++ b/src/pendulum/locales/en_us/__init__.py
diff --git a/src/pendulum/locales/en_us/custom.py b/src/pendulum/locales/en_us/custom.py
new file mode 100644
index 0000000..72d2005
--- /dev/null
+++ b/src/pendulum/locales/en_us/custom.py
@@ -0,0 +1,25 @@
+"""
+en-us custom locale file.
+"""
+from __future__ import annotations
+
+
+translations = {
+ "units": {"few_second": "a few seconds"},
+ # Relative time
+ "ago": "{} ago",
+ "from_now": "in {}",
+ "after": "{0} after",
+ "before": "{0} before",
+ # Ordinals
+ "ordinal": {"one": "st", "two": "nd", "few": "rd", "other": "th"},
+ # 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/src/pendulum/locales/en_us/locale.py b/src/pendulum/locales/en_us/locale.py
new file mode 100644
index 0000000..7f40f3f
--- /dev/null
+++ b/src/pendulum/locales/en_us/locale.py
@@ -0,0 +1,240 @@
+from __future__ import annotations
+
+from pendulum.locales.en_us.custom import translations as custom_translations
+
+
+"""
+en-us locale file.
+
+It has been generated automatically and must not be modified directly.
+"""
+
+
+locale = {
+ "plural": lambda n: "one"
+ if ((n == n and (n == 1)) and (0 == 0 and (0 == 0)))
+ else "other",
+ "ordinal": lambda n: "few"
+ if (
+ ((n % 10) == (n % 10) and ((n % 10) == 3))
+ and (not ((n % 100) == (n % 100) and ((n % 100) == 13)))
+ )
+ else "one"
+ if (
+ ((n % 10) == (n % 10) and ((n % 10) == 1))
+ and (not ((n % 100) == (n % 100) and ((n % 100) == 11)))
+ )
+ else "two"
+ if (
+ ((n % 10) == (n % 10) and ((n % 10) == 2))
+ and (not ((n % 100) == (n % 100) and ((n % 100) == 12)))
+ )
+ else "other",
+ "translations": {
+ "days": {
+ "abbreviated": {
+ 0: "Mon",
+ 1: "Tue",
+ 2: "Wed",
+ 3: "Thu",
+ 4: "Fri",
+ 5: "Sat",
+ 6: "Sun",
+ },
+ "narrow": {
+ 0: "M",
+ 1: "T",
+ 2: "W",
+ 3: "T",
+ 4: "F",
+ 5: "S",
+ 6: "S",
+ },
+ "short": {
+ 0: "Mo",
+ 1: "Tu",
+ 2: "We",
+ 3: "Th",
+ 4: "Fr",
+ 5: "Sa",
+ 6: "Su",
+ },
+ "wide": {
+ 0: "Monday",
+ 1: "Tuesday",
+ 2: "Wednesday",
+ 3: "Thursday",
+ 4: "Friday",
+ 5: "Saturday",
+ 6: "Sunday",
+ },
+ },
+ "months": {
+ "abbreviated": {
+ 1: "Jan",
+ 2: "Feb",
+ 3: "Mar",
+ 4: "Apr",
+ 5: "May",
+ 6: "Jun",
+ 7: "Jul",
+ 8: "Aug",
+ 9: "Sep",
+ 10: "Oct",
+ 11: "Nov",
+ 12: "Dec",
+ },
+ "narrow": {
+ 1: "J",
+ 2: "F",
+ 3: "M",
+ 4: "A",
+ 5: "M",
+ 6: "J",
+ 7: "J",
+ 8: "A",
+ 9: "S",
+ 10: "O",
+ 11: "N",
+ 12: "D",
+ },
+ "wide": {
+ 1: "January",
+ 2: "February",
+ 3: "March",
+ 4: "April",
+ 5: "May",
+ 6: "June",
+ 7: "July",
+ 8: "August",
+ 9: "September",
+ 10: "October",
+ 11: "November",
+ 12: "December",
+ },
+ },
+ "units": {
+ "year": {
+ "one": "{0} year",
+ "other": "{0} years",
+ },
+ "month": {
+ "one": "{0} month",
+ "other": "{0} months",
+ },
+ "week": {
+ "one": "{0} week",
+ "other": "{0} weeks",
+ },
+ "day": {
+ "one": "{0} day",
+ "other": "{0} days",
+ },
+ "hour": {
+ "one": "{0} hour",
+ "other": "{0} hours",
+ },
+ "minute": {
+ "one": "{0} minute",
+ "other": "{0} minutes",
+ },
+ "second": {
+ "one": "{0} second",
+ "other": "{0} seconds",
+ },
+ "microsecond": {
+ "one": "{0} microsecond",
+ "other": "{0} microseconds",
+ },
+ },
+ "relative": {
+ "year": {
+ "future": {
+ "other": "in {0} years",
+ "one": "in {0} year",
+ },
+ "past": {
+ "other": "{0} years ago",
+ "one": "{0} year ago",
+ },
+ },
+ "month": {
+ "future": {
+ "other": "in {0} months",
+ "one": "in {0} month",
+ },
+ "past": {
+ "other": "{0} months ago",
+ "one": "{0} month ago",
+ },
+ },
+ "week": {
+ "future": {
+ "other": "in {0} weeks",
+ "one": "in {0} week",
+ },
+ "past": {
+ "other": "{0} weeks ago",
+ "one": "{0} week ago",
+ },
+ },
+ "day": {
+ "future": {
+ "other": "in {0} days",
+ "one": "in {0} day",
+ },
+ "past": {
+ "other": "{0} days ago",
+ "one": "{0} day ago",
+ },
+ },
+ "hour": {
+ "future": {
+ "other": "in {0} hours",
+ "one": "in {0} hour",
+ },
+ "past": {
+ "other": "{0} hours ago",
+ "one": "{0} hour ago",
+ },
+ },
+ "minute": {
+ "future": {
+ "other": "in {0} minutes",
+ "one": "in {0} minute",
+ },
+ "past": {
+ "other": "{0} minutes ago",
+ "one": "{0} minute ago",
+ },
+ },
+ "second": {
+ "future": {
+ "other": "in {0} seconds",
+ "one": "in {0} second",
+ },
+ "past": {
+ "other": "{0} seconds ago",
+ "one": "{0} second ago",
+ },
+ },
+ },
+ "day_periods": {
+ "midnight": "midnight",
+ "am": "AM",
+ "noon": "noon",
+ "pm": "PM",
+ "morning1": "in the morning",
+ "afternoon1": "in the afternoon",
+ "evening1": "in the evening",
+ "night1": "at night",
+ },
+ "week_data": {
+ "min_days": 1,
+ "first_day": 6,
+ "weekend_start": 5,
+ "weekend_end": 6,
+ },
+ },
+ "custom": custom_translations,
+}
diff --git a/pendulum/locales/fa/__init__.py b/src/pendulum/locales/es/__init__.py
index e69de29..e69de29 100644
--- a/pendulum/locales/fa/__init__.py
+++ b/src/pendulum/locales/es/__init__.py
diff --git a/pendulum/locales/es/custom.py b/src/pendulum/locales/es/custom.py
index 4b7e2b5..4acb411 100644
--- a/pendulum/locales/es/custom.py
+++ b/src/pendulum/locales/es/custom.py
@@ -1,6 +1,8 @@
"""
es custom locale file.
"""
+from __future__ import annotations
+
translations = {
"units": {"few_second": "unos segundos"},
diff --git a/pendulum/locales/es/locale.py b/src/pendulum/locales/es/locale.py
index edba4d3..4ab2784 100644
--- a/pendulum/locales/es/locale.py
+++ b/src/pendulum/locales/es/locale.py
@@ -1,4 +1,6 @@
-from .custom import translations as custom_translations
+from __future__ import annotations
+
+from pendulum.locales.es.custom import translations as custom_translations
"""
@@ -14,24 +16,24 @@ locale = {
"translations": {
"days": {
"abbreviated": {
- 0: "dom.",
- 1: "lun.",
- 2: "mar.",
- 3: "mié.",
- 4: "jue.",
- 5: "vie.",
- 6: "sáb.",
+ 0: "lun.",
+ 1: "mar.",
+ 2: "mié.",
+ 3: "jue.",
+ 4: "vie.",
+ 5: "sáb.",
+ 6: "dom.",
},
- "narrow": {0: "D", 1: "L", 2: "M", 3: "X", 4: "J", 5: "V", 6: "S"},
- "short": {0: "DO", 1: "LU", 2: "MA", 3: "MI", 4: "JU", 5: "VI", 6: "SA"},
+ "narrow": {0: "L", 1: "M", 2: "X", 3: "J", 4: "V", 5: "S", 6: "D"},
+ "short": {0: "LU", 1: "MA", 2: "MI", 3: "JU", 4: "VI", 5: "SA", 6: "DO"},
"wide": {
- 0: "domingo",
- 1: "lunes",
- 2: "martes",
- 3: "miércoles",
- 4: "jueves",
- 5: "viernes",
- 6: "sábado",
+ 0: "lunes",
+ 1: "martes",
+ 2: "miércoles",
+ 3: "jueves",
+ 4: "viernes",
+ 5: "sábado",
+ 6: "domingo",
},
},
"months": {
@@ -136,6 +138,12 @@ locale = {
"evening1": "de la tarde",
"night1": "de la noche",
},
+ "week_data": {
+ "min_days": 1,
+ "first_day": 0,
+ "weekend_start": 5,
+ "weekend_end": 6,
+ },
},
"custom": custom_translations,
}
diff --git a/pendulum/locales/fo/__init__.py b/src/pendulum/locales/fa/__init__.py
index e69de29..e69de29 100644
--- a/pendulum/locales/fo/__init__.py
+++ b/src/pendulum/locales/fa/__init__.py
diff --git a/pendulum/locales/fa/custom.py b/src/pendulum/locales/fa/custom.py
index 082bfad..e4b4a60 100644
--- a/pendulum/locales/fa/custom.py
+++ b/src/pendulum/locales/fa/custom.py
@@ -1,6 +1,8 @@
"""
fa custom locale file.
"""
+from __future__ import annotations
+
translations = {
# Relative time
diff --git a/pendulum/locales/fa/locale.py b/src/pendulum/locales/fa/locale.py
index 32f8e5f..1c3f6c5 100644
--- a/pendulum/locales/fa/locale.py
+++ b/src/pendulum/locales/fa/locale.py
@@ -1,4 +1,6 @@
-from .custom import translations as custom_translations
+from __future__ import annotations
+
+from pendulum.locales.fa.custom import translations as custom_translations
"""
@@ -16,24 +18,24 @@ locale = {
"translations": {
"days": {
"abbreviated": {
- 0: "یکشنبه",
- 1: "دوشنبه",
- 2: "سه\u200cشنبه",
- 3: "چهارشنبه",
- 4: "پنجشنبه",
- 5: "جمعه",
- 6: "شنبه",
+ 0: "دوشنبه",
+ 1: "سه\u200cشنبه",
+ 2: "چهارشنبه",
+ 3: "پنجشنبه",
+ 4: "جمعه",
+ 5: "شنبه",
+ 6: "یکشنبه",
},
- "narrow": {0: "ی", 1: "د", 2: "س", 3: "چ", 4: "پ", 5: "ج", 6: "ش"},
- "short": {0: "۱ش", 1: "۲ش", 2: "۳ش", 3: "۴ش", 4: "۵ش", 5: "ج", 6: "ش"},
+ "narrow": {0: "د", 1: "س", 2: "چ", 3: "پ", 4: "ج", 5: "ش", 6: "ی"},
+ "short": {0: "۲ش", 1: "۳ش", 2: "۴ش", 3: "۵ش", 4: "ج", 5: "ش", 6: "۱ش"},
"wide": {
- 0: "یکشنبه",
- 1: "دوشنبه",
- 2: "سه\u200cشنبه",
- 3: "چهارشنبه",
- 4: "پنجشنبه",
- 5: "جمعه",
- 6: "شنبه",
+ 0: "دوشنبه",
+ 1: "سه\u200cشنبه",
+ 2: "چهارشنبه",
+ 3: "پنجشنبه",
+ 4: "جمعه",
+ 5: "شنبه",
+ 6: "یکشنبه",
},
},
"months": {
@@ -130,6 +132,12 @@ locale = {
"evening1": "عصر",
"night1": "شب",
},
+ "week_data": {
+ "min_days": 1,
+ "first_day": 0,
+ "weekend_start": 5,
+ "weekend_end": 6,
+ },
},
"custom": custom_translations,
}
diff --git a/pendulum/locales/fr/__init__.py b/src/pendulum/locales/fo/__init__.py
index e69de29..e69de29 100644
--- a/pendulum/locales/fr/__init__.py
+++ b/src/pendulum/locales/fo/__init__.py
diff --git a/pendulum/locales/fo/custom.py b/src/pendulum/locales/fo/custom.py
index 456dd59..3f0fd1c 100644
--- a/pendulum/locales/fo/custom.py
+++ b/src/pendulum/locales/fo/custom.py
@@ -1,6 +1,8 @@
"""
fo custom locale file.
"""
+from __future__ import annotations
+
translations = {
# Relative time
diff --git a/pendulum/locales/fo/locale.py b/src/pendulum/locales/fo/locale.py
index 10319ea..28ec0c0 100644
--- a/pendulum/locales/fo/locale.py
+++ b/src/pendulum/locales/fo/locale.py
@@ -1,4 +1,6 @@
-from .custom import translations as custom_translations
+from __future__ import annotations
+
+from pendulum.locales.fo.custom import translations as custom_translations
"""
@@ -14,32 +16,32 @@ locale = {
"translations": {
"days": {
"abbreviated": {
- 0: "sun.",
- 1: "mán.",
- 2: "týs.",
- 3: "mik.",
- 4: "hós.",
- 5: "frí.",
- 6: "ley.",
+ 0: "mán.",
+ 1: "týs.",
+ 2: "mik.",
+ 3: "hós.",
+ 4: "frí.",
+ 5: "ley.",
+ 6: "sun.",
},
- "narrow": {0: "S", 1: "M", 2: "T", 3: "M", 4: "H", 5: "F", 6: "L"},
+ "narrow": {0: "M", 1: "T", 2: "M", 3: "H", 4: "F", 5: "L", 6: "S"},
"short": {
- 0: "su.",
- 1: "má.",
- 2: "tý.",
- 3: "mi.",
- 4: "hó.",
- 5: "fr.",
- 6: "le.",
+ 0: "má.",
+ 1: "tý.",
+ 2: "mi.",
+ 3: "hó.",
+ 4: "fr.",
+ 5: "le.",
+ 6: "su.",
},
"wide": {
- 0: "sunnudagur",
- 1: "mánadagur",
- 2: "týsdagur",
- 3: "mikudagur",
- 4: "hósdagur",
- 5: "fríggjadagur",
- 6: "leygardagur",
+ 0: "mánadagur",
+ 1: "týsdagur",
+ 2: "mikudagur",
+ 3: "hósdagur",
+ 4: "fríggjadagur",
+ 5: "leygardagur",
+ 6: "sunnudagur",
},
},
"months": {
@@ -127,6 +129,12 @@ locale = {
},
},
"day_periods": {"am": "AM", "pm": "PM"},
+ "week_data": {
+ "min_days": 1,
+ "first_day": 0,
+ "weekend_start": 5,
+ "weekend_end": 6,
+ },
},
"custom": custom_translations,
}
diff --git a/pendulum/locales/he/__init__.py b/src/pendulum/locales/fr/__init__.py
index e69de29..e69de29 100644
--- a/pendulum/locales/he/__init__.py
+++ b/src/pendulum/locales/fr/__init__.py
diff --git a/pendulum/locales/fr/custom.py b/src/pendulum/locales/fr/custom.py
index 134f297..913656c 100644
--- a/pendulum/locales/fr/custom.py
+++ b/src/pendulum/locales/fr/custom.py
@@ -1,6 +1,8 @@
"""
fr custom locale file.
"""
+from __future__ import annotations
+
translations = {
"units": {"few_second": "quelques secondes"},
diff --git a/pendulum/locales/fr/locale.py b/src/pendulum/locales/fr/locale.py
index 8855d53..30ee8cd 100644
--- a/pendulum/locales/fr/locale.py
+++ b/src/pendulum/locales/fr/locale.py
@@ -1,4 +1,6 @@
-from .custom import translations as custom_translations
+from __future__ import annotations
+
+from pendulum.locales.fr.custom import translations as custom_translations
"""
@@ -14,24 +16,24 @@ locale = {
"translations": {
"days": {
"abbreviated": {
- 0: "dim.",
- 1: "lun.",
- 2: "mar.",
- 3: "mer.",
- 4: "jeu.",
- 5: "ven.",
- 6: "sam.",
+ 0: "lun.",
+ 1: "mar.",
+ 2: "mer.",
+ 3: "jeu.",
+ 4: "ven.",
+ 5: "sam.",
+ 6: "dim.",
},
- "narrow": {0: "D", 1: "L", 2: "M", 3: "M", 4: "J", 5: "V", 6: "S"},
- "short": {0: "di", 1: "lu", 2: "ma", 3: "me", 4: "je", 5: "ve", 6: "sa"},
+ "narrow": {0: "L", 1: "M", 2: "M", 3: "J", 4: "V", 5: "S", 6: "D"},
+ "short": {0: "lu", 1: "ma", 2: "me", 3: "je", 4: "ve", 5: "sa", 6: "di"},
"wide": {
- 0: "dimanche",
- 1: "lundi",
- 2: "mardi",
- 3: "mercredi",
- 4: "jeudi",
- 5: "vendredi",
- 6: "samedi",
+ 0: "lundi",
+ 1: "mardi",
+ 2: "mercredi",
+ 3: "jeudi",
+ 4: "vendredi",
+ 5: "samedi",
+ 6: "dimanche",
},
},
"months": {
@@ -128,6 +130,12 @@ locale = {
"evening1": "du soir",
"night1": "de nuit",
},
+ "week_data": {
+ "min_days": 1,
+ "first_day": 0,
+ "weekend_start": 5,
+ "weekend_end": 6,
+ },
},
"custom": custom_translations,
}
diff --git a/pendulum/locales/id/__init__.py b/src/pendulum/locales/he/__init__.py
index e69de29..e69de29 100644
--- a/pendulum/locales/id/__init__.py
+++ b/src/pendulum/locales/he/__init__.py
diff --git a/pendulum/locales/he/custom.py b/src/pendulum/locales/he/custom.py
index 51f8476..c8e1f70 100644
--- a/pendulum/locales/he/custom.py
+++ b/src/pendulum/locales/he/custom.py
@@ -1,6 +1,8 @@
"""
he custom locale file.
"""
+from __future__ import annotations
+
translations = {
"units": {"few_second": "כמה שניות"},
diff --git a/pendulum/locales/he/locale.py b/src/pendulum/locales/he/locale.py
index 457c101..42d55aa 100644
--- a/pendulum/locales/he/locale.py
+++ b/src/pendulum/locales/he/locale.py
@@ -1,4 +1,6 @@
-from .custom import translations as custom_translations
+from __future__ import annotations
+
+from pendulum.locales.he.custom import translations as custom_translations
"""
@@ -23,40 +25,40 @@ locale = {
"translations": {
"days": {
"abbreviated": {
- 0: "יום א׳",
- 1: "יום ב׳",
- 2: "יום ג׳",
- 3: "יום ד׳",
- 4: "יום ה׳",
- 5: "יום ו׳",
- 6: "שבת",
+ 0: "יום ב׳",
+ 1: "יום ג׳",
+ 2: "יום ד׳",
+ 3: "יום ה׳",
+ 4: "יום ו׳",
+ 5: "שבת",
+ 6: "יום א׳",
},
"narrow": {
- 0: "א׳",
- 1: "ב׳",
- 2: "ג׳",
- 3: "ד׳",
- 4: "ה׳",
- 5: "ו׳",
- 6: "ש׳",
+ 0: "ב׳",
+ 1: "ג׳",
+ 2: "ד׳",
+ 3: "ה׳",
+ 4: "ו׳",
+ 5: "ש׳",
+ 6: "א׳",
},
"short": {
- 0: "א׳",
- 1: "ב׳",
- 2: "ג׳",
- 3: "ד׳",
- 4: "ה׳",
- 5: "ו׳",
- 6: "ש׳",
+ 0: "ב׳",
+ 1: "ג׳",
+ 2: "ד׳",
+ 3: "ה׳",
+ 4: "ו׳",
+ 5: "ש׳",
+ 6: "א׳",
},
"wide": {
- 0: "יום ראשון",
- 1: "יום שני",
- 2: "יום שלישי",
- 3: "יום רביעי",
- 4: "יום חמישי",
- 5: "יום שישי",
- 6: "יום שבת",
+ 0: "יום שני",
+ 1: "יום שלישי",
+ 2: "יום רביעי",
+ 3: "יום חמישי",
+ 4: "יום שישי",
+ 5: "יום שבת",
+ 6: "יום ראשון",
},
},
"months": {
@@ -264,6 +266,12 @@ locale = {
"night1": "בלילה",
"night2": "לפנות בוקר",
},
+ "week_data": {
+ "min_days": 1,
+ "first_day": 0,
+ "weekend_start": 5,
+ "weekend_end": 6,
+ },
},
"custom": custom_translations,
}
diff --git a/pendulum/locales/it/__init__.py b/src/pendulum/locales/id/__init__.py
index e69de29..e69de29 100644
--- a/pendulum/locales/it/__init__.py
+++ b/src/pendulum/locales/id/__init__.py
diff --git a/pendulum/locales/id/custom.py b/src/pendulum/locales/id/custom.py
index 3ba2035..3d4460c 100644
--- a/pendulum/locales/id/custom.py
+++ b/src/pendulum/locales/id/custom.py
@@ -1,6 +1,8 @@
"""
id custom locale file.
"""
+from __future__ import annotations
+
translations = {
"units": {"few_second": "beberapa detik"},
diff --git a/pendulum/locales/id/locale.py b/src/pendulum/locales/id/locale.py
index bc994ce..2073357 100644
--- a/pendulum/locales/id/locale.py
+++ b/src/pendulum/locales/id/locale.py
@@ -1,4 +1,6 @@
-from .custom import translations as custom_translations
+from __future__ import annotations
+
+from pendulum.locales.id.custom import translations as custom_translations
"""
@@ -14,32 +16,32 @@ locale = {
"translations": {
"days": {
"abbreviated": {
- 0: "Min",
- 1: "Sen",
- 2: "Sel",
- 3: "Rab",
- 4: "Kam",
- 5: "Jum",
- 6: "Sab",
+ 0: "Sen",
+ 1: "Sel",
+ 2: "Rab",
+ 3: "Kam",
+ 4: "Jum",
+ 5: "Sab",
+ 6: "Min",
},
- "narrow": {0: "M", 1: "S", 2: "S", 3: "R", 4: "K", 5: "J", 6: "S"},
+ "narrow": {0: "S", 1: "S", 2: "R", 3: "K", 4: "J", 5: "S", 6: "M"},
"short": {
- 0: "Min",
- 1: "Sen",
- 2: "Sel",
- 3: "Rab",
- 4: "Kam",
- 5: "Jum",
- 6: "Sab",
+ 0: "Sen",
+ 1: "Sel",
+ 2: "Rab",
+ 3: "Kam",
+ 4: "Jum",
+ 5: "Sab",
+ 6: "Min",
},
"wide": {
- 0: "Minggu",
- 1: "Senin",
- 2: "Selasa",
- 3: "Rabu",
- 4: "Kamis",
- 5: "Jumat",
- 6: "Sabtu",
+ 0: "Senin",
+ 1: "Selasa",
+ 2: "Rabu",
+ 3: "Kamis",
+ 4: "Jumat",
+ 5: "Sabtu",
+ 6: "Minggu",
},
},
"months": {
@@ -136,6 +138,12 @@ locale = {
"evening1": "sore",
"night1": "malam",
},
+ "week_data": {
+ "min_days": 1,
+ "first_day": 0,
+ "weekend_start": 5,
+ "weekend_end": 6,
+ },
},
"custom": custom_translations,
}
diff --git a/pendulum/locales/ja/__init__.py b/src/pendulum/locales/it/__init__.py
index e69de29..e69de29 100644
--- a/pendulum/locales/ja/__init__.py
+++ b/src/pendulum/locales/it/__init__.py
diff --git a/pendulum/locales/it/custom.py b/src/pendulum/locales/it/custom.py
index e5cf1cc..b1a77a0 100644
--- a/pendulum/locales/it/custom.py
+++ b/src/pendulum/locales/it/custom.py
@@ -1,6 +1,7 @@
"""
it custom locale file.
"""
+from __future__ import annotations
translations = {
diff --git a/pendulum/locales/it/locale.py b/src/pendulum/locales/it/locale.py
index bb3fdcf..ae5dc39 100644
--- a/pendulum/locales/it/locale.py
+++ b/src/pendulum/locales/it/locale.py
@@ -1,4 +1,6 @@
-from .custom import translations as custom_translations
+from __future__ import annotations
+
+from pendulum.locales.it.custom import translations as custom_translations
"""
@@ -18,32 +20,32 @@ locale = {
"translations": {
"days": {
"abbreviated": {
- 0: "dom",
- 1: "lun",
- 2: "mar",
- 3: "mer",
- 4: "gio",
- 5: "ven",
- 6: "sab",
+ 0: "lun",
+ 1: "mar",
+ 2: "mer",
+ 3: "gio",
+ 4: "ven",
+ 5: "sab",
+ 6: "dom",
},
- "narrow": {0: "D", 1: "L", 2: "M", 3: "M", 4: "G", 5: "V", 6: "S"},
+ "narrow": {0: "L", 1: "M", 2: "M", 3: "G", 4: "V", 5: "S", 6: "D"},
"short": {
- 0: "dom",
- 1: "lun",
- 2: "mar",
- 3: "mer",
- 4: "gio",
- 5: "ven",
- 6: "sab",
+ 0: "lun",
+ 1: "mar",
+ 2: "mer",
+ 3: "gio",
+ 4: "ven",
+ 5: "sab",
+ 6: "dom",
},
"wide": {
- 0: "domenica",
- 1: "lunedì",
- 2: "martedì",
- 3: "mercoledì",
- 4: "giovedì",
- 5: "venerdì",
- 6: "sabato",
+ 0: "lunedì",
+ 1: "martedì",
+ 2: "mercoledì",
+ 3: "giovedì",
+ 4: "venerdì",
+ 5: "sabato",
+ 6: "domenica",
},
},
"months": {
@@ -140,6 +142,12 @@ locale = {
"evening1": "di sera",
"night1": "di notte",
},
+ "week_data": {
+ "min_days": 1,
+ "first_day": 0,
+ "weekend_start": 5,
+ "weekend_end": 6,
+ },
},
"custom": custom_translations,
}
diff --git a/pendulum/locales/ko/__init__.py b/src/pendulum/locales/ja/__init__.py
index e69de29..e69de29 100644
--- a/pendulum/locales/ko/__init__.py
+++ b/src/pendulum/locales/ja/__init__.py
diff --git a/pendulum/locales/ja/custom.py b/src/pendulum/locales/ja/custom.py
index c076250..4cb5b95 100644
--- a/pendulum/locales/ja/custom.py
+++ b/src/pendulum/locales/ja/custom.py
@@ -1,6 +1,8 @@
"""
ja custom locale file.
"""
+from __future__ import annotations
+
translations = {
"units": {"few_second": "数秒"},
diff --git a/pendulum/locales/ja/locale.py b/src/pendulum/locales/ja/locale.py
index 574d2ec..a1d3bd9 100644
--- a/pendulum/locales/ja/locale.py
+++ b/src/pendulum/locales/ja/locale.py
@@ -1,4 +1,6 @@
-from .custom import translations as custom_translations
+from __future__ import annotations
+
+from pendulum.locales.ja.custom import translations as custom_translations
"""
@@ -14,40 +16,40 @@ locale = {
"translations": {
"days": {
"abbreviated": {
- 0: "日",
- 1: "月",
- 2: "火",
- 3: "水",
- 4: "木",
- 5: "金",
- 6: "土",
+ 0: "月",
+ 1: "火",
+ 2: "水",
+ 3: "木",
+ 4: "金",
+ 5: "土",
+ 6: "日",
},
"narrow": {
- 0: "日",
- 1: "月",
- 2: "火",
- 3: "水",
- 4: "木",
- 5: "金",
- 6: "土",
+ 0: "月",
+ 1: "火",
+ 2: "水",
+ 3: "木",
+ 4: "金",
+ 5: "土",
+ 6: "日",
},
"short": {
- 0: "日",
- 1: "月",
- 2: "火",
- 3: "水",
- 4: "木",
- 5: "金",
- 6: "土",
+ 0: "月",
+ 1: "火",
+ 2: "水",
+ 3: "木",
+ 4: "金",
+ 5: "土",
+ 6: "日",
},
"wide": {
- 0: "日曜日",
- 1: "月曜日",
- 2: "火曜日",
- 3: "水曜日",
- 4: "木曜日",
- 5: "金曜日",
- 6: "土曜日",
+ 0: "月曜日",
+ 1: "火曜日",
+ 2: "水曜日",
+ 3: "木曜日",
+ 4: "金曜日",
+ 5: "土曜日",
+ 6: "日曜日",
},
},
"months": {
@@ -189,6 +191,12 @@ locale = {
"night1": "夜",
"night2": "夜中",
},
+ "week_data": {
+ "min_days": 1,
+ "first_day": 0,
+ "weekend_start": 5,
+ "weekend_end": 6,
+ },
},
"custom": custom_translations,
}
diff --git a/pendulum/locales/lt/__init__.py b/src/pendulum/locales/ko/__init__.py
index e69de29..e69de29 100644
--- a/pendulum/locales/lt/__init__.py
+++ b/src/pendulum/locales/ko/__init__.py
diff --git a/pendulum/locales/ko/custom.py b/src/pendulum/locales/ko/custom.py
index 2c0e50c..b7476ff 100644
--- a/pendulum/locales/ko/custom.py
+++ b/src/pendulum/locales/ko/custom.py
@@ -1,6 +1,8 @@
"""
ko custom locale file.
"""
+from __future__ import annotations
+
translations = {
# Relative time
diff --git a/pendulum/locales/ko/locale.py b/src/pendulum/locales/ko/locale.py
index 0f5a346..b285dc0 100644
--- a/pendulum/locales/ko/locale.py
+++ b/src/pendulum/locales/ko/locale.py
@@ -1,4 +1,6 @@
-from .custom import translations as custom_translations
+from __future__ import annotations
+
+from pendulum.locales.ko.custom import translations as custom_translations
"""
@@ -13,17 +15,17 @@ locale = {
"ordinal": lambda n: "other",
"translations": {
"days": {
- "abbreviated": {0: "일", 1: "월", 2: "화", 3: "수", 4: "목", 5: "금", 6: "토"},
- "narrow": {0: "일", 1: "월", 2: "화", 3: "수", 4: "목", 5: "금", 6: "토"},
- "short": {0: "일", 1: "월", 2: "화", 3: "수", 4: "목", 5: "금", 6: "토"},
+ "abbreviated": {0: "월", 1: "화", 2: "수", 3: "목", 4: "금", 5: "토", 6: "일"},
+ "narrow": {0: "월", 1: "화", 2: "수", 3: "목", 4: "금", 5: "토", 6: "일"},
+ "short": {0: "월", 1: "화", 2: "수", 3: "목", 4: "금", 5: "토", 6: "일"},
"wide": {
- 0: "일요일",
- 1: "월요일",
- 2: "화요일",
- 3: "수요일",
- 4: "목요일",
- 5: "금요일",
- 6: "토요일",
+ 0: "월요일",
+ 1: "화요일",
+ 2: "수요일",
+ 3: "목요일",
+ 4: "금요일",
+ 5: "토요일",
+ 6: "일요일",
},
},
"months": {
@@ -100,6 +102,12 @@ locale = {
"evening1": "저녁",
"night1": "밤",
},
+ "week_data": {
+ "min_days": 1,
+ "first_day": 0,
+ "weekend_start": 5,
+ "weekend_end": 6,
+ },
},
"custom": custom_translations,
}
diff --git a/pendulum/locales/locale.py b/src/pendulum/locales/locale.py
index 637509a..21eaaec 100644
--- a/pendulum/locales/locale.py
+++ b/src/pendulum/locales/locale.py
@@ -1,17 +1,15 @@
from __future__ import annotations
+import re
+
from importlib import import_module
from pathlib import Path
-
-import re
-import sys
-from typing import Any, cast
+from typing import Any
+from typing import ClassVar
from typing import Dict
+from typing import cast
-if sys.version_info >= (3, 9):
- from importlib import resources
-else:
- import importlib_resources as resources
+from pendulum.utils._compat import resources
class Locale:
@@ -19,7 +17,7 @@ class Locale:
Represent a specific locale.
"""
- _cache: dict[str, Locale] = {}
+ _cache: ClassVar[dict[str, Locale]] = {}
def __init__(self, locale: str, data: Any) -> None:
self._locale: str = locale
diff --git a/pendulum/locales/nb/__init__.py b/src/pendulum/locales/lt/__init__.py
index e69de29..e69de29 100644
--- a/pendulum/locales/nb/__init__.py
+++ b/src/pendulum/locales/lt/__init__.py
diff --git a/pendulum/locales/lt/custom.py b/src/pendulum/locales/lt/custom.py
index 6480c31..d7f17d3 100644
--- a/pendulum/locales/lt/custom.py
+++ b/src/pendulum/locales/lt/custom.py
@@ -1,6 +1,8 @@
"""
lt custom locale file.
"""
+from __future__ import annotations
+
translations = {
# Relative time
diff --git a/pendulum/locales/lt/locale.py b/src/pendulum/locales/lt/locale.py
index fb017ef..6e9a460 100644
--- a/pendulum/locales/lt/locale.py
+++ b/src/pendulum/locales/lt/locale.py
@@ -1,4 +1,6 @@
-from .custom import translations as custom_translations
+from __future__ import annotations
+
+from pendulum.locales.lt.custom import translations as custom_translations
"""
@@ -26,24 +28,24 @@ locale = {
"translations": {
"days": {
"abbreviated": {
- 0: "sk",
- 1: "pr",
- 2: "an",
- 3: "tr",
- 4: "kt",
- 5: "pn",
- 6: "št",
- },
- "narrow": {0: "S", 1: "P", 2: "A", 3: "T", 4: "K", 5: "P", 6: "Š"},
- "short": {0: "Sk", 1: "Pr", 2: "An", 3: "Tr", 4: "Kt", 5: "Pn", 6: "Št"},
+ 0: "pr",
+ 1: "an",
+ 2: "tr",
+ 3: "kt",
+ 4: "pn",
+ 5: "št",
+ 6: "sk",
+ },
+ "narrow": {0: "P", 1: "A", 2: "T", 3: "K", 4: "P", 5: "Š", 6: "S"},
+ "short": {0: "Pr", 1: "An", 2: "Tr", 3: "Kt", 4: "Pn", 5: "Št", 6: "Sk"},
"wide": {
- 0: "sekmadienis",
- 1: "pirmadienis",
- 2: "antradienis",
- 3: "trečiadienis",
- 4: "ketvirtadienis",
- 5: "penktadienis",
- 6: "šeštadienis",
+ 0: "pirmadienis",
+ 1: "antradienis",
+ 2: "trečiadienis",
+ 3: "ketvirtadienis",
+ 4: "penktadienis",
+ 5: "šeštadienis",
+ 6: "sekmadienis",
},
},
"months": {
@@ -250,6 +252,12 @@ locale = {
"evening1": "vakaras",
"night1": "naktis",
},
+ "week_data": {
+ "min_days": 1,
+ "first_day": 0,
+ "weekend_start": 5,
+ "weekend_end": 6,
+ },
},
"custom": custom_translations,
}
diff --git a/pendulum/locales/nl/__init__.py b/src/pendulum/locales/nb/__init__.py
index e69de29..e69de29 100644
--- a/pendulum/locales/nl/__init__.py
+++ b/src/pendulum/locales/nb/__init__.py
diff --git a/pendulum/locales/nb/custom.py b/src/pendulum/locales/nb/custom.py
index 4c7cd6a..554e7f3 100644
--- a/pendulum/locales/nb/custom.py
+++ b/src/pendulum/locales/nb/custom.py
@@ -1,6 +1,8 @@
"""
nn custom locale file.
"""
+from __future__ import annotations
+
translations = {
# Relative time
diff --git a/pendulum/locales/nb/locale.py b/src/pendulum/locales/nb/locale.py
index c8297a8..084f019 100644
--- a/pendulum/locales/nb/locale.py
+++ b/src/pendulum/locales/nb/locale.py
@@ -1,4 +1,6 @@
-from .custom import translations as custom_translations
+from __future__ import annotations
+
+from pendulum.locales.nb.custom import translations as custom_translations
"""
@@ -14,32 +16,32 @@ locale = {
"translations": {
"days": {
"abbreviated": {
- 0: "søn.",
- 1: "man.",
- 2: "tir.",
- 3: "ons.",
- 4: "tor.",
- 5: "fre.",
- 6: "lør.",
+ 0: "man.",
+ 1: "tir.",
+ 2: "ons.",
+ 3: "tor.",
+ 4: "fre.",
+ 5: "lør.",
+ 6: "søn.",
},
- "narrow": {0: "S", 1: "M", 2: "T", 3: "O", 4: "T", 5: "F", 6: "L"},
+ "narrow": {0: "M", 1: "T", 2: "O", 3: "T", 4: "F", 5: "L", 6: "S"},
"short": {
- 0: "sø.",
- 1: "ma.",
- 2: "ti.",
- 3: "on.",
- 4: "to.",
- 5: "fr.",
- 6: "lø.",
+ 0: "ma.",
+ 1: "ti.",
+ 2: "on.",
+ 3: "to.",
+ 4: "fr.",
+ 5: "lø.",
+ 6: "sø.",
},
"wide": {
- 0: "søndag",
- 1: "mandag",
- 2: "tirsdag",
- 3: "onsdag",
- 4: "torsdag",
- 5: "fredag",
- 6: "lørdag",
+ 0: "mandag",
+ 1: "tirsdag",
+ 2: "onsdag",
+ 3: "torsdag",
+ 4: "fredag",
+ 5: "lørdag",
+ 6: "søndag",
},
},
"months": {
@@ -145,6 +147,12 @@ locale = {
"evening1": "kvelden",
"night1": "natten",
},
+ "week_data": {
+ "min_days": 1,
+ "first_day": 0,
+ "weekend_start": 5,
+ "weekend_end": 6,
+ },
},
"custom": custom_translations,
}
diff --git a/pendulum/locales/nn/__init__.py b/src/pendulum/locales/nl/__init__.py
index e69de29..e69de29 100644
--- a/pendulum/locales/nn/__init__.py
+++ b/src/pendulum/locales/nl/__init__.py
diff --git a/pendulum/locales/nl/custom.py b/src/pendulum/locales/nl/custom.py
index 2ca5a85..ca90673 100644
--- a/pendulum/locales/nl/custom.py
+++ b/src/pendulum/locales/nl/custom.py
@@ -1,6 +1,8 @@
"""
nl custom locale file.
"""
+from __future__ import annotations
+
translations = {
"units": {"few_second": "enkele seconden"},
diff --git a/pendulum/locales/nl/locale.py b/src/pendulum/locales/nl/locale.py
index cb1570d..68b54e7 100644
--- a/pendulum/locales/nl/locale.py
+++ b/src/pendulum/locales/nl/locale.py
@@ -1,4 +1,6 @@
-from .custom import translations as custom_translations
+from __future__ import annotations
+
+from pendulum.locales.nl.custom import translations as custom_translations
"""
@@ -16,24 +18,24 @@ locale = {
"translations": {
"days": {
"abbreviated": {
- 0: "zo",
- 1: "ma",
- 2: "di",
- 3: "wo",
- 4: "do",
- 5: "vr",
- 6: "za",
+ 0: "ma",
+ 1: "di",
+ 2: "wo",
+ 3: "do",
+ 4: "vr",
+ 5: "za",
+ 6: "zo",
},
- "narrow": {0: "Z", 1: "M", 2: "D", 3: "W", 4: "D", 5: "V", 6: "Z"},
- "short": {0: "zo", 1: "ma", 2: "di", 3: "wo", 4: "do", 5: "vr", 6: "za"},
+ "narrow": {0: "M", 1: "D", 2: "W", 3: "D", 4: "V", 5: "Z", 6: "Z"},
+ "short": {0: "ma", 1: "di", 2: "wo", 3: "do", 4: "vr", 5: "za", 6: "zo"},
"wide": {
- 0: "zondag",
- 1: "maandag",
- 2: "dinsdag",
- 3: "woensdag",
- 4: "donderdag",
- 5: "vrijdag",
- 6: "zaterdag",
+ 0: "maandag",
+ 1: "dinsdag",
+ 2: "woensdag",
+ 3: "donderdag",
+ 4: "vrijdag",
+ 5: "zaterdag",
+ 6: "zondag",
},
},
"months": {
@@ -128,6 +130,12 @@ locale = {
"afternoon1": "‘s middags",
"evening1": "‘s avonds",
"night1": "‘s nachts",
+ "week_data": {
+ "min_days": 1,
+ "first_day": 0,
+ "weekend_start": 5,
+ "weekend_end": 6,
+ },
},
},
"custom": custom_translations,
diff --git a/pendulum/locales/pl/__init__.py b/src/pendulum/locales/nn/__init__.py
index e69de29..e69de29 100644
--- a/pendulum/locales/pl/__init__.py
+++ b/src/pendulum/locales/nn/__init__.py
diff --git a/pendulum/locales/nn/custom.py b/src/pendulum/locales/nn/custom.py
index 4c7cd6a..554e7f3 100644
--- a/pendulum/locales/nn/custom.py
+++ b/src/pendulum/locales/nn/custom.py
@@ -1,6 +1,8 @@
"""
nn custom locale file.
"""
+from __future__ import annotations
+
translations = {
# Relative time
diff --git a/pendulum/locales/nn/locale.py b/src/pendulum/locales/nn/locale.py
index eb46e1d..737ee6d 100644
--- a/pendulum/locales/nn/locale.py
+++ b/src/pendulum/locales/nn/locale.py
@@ -1,4 +1,6 @@
-from .custom import translations as custom_translations
+from __future__ import annotations
+
+from pendulum.locales.nn.custom import translations as custom_translations
"""
@@ -14,32 +16,32 @@ locale = {
"translations": {
"days": {
"abbreviated": {
- 0: "søn.",
- 1: "mån.",
- 2: "tys.",
- 3: "ons.",
- 4: "tor.",
- 5: "fre.",
- 6: "lau.",
+ 0: "mån.",
+ 1: "tys.",
+ 2: "ons.",
+ 3: "tor.",
+ 4: "fre.",
+ 5: "lau.",
+ 6: "søn.",
},
- "narrow": {0: "S", 1: "M", 2: "T", 3: "O", 4: "T", 5: "F", 6: "L"},
+ "narrow": {0: "M", 1: "T", 2: "O", 3: "T", 4: "F", 5: "L", 6: "S"},
"short": {
- 0: "sø.",
- 1: "må.",
- 2: "ty.",
- 3: "on.",
- 4: "to.",
- 5: "fr.",
- 6: "la.",
+ 0: "må.",
+ 1: "ty.",
+ 2: "on.",
+ 3: "to.",
+ 4: "fr.",
+ 5: "la.",
+ 6: "sø.",
},
"wide": {
- 0: "søndag",
- 1: "måndag",
- 2: "tysdag",
- 3: "onsdag",
- 4: "torsdag",
- 5: "fredag",
- 6: "laurdag",
+ 0: "måndag",
+ 1: "tysdag",
+ 2: "onsdag",
+ 3: "torsdag",
+ 4: "fredag",
+ 5: "laurdag",
+ 6: "søndag",
},
},
"months": {
@@ -136,6 +138,12 @@ locale = {
},
},
"day_periods": {"am": "formiddag", "pm": "ettermiddag"},
+ "week_data": {
+ "min_days": 1,
+ "first_day": 0,
+ "weekend_start": 5,
+ "weekend_end": 6,
+ },
},
"custom": custom_translations,
}
diff --git a/pendulum/locales/pt_br/__init__.py b/src/pendulum/locales/pl/__init__.py
index e69de29..e69de29 100644
--- a/pendulum/locales/pt_br/__init__.py
+++ b/src/pendulum/locales/pl/__init__.py
diff --git a/pendulum/locales/pl/custom.py b/src/pendulum/locales/pl/custom.py
index 9741b74..0aaab90 100644
--- a/pendulum/locales/pl/custom.py
+++ b/src/pendulum/locales/pl/custom.py
@@ -1,6 +1,8 @@
"""
pl custom locale file.
"""
+from __future__ import annotations
+
translations = {
"units": {"few_second": "kilka sekund"},
diff --git a/pendulum/locales/pl/locale.py b/src/pendulum/locales/pl/locale.py
index bf6af10..c709120 100644
--- a/pendulum/locales/pl/locale.py
+++ b/src/pendulum/locales/pl/locale.py
@@ -1,4 +1,6 @@
-from .custom import translations as custom_translations
+from __future__ import annotations
+
+from pendulum.locales.pl.custom import translations as custom_translations
"""
@@ -41,32 +43,32 @@ locale = {
"translations": {
"days": {
"abbreviated": {
- 0: "niedz.",
- 1: "pon.",
- 2: "wt.",
- 3: "śr.",
- 4: "czw.",
- 5: "pt.",
- 6: "sob.",
+ 0: "pon.",
+ 1: "wt.",
+ 2: "śr.",
+ 3: "czw.",
+ 4: "pt.",
+ 5: "sob.",
+ 6: "niedz.",
},
- "narrow": {0: "n", 1: "p", 2: "w", 3: "ś", 4: "c", 5: "p", 6: "s"},
+ "narrow": {0: "p", 1: "w", 2: "ś", 3: "c", 4: "p", 5: "s", 6: "n"},
"short": {
- 0: "nie",
- 1: "pon",
- 2: "wto",
- 3: "śro",
- 4: "czw",
- 5: "pią",
- 6: "sob",
+ 0: "pon",
+ 1: "wto",
+ 2: "śro",
+ 3: "czw",
+ 4: "pią",
+ 5: "sob",
+ 6: "nie",
},
"wide": {
- 0: "niedziela",
- 1: "poniedziałek",
- 2: "wtorek",
- 3: "środa",
- 4: "czwartek",
- 5: "piątek",
- 6: "sobota",
+ 0: "poniedziałek",
+ 1: "wtorek",
+ 2: "środa",
+ 3: "czwartek",
+ 4: "piątek",
+ 5: "sobota",
+ 6: "niedziela",
},
},
"months": {
@@ -274,6 +276,12 @@ locale = {
"evening1": "wieczorem",
"night1": "w nocy",
},
+ "week_data": {
+ "min_days": 1,
+ "first_day": 0,
+ "weekend_start": 5,
+ "weekend_end": 6,
+ },
},
"custom": custom_translations,
}
diff --git a/pendulum/locales/ru/__init__.py b/src/pendulum/locales/pt_br/__init__.py
index e69de29..e69de29 100644
--- a/pendulum/locales/ru/__init__.py
+++ b/src/pendulum/locales/pt_br/__init__.py
diff --git a/pendulum/locales/pt_br/custom.py b/src/pendulum/locales/pt_br/custom.py
index 12aced7..87a7702 100644
--- a/pendulum/locales/pt_br/custom.py
+++ b/src/pendulum/locales/pt_br/custom.py
@@ -1,6 +1,8 @@
"""
pt-br custom locale file.
"""
+from __future__ import annotations
+
translations = {
# Relative time
diff --git a/pendulum/locales/pt_br/locale.py b/src/pendulum/locales/pt_br/locale.py
index 742c41f..793cba8 100644
--- a/pendulum/locales/pt_br/locale.py
+++ b/src/pendulum/locales/pt_br/locale.py
@@ -1,4 +1,6 @@
-from .custom import translations as custom_translations
+from __future__ import annotations
+
+from pendulum.locales.pt_br.custom import translations as custom_translations
"""
@@ -16,32 +18,32 @@ locale = {
"translations": {
"days": {
"abbreviated": {
- 0: "dom",
- 1: "seg",
- 2: "ter",
- 3: "qua",
- 4: "qui",
- 5: "sex",
- 6: "sáb",
+ 0: "seg",
+ 1: "ter",
+ 2: "qua",
+ 3: "qui",
+ 4: "sex",
+ 5: "sáb",
+ 6: "dom",
},
- "narrow": {0: "D", 1: "S", 2: "T", 3: "Q", 4: "Q", 5: "S", 6: "S"},
+ "narrow": {0: "S", 1: "T", 2: "Q", 3: "Q", 4: "S", 5: "S", 6: "D"},
"short": {
- 0: "dom",
- 1: "seg",
- 2: "ter",
- 3: "qua",
- 4: "qui",
- 5: "sex",
- 6: "sáb",
+ 0: "seg",
+ 1: "ter",
+ 2: "qua",
+ 3: "qui",
+ 4: "sex",
+ 5: "sáb",
+ 6: "dom",
},
"wide": {
- 0: "domingo",
- 1: "segunda-feira",
- 2: "terça-feira",
- 3: "quarta-feira",
- 4: "quinta-feira",
- 5: "sexta-feira",
- 6: "sábado",
+ 0: "segunda-feira",
+ 1: "terça-feira",
+ 2: "quarta-feira",
+ 3: "quinta-feira",
+ 4: "sexta-feira",
+ 5: "sábado",
+ 6: "domingo",
},
},
"months": {
@@ -138,6 +140,12 @@ locale = {
"evening1": "da noite",
"night1": "da madrugada",
},
+ "week_data": {
+ "min_days": 1,
+ "first_day": 6,
+ "weekend_start": 5,
+ "weekend_end": 6,
+ },
},
"custom": custom_translations,
}
diff --git a/pendulum/locales/sk/__init__.py b/src/pendulum/locales/ru/__init__.py
index e69de29..e69de29 100644
--- a/pendulum/locales/sk/__init__.py
+++ b/src/pendulum/locales/ru/__init__.py
diff --git a/pendulum/locales/ru/custom.py b/src/pendulum/locales/ru/custom.py
index b4c89bb..e1f87ff 100644
--- a/pendulum/locales/ru/custom.py
+++ b/src/pendulum/locales/ru/custom.py
@@ -1,6 +1,8 @@
"""
ru custom locale file.
"""
+from __future__ import annotations
+
translations = {
# Relative time
diff --git a/pendulum/locales/ru/locale.py b/src/pendulum/locales/ru/locale.py
index 3736e0b..b9eab83 100644
--- a/pendulum/locales/ru/locale.py
+++ b/src/pendulum/locales/ru/locale.py
@@ -1,4 +1,6 @@
-from .custom import translations as custom_translations
+from __future__ import annotations
+
+from pendulum.locales.ru.custom import translations as custom_translations
"""
@@ -41,24 +43,24 @@ locale = {
"translations": {
"days": {
"abbreviated": {
- 0: "вс",
- 1: "пн",
- 2: "вт",
- 3: "ср",
- 4: "чт",
- 5: "пт",
- 6: "сб",
+ 0: "пн",
+ 1: "вт",
+ 2: "ср",
+ 3: "чт",
+ 4: "пт",
+ 5: "сб",
+ 6: "вс",
},
- "narrow": {0: "вс", 1: "пн", 2: "вт", 3: "ср", 4: "чт", 5: "пт", 6: "сб"},
- "short": {0: "вс", 1: "пн", 2: "вт", 3: "ср", 4: "чт", 5: "пт", 6: "сб"},
+ "narrow": {0: "пн", 1: "вт", 2: "ср", 3: "чт", 4: "пт", 5: "сб", 6: "вс"},
+ "short": {0: "пн", 1: "вт", 2: "ср", 3: "чт", 4: "пт", 5: "сб", 6: "вс"},
"wide": {
- 0: "воскресенье",
- 1: "понедельник",
- 2: "вторник",
- 3: "среда",
- 4: "четверг",
- 5: "пятница",
- 6: "суббота",
+ 0: "понедельник",
+ 1: "вторник",
+ 2: "среда",
+ 3: "четверг",
+ 4: "пятница",
+ 5: "суббота",
+ 6: "воскресенье",
},
},
"months": {
@@ -265,6 +267,12 @@ locale = {
"evening1": "вечера",
"night1": "ночи",
},
+ "week_data": {
+ "min_days": 1,
+ "first_day": 0,
+ "weekend_start": 5,
+ "weekend_end": 6,
+ },
},
"custom": custom_translations,
}
diff --git a/pendulum/locales/sv/__init__.py b/src/pendulum/locales/sk/__init__.py
index e69de29..e69de29 100644
--- a/pendulum/locales/sv/__init__.py
+++ b/src/pendulum/locales/sk/__init__.py
diff --git a/pendulum/locales/sk/custom.py b/src/pendulum/locales/sk/custom.py
index 71afb15..0059c11 100644
--- a/pendulum/locales/sk/custom.py
+++ b/src/pendulum/locales/sk/custom.py
@@ -1,6 +1,8 @@
"""
sk custom locale file.
"""
+from __future__ import annotations
+
translations = {
# Relative time
diff --git a/pendulum/locales/sk/locale.py b/src/pendulum/locales/sk/locale.py
index 8d3459f..530303f 100644
--- a/pendulum/locales/sk/locale.py
+++ b/src/pendulum/locales/sk/locale.py
@@ -1,4 +1,6 @@
-from .custom import translations as custom_translations
+from __future__ import annotations
+
+from pendulum.locales.sk.custom import translations as custom_translations
"""
@@ -20,40 +22,40 @@ locale = {
"translations": {
"days": {
"abbreviated": {
- 0: "ne",
- 1: "po",
- 2: "ut",
- 3: "st",
- 4: "št",
- 5: "pi",
- 6: "so",
+ 0: "po",
+ 1: "ut",
+ 2: "st",
+ 3: "št",
+ 4: "pi",
+ 5: "so",
+ 6: "ne",
},
"narrow": {
- 0: "n",
- 1: "p",
- 2: "u",
- 3: "s",
- 4: "š",
- 5: "p",
- 6: "s",
+ 0: "p",
+ 1: "u",
+ 2: "s",
+ 3: "š",
+ 4: "p",
+ 5: "s",
+ 6: "n",
},
"short": {
- 0: "ne",
- 1: "po",
- 2: "ut",
- 3: "st",
- 4: "št",
- 5: "pi",
- 6: "so",
+ 0: "po",
+ 1: "ut",
+ 2: "st",
+ 3: "št",
+ 4: "pi",
+ 5: "so",
+ 6: "ne",
},
"wide": {
- 0: "nedeľa",
- 1: "pondelok",
- 2: "utorok",
- 3: "streda",
- 4: "štvrtok",
- 5: "piatok",
- 6: "sobota",
+ 0: "pondelok",
+ 1: "utorok",
+ 2: "streda",
+ 3: "štvrtok",
+ 4: "piatok",
+ 5: "sobota",
+ 6: "nedeľa",
},
},
"months": {
@@ -261,6 +263,12 @@ locale = {
"evening1": "večer",
"night1": "v noci",
},
+ "week_data": {
+ "min_days": 1,
+ "first_day": 0,
+ "weekend_start": 5,
+ "weekend_end": 6,
+ },
},
"custom": custom_translations,
}
diff --git a/pendulum/locales/zh/__init__.py b/src/pendulum/locales/sv/__init__.py
index e69de29..e69de29 100644
--- a/pendulum/locales/zh/__init__.py
+++ b/src/pendulum/locales/sv/__init__.py
diff --git a/pendulum/locales/sv/custom.py b/src/pendulum/locales/sv/custom.py
index 7158f4b..83f36b1 100644
--- a/pendulum/locales/sv/custom.py
+++ b/src/pendulum/locales/sv/custom.py
@@ -1,6 +1,8 @@
"""
sv custom locale file.
"""
+from __future__ import annotations
+
translations = {
# Relative time
diff --git a/pendulum/locales/sv/locale.py b/src/pendulum/locales/sv/locale.py
index 5b74a6e..c3c4472 100644
--- a/pendulum/locales/sv/locale.py
+++ b/src/pendulum/locales/sv/locale.py
@@ -1,4 +1,6 @@
-from .custom import translations as custom_translations
+from __future__ import annotations
+
+from pendulum.locales.sv.custom import translations as custom_translations
"""
@@ -21,40 +23,40 @@ locale = {
"translations": {
"days": {
"abbreviated": {
- 0: "sön",
- 1: "mån",
- 2: "tis",
- 3: "ons",
- 4: "tors",
- 5: "fre",
- 6: "lör",
+ 0: "mån",
+ 1: "tis",
+ 2: "ons",
+ 3: "tors",
+ 4: "fre",
+ 5: "lör",
+ 6: "sön",
},
"narrow": {
- 0: "S",
- 1: "M",
- 2: "T",
- 3: "O",
- 4: "T",
- 5: "F",
- 6: "L",
+ 0: "M",
+ 1: "T",
+ 2: "O",
+ 3: "T",
+ 4: "F",
+ 5: "L",
+ 6: "S",
},
"short": {
- 0: "sö",
- 1: "må",
- 2: "ti",
- 3: "on",
- 4: "to",
- 5: "fr",
- 6: "lö",
+ 0: "må",
+ 1: "ti",
+ 2: "on",
+ 3: "to",
+ 4: "fr",
+ 5: "lö",
+ 6: "sö",
},
"wide": {
- 0: "söndag",
- 1: "måndag",
- 2: "tisdag",
- 3: "onsdag",
- 4: "torsdag",
- 5: "fredag",
- 6: "lördag",
+ 0: "måndag",
+ 1: "tisdag",
+ 2: "onsdag",
+ 3: "torsdag",
+ 4: "fredag",
+ 5: "lördag",
+ 6: "söndag",
},
},
"months": {
@@ -217,6 +219,12 @@ locale = {
"evening1": "på kvällen",
"night1": "på natten",
},
+ "week_data": {
+ "min_days": 1,
+ "first_day": 0,
+ "weekend_start": 5,
+ "weekend_end": 6,
+ },
},
"custom": custom_translations,
}
diff --git a/pendulum/mixins/__init__.py b/src/pendulum/locales/tr/__init__.py
index e69de29..e69de29 100644
--- a/pendulum/mixins/__init__.py
+++ b/src/pendulum/locales/tr/__init__.py
diff --git a/src/pendulum/locales/tr/custom.py b/src/pendulum/locales/tr/custom.py
new file mode 100644
index 0000000..b7fd3c7
--- /dev/null
+++ b/src/pendulum/locales/tr/custom.py
@@ -0,0 +1,24 @@
+"""
+tr custom locale file.
+"""
+from __future__ import annotations
+
+
+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/src/pendulum/locales/tr/locale.py b/src/pendulum/locales/tr/locale.py
new file mode 100644
index 0000000..f0233f2
--- /dev/null
+++ b/src/pendulum/locales/tr/locale.py
@@ -0,0 +1,225 @@
+from __future__ import annotations
+
+from pendulum.locales.tr.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: "Pzt",
+ 1: "Sal",
+ 2: "Çar",
+ 3: "Per",
+ 4: "Cum",
+ 5: "Cmt",
+ 6: "Paz",
+ },
+ "narrow": {
+ 0: "P",
+ 1: "S",
+ 2: "Ç",
+ 3: "P",
+ 4: "C",
+ 5: "C",
+ 6: "P",
+ },
+ "short": {
+ 0: "Pt",
+ 1: "Sa",
+ 2: "Ça",
+ 3: "Pe",
+ 4: "Cu",
+ 5: "Ct",
+ 6: "Pa",
+ },
+ "wide": {
+ 0: "Pazartesi",
+ 1: "Salı",
+ 2: "Çarşamba",
+ 3: "Perşembe",
+ 4: "Cuma",
+ 5: "Cumartesi",
+ 6: "Pazar",
+ },
+ },
+ "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",
+ },
+ "week_data": {
+ "min_days": 1,
+ "first_day": 0,
+ "weekend_start": 5,
+ "weekend_end": 6,
+ },
+ },
+ "custom": custom_translations,
+}
diff --git a/pendulum/testing/__init__.py b/src/pendulum/locales/zh/__init__.py
index e69de29..e69de29 100644
--- a/pendulum/testing/__init__.py
+++ b/src/pendulum/locales/zh/__init__.py
diff --git a/pendulum/locales/zh/custom.py b/src/pendulum/locales/zh/custom.py
index 69bc4ca..cf47a40 100644
--- a/pendulum/locales/zh/custom.py
+++ b/src/pendulum/locales/zh/custom.py
@@ -1,6 +1,8 @@
"""
zh custom locale file.
"""
+from __future__ import annotations
+
translations = {
# Relative time
diff --git a/pendulum/locales/zh/locale.py b/src/pendulum/locales/zh/locale.py
index 2df477f..eb85ab7 100644
--- a/pendulum/locales/zh/locale.py
+++ b/src/pendulum/locales/zh/locale.py
@@ -1,4 +1,6 @@
-from .custom import translations as custom_translations
+from __future__ import annotations
+
+from pendulum.locales.zh.custom import translations as custom_translations
"""
@@ -14,24 +16,24 @@ locale = {
"translations": {
"days": {
"abbreviated": {
- 0: "周日",
- 1: "周一",
- 2: "周二",
- 3: "周三",
- 4: "周四",
- 5: "周五",
- 6: "周六",
+ 0: "周一",
+ 1: "周二",
+ 2: "周三",
+ 3: "周四",
+ 4: "周五",
+ 5: "周六",
+ 6: "周日",
},
- "narrow": {0: "日", 1: "一", 2: "二", 3: "三", 4: "四", 5: "五", 6: "六"},
- "short": {0: "周日", 1: "周一", 2: "周二", 3: "周三", 4: "周四", 5: "周五", 6: "周六"},
+ "narrow": {0: "一", 1: "二", 2: "三", 3: "四", 4: "五", 5: "六", 6: "日"},
+ "short": {0: "周一", 1: "周二", 2: "周三", 3: "周四", 4: "周五", 5: "周六", 6: "周日"},
"wide": {
- 0: "星期日",
- 1: "星期一",
- 2: "星期二",
- 3: "星期三",
- 4: "星期四",
- 5: "星期五",
- 6: "星期六",
+ 0: "星期一",
+ 1: "星期二",
+ 2: "星期三",
+ 3: "星期四",
+ 4: "星期五",
+ 5: "星期六",
+ 6: "星期日",
},
},
"months": {
@@ -108,6 +110,12 @@ locale = {
"evening1": "晚上",
"night1": "凌晨",
},
+ "week_data": {
+ "min_days": 1,
+ "first_day": 0,
+ "weekend_start": 5,
+ "weekend_end": 6,
+ },
},
"custom": custom_translations,
}
diff --git a/pendulum/tz/data/__init__.py b/src/pendulum/mixins/__init__.py
index e69de29..e69de29 100644
--- a/pendulum/tz/data/__init__.py
+++ b/src/pendulum/mixins/__init__.py
diff --git a/pendulum/mixins/default.py b/src/pendulum/mixins/default.py
index 59f985e..f2531f3 100644
--- a/pendulum/mixins/default.py
+++ b/src/pendulum/mixins/default.py
@@ -2,6 +2,7 @@ from __future__ import annotations
from pendulum.formatting import Formatter
+
_formatter = Formatter()
@@ -21,7 +22,7 @@ class FormattableMixin:
"""
Methods for automatic json serialization by simplejson.
"""
- return str(self)
+ return self.isoformat()
def __format__(self, format_spec: str) -> str:
if len(format_spec) > 0:
diff --git a/pendulum/parser.py b/src/pendulum/parser.py
index 77900e2..5f9a0f7 100644
--- a/pendulum/parser.py
+++ b/src/pendulum/parser.py
@@ -5,21 +5,22 @@ import typing as t
import pendulum
+from pendulum.duration import Duration
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
- from pendulum.duration import Duration
from pendulum.interval import Interval
from pendulum.time import Time
try:
- from pendulum.parsing._iso8601 import Duration as CDuration
+ from pendulum._pendulum import Duration as RustDuration
except ImportError:
- CDuration = None # type: ignore[misc, assignment]
+ RustDuration = None # type: ignore[assignment,misc]
def parse(text: str, **options: t.Any) -> Date | Time | DateTime | Duration:
@@ -109,7 +110,10 @@ def _parse(text: str, **options: t.Any) -> Date | DateTime | Time | Duration | I
),
)
- if CDuration and isinstance(parsed, CDuration):
+ if isinstance(parsed, Duration):
+ return parsed
+
+ if RustDuration is not None and isinstance(parsed, RustDuration):
return pendulum.duration(
years=parsed.years,
months=parsed.months,
@@ -121,4 +125,4 @@ def _parse(text: str, **options: t.Any) -> Date | DateTime | Time | Duration | I
microseconds=parsed.microseconds,
)
- return parsed
+ raise NotImplementedError
diff --git a/pendulum/parsing/__init__.py b/src/pendulum/parsing/__init__.py
index 0e64065..761f52c 100644
--- a/pendulum/parsing/__init__.py
+++ b/src/pendulum/parsing/__init__.py
@@ -17,20 +17,22 @@ from dateutil import parser
from pendulum.parsing.exceptions import ParserError
+
with_extensions = os.getenv("PENDULUM_EXTENSIONS", "1") == "1"
try:
if not with_extensions or struct.calcsize("P") == 4:
raise ImportError()
- from pendulum.parsing._iso8601 import Duration
- from pendulum.parsing._iso8601 import parse_iso8601
+ from pendulum._pendulum import Duration
+ from pendulum._pendulum import parse_iso8601
except ImportError:
- from pendulum.duration import Duration # type: ignore[misc]
- from pendulum.parsing.iso8601 import parse_iso8601 # type: ignore[misc]
+ from pendulum.duration import Duration # type: ignore[assignment]
+ 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 +43,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 +175,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
@@ -231,3 +230,6 @@ def _parse_iso8601_interval(text: str) -> _Interval:
return _Interval(
cast(datetime, start), cast(datetime, end), cast(Duration, duration)
)
+
+
+__all__ = ["parse", "parse_iso8601"]
diff --git a/pendulum/parsing/exceptions/__init__.py b/src/pendulum/parsing/exceptions/__init__.py
index 05195b5..9f2d809 100644
--- a/pendulum/parsing/exceptions/__init__.py
+++ b/src/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/src/pendulum/parsing/iso8601.py
index 907cf13..cc4dd7a 100644
--- a/pendulum/parsing/iso8601.py
+++ b/src/pendulum/parsing/iso8601.py
@@ -17,9 +17,11 @@ from pendulum.helpers import week_day
from pendulum.parsing.exceptions import ParserError
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)
@@ -39,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)
@@ -59,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)"
")?"
@@ -107,7 +109,7 @@ def parse_iso8601(
minute = 0
second = 0
microsecond = 0
- tzinfo: FixedTimezone | None = None
+ tzinfo: FixedTimezone | Timezone | None = None
if m.group("date"):
# A date has been specified
@@ -183,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}{month!s:0>2}"
return datetime.time(int(hhmmss[:2]), int(hhmmss[2:4]), int(hhmmss[4:]))
@@ -253,7 +255,7 @@ def parse_iso8601(
tzinfo = FixedTimezone(offset)
if is_time:
- return datetime.time(hour, minute, second, microsecond)
+ return datetime.time(hour, minute, second, microsecond, tzinfo=tzinfo)
return datetime.datetime(
year, month, day, hour, minute, second, microsecond, tzinfo=tzinfo
@@ -418,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/py.typed b/src/pendulum/py.typed
index e69de29..e69de29 100644
--- a/pendulum/py.typed
+++ b/src/pendulum/py.typed
diff --git a/pendulum/utils/__init__.py b/src/pendulum/testing/__init__.py
index e69de29..e69de29 100644
--- a/pendulum/utils/__init__.py
+++ b/src/pendulum/testing/__init__.py
diff --git a/src/pendulum/testing/traveller.py b/src/pendulum/testing/traveller.py
new file mode 100644
index 0000000..3ef3af4
--- /dev/null
+++ b/src/pendulum/testing/traveller.py
@@ -0,0 +1,172 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+from typing import cast
+
+from pendulum.datetime import DateTime
+from pendulum.utils._compat import PYPY
+
+
+if TYPE_CHECKING:
+ from types import TracebackType
+
+ from typing_extensions import Self
+
+
+class BaseTraveller:
+ def __init__(self, datetime_class: type[DateTime] = DateTime) -> None:
+ self._datetime_class: type[DateTime] = datetime_class
+
+ def freeze(self) -> Self:
+ raise self._not_implemented()
+
+ def travel_back(self) -> Self:
+ raise self._not_implemented()
+
+ def travel(
+ self,
+ years: int = 0,
+ months: int = 0,
+ weeks: int = 0,
+ days: int = 0,
+ hours: int = 0,
+ minutes: int = 0,
+ seconds: int = 0,
+ microseconds: int = 0,
+ ) -> Self:
+ raise self._not_implemented()
+
+ def travel_to(self, dt: DateTime, *, freeze: bool = False) -> Self:
+ raise self._not_implemented()
+
+ def __enter__(self) -> Self:
+ return self
+
+ def __exit__(
+ self,
+ exc_type: type[BaseException] | None,
+ exc_val: BaseException | None,
+ exc_tb: TracebackType,
+ ) -> None:
+ ...
+
+ def _not_implemented(self) -> NotImplementedError:
+ return NotImplementedError()
+
+
+if not PYPY:
+ try:
+ import time_machine
+ except ImportError:
+ time_machine = None # type: ignore[assignment]
+
+ if time_machine is not None:
+
+ class Traveller(BaseTraveller):
+ def __init__(self, datetime_class: type[DateTime] = DateTime) -> None:
+ super().__init__(datetime_class)
+
+ self._started: bool = False
+ self._traveller: time_machine.travel | None = None
+ self._coordinates: time_machine.Coordinates | None = None
+
+ def freeze(self) -> Self:
+ if self._started:
+ cast(time_machine.Coordinates, self._coordinates).move_to(
+ self._datetime_class.now(), tick=False
+ )
+ else:
+ self._start(freeze=True)
+
+ return self
+
+ def travel_back(self) -> Self:
+ if not self._started:
+ return self
+
+ cast(time_machine.travel, self._traveller).stop()
+ self._coordinates = None
+ self._traveller = None
+ self._started = False
+
+ return self
+
+ def travel(
+ self,
+ years: int = 0,
+ months: int = 0,
+ weeks: int = 0,
+ days: int = 0,
+ hours: int = 0,
+ minutes: int = 0,
+ seconds: int = 0,
+ microseconds: int = 0,
+ *,
+ freeze: bool = False,
+ ) -> Self:
+ self._start(freeze=freeze)
+
+ cast(time_machine.Coordinates, self._coordinates).move_to(
+ self._datetime_class.now().add(
+ years=years,
+ months=months,
+ weeks=weeks,
+ days=days,
+ hours=hours,
+ minutes=minutes,
+ seconds=seconds,
+ microseconds=microseconds,
+ )
+ )
+
+ return self
+
+ def travel_to(self, dt: DateTime, *, freeze: bool = False) -> Self:
+ self._start(freeze=freeze)
+
+ cast(time_machine.Coordinates, self._coordinates).move_to(dt)
+
+ return self
+
+ def _start(self, freeze: bool = False) -> None:
+ if self._started:
+ return
+
+ if not self._traveller:
+ self._traveller = time_machine.travel(
+ self._datetime_class.now(), tick=not freeze
+ )
+
+ self._coordinates = self._traveller.start()
+
+ self._started = True
+
+ def __enter__(self) -> Self:
+ self._start()
+
+ return self
+
+ def __exit__(
+ self,
+ exc_type: type[BaseException] | None,
+ exc_val: BaseException | None,
+ exc_tb: TracebackType,
+ ) -> None:
+ self.travel_back()
+
+ else:
+
+ class Traveller(BaseTraveller): # type: ignore[no-redef]
+ def _not_implemented(self) -> NotImplementedError:
+ return NotImplementedError(
+ "Time travelling is an optional feature. "
+ 'You can add it by installing Pendulum with the "test" extra.'
+ )
+
+else:
+
+ class Traveller(BaseTraveller): # type: ignore[no-redef]
+ def _not_implemented(self) -> NotImplementedError:
+ return NotImplementedError(
+ "Time travelling is not supported on the PyPy Python implementation."
+ )
diff --git a/pendulum/time.py b/src/pendulum/time.py
index f979e25..23c79c0 100644
--- a/pendulum/time.py
+++ b/src/pendulum/time.py
@@ -17,9 +17,16 @@ from pendulum.constants import USECS_PER_SEC
from pendulum.duration import AbsoluteDuration
from pendulum.duration import Duration
from pendulum.mixins.default import FormattableMixin
+from pendulum.tz.timezone import UTC
+
if TYPE_CHECKING:
- from typing import Literal
+ from typing_extensions import Literal
+ from typing_extensions import Self
+ from typing_extensions import SupportsIndex
+
+ from pendulum.tz.timezone import FixedTimezone
+ from pendulum.tz.timezone import Timezone
class Time(FormattableMixin, time):
@@ -27,6 +34,17 @@ class Time(FormattableMixin, time):
Represents a time instance as hour, minute, second, microsecond.
"""
+ @classmethod
+ def instance(
+ cls, t: time, tz: str | Timezone | FixedTimezone | datetime.tzinfo | None = UTC
+ ) -> Self:
+ tz = t.tzinfo or tz
+
+ if tz is not None:
+ tz = pendulum._safe_timezone(tz)
+
+ return cls(t.hour, t.minute, t.second, t.microsecond, tzinfo=tz, fold=t.fold)
+
# String formatting
def __repr__(self) -> str:
us = ""
@@ -35,7 +53,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__}"
@@ -44,7 +62,7 @@ class Time(FormattableMixin, time):
# Comparisons
- def closest(self, dt1: Time | time, dt2: Time | time) -> Time:
+ def closest(self, dt1: Time | time, dt2: Time | time) -> Self:
"""
Get the closest time from the instance.
"""
@@ -56,7 +74,7 @@ class Time(FormattableMixin, time):
return dt2
- def farthest(self, dt1: Time | time, dt2: Time | time) -> Time:
+ def farthest(self, dt1: Time | time, dt2: Time | time) -> Self:
"""
Get the farthest time from the instance.
"""
@@ -250,13 +268,13 @@ class Time(FormattableMixin, time):
def replace(
self,
- hour: int | None = None,
- minute: int | None = None,
- second: int | None = None,
- microsecond: int | None = None,
+ hour: SupportsIndex | None = None,
+ minute: SupportsIndex | None = None,
+ second: SupportsIndex | None = None,
+ microsecond: SupportsIndex | None = None,
tzinfo: bool | datetime.tzinfo | Literal[True] | None = True,
fold: int = 0,
- ) -> Time:
+ ) -> Self:
if tzinfo is True:
tzinfo = self.tzinfo
@@ -281,7 +299,7 @@ class Time(FormattableMixin, time):
return (self,)
def _get_state(
- self, protocol: int = 3
+ self, protocol: SupportsIndex = 3
) -> tuple[int, int, int, int, datetime.tzinfo | None]:
tz = self.tzinfo
@@ -292,8 +310,8 @@ class Time(FormattableMixin, time):
) -> tuple[type[Time], tuple[int, int, int, int, datetime.tzinfo | None]]:
return self.__reduce_ex__(2)
- def __reduce_ex__( # type: ignore[override]
- self, protocol: int
+ def __reduce_ex__(
+ self, protocol: SupportsIndex
) -> tuple[type[Time], tuple[int, int, int, int, datetime.tzinfo | None]]:
return self.__class__, self._get_state(protocol)
diff --git a/pendulum/tz/__init__.py b/src/pendulum/tz/__init__.py
index 45c9855..36c2c69 100644
--- a/pendulum/tz/__init__.py
+++ b/src/pendulum/tz/__init__.py
@@ -1,6 +1,7 @@
from __future__ import annotations
-import sys
+from pathlib import Path
+from typing import cast
from pendulum.tz.local_timezone import get_local_timezone
from pendulum.tz.local_timezone import set_local_timezone
@@ -8,11 +9,8 @@ from pendulum.tz.local_timezone import test_local_timezone
from pendulum.tz.timezone import UTC
from pendulum.tz.timezone import FixedTimezone
from pendulum.tz.timezone import Timezone
+from pendulum.utils._compat import resources
-if sys.version_info >= (3, 9):
- from importlib import resources
-else:
- import importlib_resources as resources
PRE_TRANSITION = "pre"
POST_TRANSITION = "post"
@@ -27,25 +25,12 @@ def timezones() -> tuple[str, ...]:
global _timezones
if _timezones is None:
- with resources.files("tzdata").joinpath("zones").open() as f:
+ with cast(Path, resources.files("tzdata").joinpath("zones")).open() as f:
_timezones = tuple(tz.strip() for tz in f.readlines())
return _timezones
-def timezone(name: str | int) -> Timezone | FixedTimezone:
- """
- Return a Timezone instance given its name.
- """
- if isinstance(name, int):
- return fixed_timezone(name)
-
- if name.lower() == "utc":
- return UTC
-
- return Timezone(name)
-
-
def fixed_timezone(offset: int) -> FixedTimezone:
"""
Return a Timezone instance given its offset in seconds.
@@ -73,7 +58,6 @@ __all__ = [
"set_local_timezone",
"get_local_timezone",
"test_local_timezone",
- "timezone",
"fixed_timezone",
"local_timezone",
"timezones",
diff --git a/src/pendulum/tz/data/__init__.py b/src/pendulum/tz/data/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/pendulum/tz/data/__init__.py
diff --git a/pendulum/tz/data/windows.py b/src/pendulum/tz/data/windows.py
index 65aa6c3..e76e6bb 100644
--- a/pendulum/tz/data/windows.py
+++ b/src/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/src/pendulum/tz/exceptions.py
index b8833ac..d06c133 100644
--- a/pendulum/tz/exceptions.py
+++ b/src/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/src/pendulum/tz/local_timezone.py
index 41cf81b..3cb488c 100644
--- a/pendulum/tz/local_timezone.py
+++ b/src/pendulum/tz/local_timezone.py
@@ -4,20 +4,20 @@ import contextlib
import os
import re
import sys
+import warnings
from contextlib import contextmanager
from typing import Iterator
from typing import cast
from pendulum.tz.exceptions import InvalidTimezone
+from pendulum.tz.timezone import UTC
from pendulum.tz.timezone import FixedTimezone
from pendulum.tz.timezone import Timezone
+
if sys.platform == "win32":
- try:
- import _winreg as winreg
- except (ImportError, AttributeError):
- import winreg
+ import winreg
_mock_local_timezone = None
_local_timezone = None
@@ -142,7 +142,7 @@ if sys.platform == "win32":
else:
def _get_windows_timezone() -> Timezone:
- ...
+ raise NotImplementedError
def _get_darwin_timezone() -> Timezone:
@@ -239,9 +239,13 @@ def _get_unix_timezone(_root: str = "/") -> Timezone:
continue
with open(tzpath, "rb") as f:
- return cast(Timezone, Timezone.from_file(f))
+ return Timezone.from_file(f)
+
+ warnings.warn(
+ "Unable not find any timezone configuration, defaulting to UTC.", stacklevel=1
+ )
- raise RuntimeError("Unable to find any timezone configuration")
+ return UTC
def _tz_from_env(tzenv: str) -> Timezone:
@@ -251,7 +255,7 @@ def _tz_from_env(tzenv: str) -> Timezone:
# TZ specifies a file
if os.path.isfile(tzenv):
with open(tzenv, "rb") as f:
- return cast(Timezone, Timezone.from_file(f))
+ return Timezone.from_file(f)
# TZ specifies a zoneinfo zone.
try:
diff --git a/pendulum/tz/timezone.py b/src/pendulum/tz/timezone.py
index f689004..0c6eb8d 100644
--- a/pendulum/tz/timezone.py
+++ b/src/pendulum/tz/timezone.py
@@ -1,9 +1,12 @@
+# mypy: no-warn-redundant-casts
from __future__ import annotations
-import datetime as datetime_
+import datetime as _datetime
from abc import ABC
from abc import abstractmethod
+from typing import TYPE_CHECKING
+from typing import TypeVar
from typing import cast
from pendulum.tz.exceptions import AmbiguousTime
@@ -11,11 +14,18 @@ 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
+
POST_TRANSITION = "post"
PRE_TRANSITION = "pre"
TRANSITION_ERROR = "error"
+_DT = TypeVar("_DT", bound=_datetime.datetime)
+
+
class PendulumTimezone(ABC):
@property
@abstractmethod
@@ -23,9 +33,7 @@ class PendulumTimezone(ABC):
raise NotImplementedError
@abstractmethod
- def convert(
- self, dt: datetime_.datetime, raise_on_unknown_times: bool = False
- ) -> datetime_.datetime:
+ def convert(self, dt: _DT, raise_on_unknown_times: bool = False) -> _DT:
raise NotImplementedError
@abstractmethod
@@ -38,11 +46,11 @@ class PendulumTimezone(ABC):
minute: int = 0,
second: int = 0,
microsecond: int = 0,
- ) -> datetime_.datetime:
+ ) -> _datetime.datetime:
raise NotImplementedError
-class Timezone(zoneinfo.ZoneInfo, PendulumTimezone): # type: ignore[misc]
+class Timezone(zoneinfo.ZoneInfo, PendulumTimezone):
"""
Represents a named timezone.
@@ -52,19 +60,17 @@ class Timezone(zoneinfo.ZoneInfo, PendulumTimezone): # type: ignore[misc]
>>> tz = Timezone('Europe/Paris')
"""
- def __new__(cls, key: str) -> Timezone:
+ def __new__(cls, key: str) -> Self:
try:
- return cast(Timezone, super().__new__(cls, key))
+ return super().__new__(cls, key) # type: ignore[call-arg]
except zoneinfo.ZoneInfoNotFoundError:
raise InvalidTimezone(key)
@property
def name(self) -> str:
- return cast(str, self.key)
+ return self.key
- def convert(
- self, dt: datetime_.datetime, raise_on_unknown_times: bool = False
- ) -> datetime_.datetime:
+ def convert(self, dt: _DT, raise_on_unknown_times: bool = False) -> _DT:
"""
Converts a datetime in the current timezone.
@@ -85,12 +91,26 @@ class Timezone(zoneinfo.ZoneInfo, PendulumTimezone): # type: ignore[misc]
>>> in_new_york.isoformat()
'2013-03-30T21:30:00-04:00'
"""
+
if dt.tzinfo is None:
- offset_before = (
- self.utcoffset(dt.replace(fold=0)) if dt.fold else self.utcoffset(dt)
+ # Technically, utcoffset() can return None, but none of the zone information
+ # in tzdata sets _tti_before to None. This can be checked with the following
+ # code:
+ #
+ # >>> import zoneinfo
+ # >>> from zoneinfo._zoneinfo import ZoneInfo
+ #
+ # >>> for tzname in zoneinfo.available_timezones():
+ # >>> if ZoneInfo(tzname)._tti_before is None:
+ # >>> print(tzname)
+
+ offset_before = cast(
+ _datetime.timedelta,
+ (self.utcoffset(dt.replace(fold=0)) if dt.fold else self.utcoffset(dt)),
)
- offset_after = (
- self.utcoffset(dt) if dt.fold else self.utcoffset(dt.replace(fold=1))
+ offset_after = cast(
+ _datetime.timedelta,
+ (self.utcoffset(dt) if dt.fold else self.utcoffset(dt.replace(fold=1))),
)
if offset_after > offset_before:
@@ -98,10 +118,14 @@ class Timezone(zoneinfo.ZoneInfo, PendulumTimezone): # type: ignore[misc]
if raise_on_unknown_times:
raise NonExistingTime(dt)
- dt += (
- (offset_after - offset_before)
- if dt.fold
- else (offset_before - offset_after)
+ dt = cast(
+ _DT,
+ dt
+ + (
+ (offset_after - offset_before)
+ if dt.fold
+ else (offset_before - offset_after)
+ ),
)
elif offset_before > offset_after and raise_on_unknown_times:
# Repeated time
@@ -109,7 +133,7 @@ class Timezone(zoneinfo.ZoneInfo, PendulumTimezone): # type: ignore[misc]
return dt.replace(tzinfo=self)
- return dt.astimezone(self)
+ return cast(_DT, dt.astimezone(self))
def datetime(
self,
@@ -120,12 +144,12 @@ class Timezone(zoneinfo.ZoneInfo, PendulumTimezone): # type: ignore[misc]
minute: int = 0,
second: int = 0,
microsecond: int = 0,
- ) -> datetime_.datetime:
+ ) -> _datetime.datetime:
"""
Return a normalized datetime for the current timezone.
"""
return self.convert(
- datetime_.datetime(
+ _datetime.datetime(
year, month, day, hour, minute, second, microsecond, fold=1
)
)
@@ -134,7 +158,7 @@ class Timezone(zoneinfo.ZoneInfo, PendulumTimezone): # type: ignore[misc]
return f"{self.__class__.__name__}('{self.name}')"
-class FixedTimezone(datetime_.tzinfo, PendulumTimezone):
+class FixedTimezone(_datetime.tzinfo, PendulumTimezone):
def __init__(self, offset: int, name: str | None = None) -> None:
sign = "-" if offset < 0 else "+"
@@ -146,15 +170,13 @@ class FixedTimezone(datetime_.tzinfo, PendulumTimezone):
self._name = name
self._offset = offset
- self._utcoffset = datetime_.timedelta(seconds=offset)
+ self._utcoffset = _datetime.timedelta(seconds=offset)
@property
def name(self) -> str:
return self._name
- def convert(
- self, dt: datetime_.datetime, raise_on_unknown_times: bool = False
- ) -> datetime_.datetime:
+ def convert(self, dt: _DT, raise_on_unknown_times: bool = False) -> _DT:
if dt.tzinfo is None:
return dt.__class__(
dt.year,
@@ -168,7 +190,7 @@ class FixedTimezone(datetime_.tzinfo, PendulumTimezone):
fold=0,
)
- return dt.astimezone(self)
+ return cast(_DT, dt.astimezone(self))
def datetime(
self,
@@ -179,9 +201,9 @@ class FixedTimezone(datetime_.tzinfo, PendulumTimezone):
minute: int = 0,
second: int = 0,
microsecond: int = 0,
- ) -> datetime_.datetime:
+ ) -> _datetime.datetime:
return self.convert(
- datetime_.datetime(
+ _datetime.datetime(
year, month, day, hour, minute, second, microsecond, fold=1
)
)
@@ -190,17 +212,17 @@ class FixedTimezone(datetime_.tzinfo, PendulumTimezone):
def offset(self) -> int:
return self._offset
- def utcoffset(self, dt: datetime_.datetime | None) -> datetime_.timedelta:
+ def utcoffset(self, dt: _datetime.datetime | None) -> _datetime.timedelta:
return self._utcoffset
- def dst(self, dt: datetime_.datetime | None) -> datetime_.timedelta:
- return datetime_.timedelta()
+ def dst(self, dt: _datetime.datetime | None) -> _datetime.timedelta:
+ return _datetime.timedelta()
- def fromutc(self, dt: datetime_.datetime) -> datetime_.datetime:
+ def fromutc(self, dt: _datetime.datetime) -> _datetime.datetime:
# Use the stdlib datetime's add method to avoid infinite recursion
- return (datetime_.datetime.__add__(dt, self._utcoffset)).replace(tzinfo=self)
+ return (_datetime.datetime.__add__(dt, self._utcoffset)).replace(tzinfo=self)
- def tzname(self, dt: datetime_.datetime | None) -> str | None:
+ def tzname(self, dt: _datetime.datetime | None) -> str | None:
return self._name
def __getinitargs__(self) -> tuple[int, str]:
diff --git a/src/pendulum/utils/__init__.py b/src/pendulum/utils/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/pendulum/utils/__init__.py
diff --git a/src/pendulum/utils/_compat.py b/src/pendulum/utils/_compat.py
new file mode 100644
index 0000000..e4dfedf
--- /dev/null
+++ b/src/pendulum/utils/_compat.py
@@ -0,0 +1,15 @@
+from __future__ import annotations
+
+import sys
+
+from pendulum.utils import _zoneinfo as zoneinfo
+
+
+PYPY = hasattr(sys, "pypy_version_info")
+
+if sys.version_info < (3, 9):
+ import importlib_resources as resources
+else:
+ from importlib import resources
+
+__all__ = ["resources", "zoneinfo"]
diff --git a/src/pendulum/utils/_zoneinfo.py b/src/pendulum/utils/_zoneinfo.py
new file mode 100644
index 0000000..e9f0251
--- /dev/null
+++ b/src/pendulum/utils/_zoneinfo.py
@@ -0,0 +1,80 @@
+from __future__ import annotations
+
+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
+ from backports.zoneinfo import InvalidTZPathWarning
+ from backports.zoneinfo import ZoneInfoNotFoundError
+ from backports.zoneinfo import available_timezones
+ from backports.zoneinfo import reset_tzpath
+
+ if TYPE_CHECKING:
+ from collections.abc import Iterable
+ from datetime import datetime
+ from datetime import timedelta
+ from datetime import tzinfo
+ from typing import Any
+ from typing import Protocol
+
+ from typing_extensions import Self
+
+ class _IOBytes(Protocol):
+ def read(self, __size: int) -> bytes:
+ ...
+
+ def seek(self, __size: int, __whence: int = ...) -> Any:
+ ...
+
+ class ZoneInfo(tzinfo):
+ @property
+ def key(self) -> str:
+ ...
+
+ def __init__(self, key: str) -> None:
+ ...
+
+ @classmethod
+ def no_cache(cls, key: str) -> Self:
+ ...
+
+ @classmethod
+ def from_file(cls, __fobj: _IOBytes, key: str | None = ...) -> Self:
+ ...
+
+ @classmethod
+ def clear_cache(cls, *, only_keys: Iterable[str] | None = ...) -> None:
+ ...
+
+ def tzname(self, __dt: datetime | None) -> str | None:
+ ...
+
+ def utcoffset(self, __dt: datetime | None) -> timedelta | None:
+ ...
+
+ def dst(self, __dt: datetime | None) -> timedelta | None:
+ ...
+
+ else:
+ from backports.zoneinfo import ZoneInfo
+
+else:
+ from zoneinfo import TZPATH
+ from zoneinfo import InvalidTZPathWarning
+ from zoneinfo import ZoneInfo
+ from zoneinfo import ZoneInfoNotFoundError
+ from zoneinfo import available_timezones
+ from zoneinfo import reset_tzpath
+
+__all__ = [
+ "ZoneInfo",
+ "reset_tzpath",
+ "available_timezones",
+ "TZPATH",
+ "ZoneInfoNotFoundError",
+ "InvalidTZPathWarning",
+]
diff --git a/tests/benchmarks/__init__.py b/tests/benchmarks/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/benchmarks/__init__.py
diff --git a/tests/benchmarks/test_parse_8601.py b/tests/benchmarks/test_parse_8601.py
new file mode 100644
index 0000000..1f838a8
--- /dev/null
+++ b/tests/benchmarks/test_parse_8601.py
@@ -0,0 +1,51 @@
+from __future__ import annotations
+
+import pytest
+
+from pendulum.parsing.iso8601 import parse_iso8601
+
+
+@pytest.mark.benchmark(group="Parsing")
+def test_parse_iso8601() -> None:
+ # Date
+ parse_iso8601("2016")
+ parse_iso8601("2016-10")
+ parse_iso8601("2016-10-06")
+ parse_iso8601("20161006")
+
+ # Time
+ parse_iso8601("201610")
+
+ # Datetime
+ parse_iso8601("2016-10-06T12:34:56.123456")
+ parse_iso8601("2016-10-06T12:34:56.123")
+ parse_iso8601("2016-10-06T12:34:56.000123")
+ parse_iso8601("2016-10-06T12")
+ parse_iso8601("2016-10-06T123456")
+ parse_iso8601("2016-10-06T123456.123456")
+ parse_iso8601("20161006T123456.123456")
+ parse_iso8601("20161006 123456.123456")
+
+ # Datetime with offset
+ parse_iso8601("2016-10-06T12:34:56.123456+05:30")
+ parse_iso8601("2016-10-06T12:34:56.123456+0530")
+ parse_iso8601("2016-10-06T12:34:56.123456-05:30")
+ parse_iso8601("2016-10-06T12:34:56.123456-0530")
+ parse_iso8601("2016-10-06T12:34:56.123456+05")
+ parse_iso8601("2016-10-06T12:34:56.123456-05")
+ parse_iso8601("20161006T123456,123456-05")
+ parse_iso8601("2016-10-06T12:34:56.123456789+05:30")
+
+ # Ordinal date
+ parse_iso8601("2012-007")
+ parse_iso8601("2012007")
+ parse_iso8601("2017-079")
+
+ # Week date
+ parse_iso8601("2012-W05")
+ parse_iso8601("2008-W39-6")
+ parse_iso8601("2009-W53-7")
+ parse_iso8601("2009-W01-1")
+
+ # Week date wth time
+ parse_iso8601("2008-W39-6T09")
diff --git a/tests/conftest.py b/tests/conftest.py
index 060e951..99f4b93 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,25 +1,38 @@
from __future__ import annotations
+from typing import TYPE_CHECKING
+
import pytest
import pendulum
+if TYPE_CHECKING:
+ from collections.abc import Iterator
+
+
@pytest.fixture(autouse=True)
-def setup():
+def setup() -> Iterator[None]:
pendulum.set_local_timezone(pendulum.timezone("America/Toronto"))
yield
pendulum.set_locale("en")
pendulum.set_local_timezone()
- pendulum.week_starts_at(pendulum.MONDAY)
- pendulum.week_ends_at(pendulum.SUNDAY)
+ pendulum.week_starts_at(pendulum.WeekDay.MONDAY)
+ pendulum.week_ends_at(pendulum.WeekDay.SUNDAY)
def assert_datetime(
- d, year, month, day, hour=None, minute=None, second=None, microsecond=None
-):
+ d: pendulum.DateTime,
+ year: int,
+ month: int,
+ day: int,
+ hour: int | None = None,
+ minute: int | None = None,
+ second: int | None = None,
+ microsecond: int | None = None,
+) -> None:
assert year == d.year
assert month == d.month
assert day == d.day
@@ -37,13 +50,19 @@ def assert_datetime(
assert microsecond == d.microsecond
-def assert_date(d, year, month, day):
+def assert_date(d: pendulum.Date, year: int, month: int, day: int) -> None:
assert year == d.year
assert month == d.month
assert day == d.day
-def assert_time(t, hour, minute, second, microsecond=None):
+def assert_time(
+ t: pendulum.Time,
+ hour: int,
+ minute: int,
+ second: int,
+ microsecond: int | None = None,
+) -> None:
assert hour == t.hour
assert minute == t.minute
assert second == t.second
@@ -53,15 +72,15 @@ def assert_time(t, hour, minute, second, microsecond=None):
def assert_duration(
- dur,
- years=None,
- months=None,
- weeks=None,
- days=None,
- hours=None,
- minutes=None,
- seconds=None,
- microseconds=None,
+ dur: pendulum.Duration,
+ years: int | None = None,
+ months: int | None = None,
+ weeks: int | None = None,
+ days: int | None = None,
+ hours: int | None = None,
+ minutes: int | None = None,
+ seconds: int | None = None,
+ microseconds: int | None = None,
) -> None:
expected = {}
actual = {}
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/date/test_day_of_week_modifiers.py b/tests/date/test_day_of_week_modifiers.py
index 55aba55..0363096 100644
--- a/tests/date/test_day_of_week_modifiers.py
+++ b/tests/date/test_day_of_week_modifiers.py
@@ -49,7 +49,7 @@ def test_next_monday():
def test_next_saturday():
- d = pendulum.date(1975, 5, 21).next(6)
+ d = pendulum.date(1975, 5, 21).next(5)
assert_date(d, 1975, 5, 24)
@@ -71,7 +71,7 @@ def test_previous_monday():
def test_previous_saturday():
- d = pendulum.date(1975, 5, 21).previous(6)
+ d = pendulum.date(1975, 5, 21).previous(5)
assert_date(d, 1975, 5, 17)
@@ -93,7 +93,7 @@ def test_first_wednesday_of_month():
def test_first_friday_of_month():
- d = pendulum.date(1975, 11, 21).first_of("month", 5)
+ d = pendulum.date(1975, 11, 21).first_of("month", 4)
assert_date(d, 1975, 11, 7)
@@ -108,7 +108,7 @@ def test_last_tuesday_of_month():
def test_last_friday_of_month():
- d = pendulum.date(1975, 12, 5).last_of("month", 5)
+ d = pendulum.date(1975, 12, 5).last_of("month", 4)
assert_date(d, 1975, 12, 26)
@@ -139,7 +139,7 @@ def test_2nd_monday_of_month():
def test_3rd_wednesday_of_month():
- d = pendulum.date(1975, 12, 5).nth_of("month", 3, 3)
+ d = pendulum.date(1975, 12, 5).nth_of("month", 3, 2)
assert_date(d, 1975, 12, 17)
@@ -155,7 +155,7 @@ def test_first_wednesday_of_quarter():
def test_first_friday_of_quarter():
- d = pendulum.date(1975, 11, 21).first_of("quarter", 5)
+ d = pendulum.date(1975, 11, 21).first_of("quarter", 4)
assert_date(d, 1975, 10, 3)
@@ -215,7 +215,7 @@ def test_2nd_monday_of_quarter():
def test_3rd_wednesday_of_quarter():
- d = pendulum.date(1975, 8, 5).nth_of("quarter", 3, 3)
+ d = pendulum.date(1975, 8, 5).nth_of("quarter", 3, 2)
assert_date(d, 1975, 7, 16)
@@ -230,7 +230,7 @@ def test_first_wednesday_of_year():
def test_first_friday_of_year():
- d = pendulum.date(1975, 11, 21).first_of("year", 5)
+ d = pendulum.date(1975, 11, 21).first_of("year", 4)
assert_date(d, 1975, 1, 3)
@@ -245,7 +245,7 @@ def test_last_tuesday_of_year():
def test_last_friday_of_year():
- d = pendulum.date(1975, 8, 5).last_of("year", 5)
+ d = pendulum.date(1975, 8, 5).last_of("year", 4)
assert_date(d, 1975, 12, 26)
diff --git a/tests/date/test_diff.py b/tests/date/test_diff.py
index 56358cc..cfececf 100644
--- a/tests/date/test_diff.py
+++ b/tests/date/test_diff.py
@@ -9,7 +9,7 @@ import pendulum
@pytest.fixture
def today():
- return pendulum.today().date()
+ return pendulum.Date.today()
def test_diff_in_years_positive():
diff --git a/tests/datetime/test_add.py b/tests/datetime/test_add.py
index 87ea39f..409f5bd 100644
--- a/tests/datetime/test_add.py
+++ b/tests/datetime/test_add.py
@@ -252,17 +252,29 @@ def test_add_time_to_new_transition_repeated_big():
assert not dt.is_dst()
-def test_add_interval():
+def test_add_duration_across_transition():
dt = pendulum.datetime(2017, 3, 11, 10, 45, tz="America/Los_Angeles")
new = dt + pendulum.duration(hours=24)
assert_datetime(new, 2017, 3, 12, 11, 45)
-def test_period_over_midnight_tz():
+def test_add_duration_across_transition_days():
+ dt = pendulum.datetime(2017, 3, 11, 10, 45, tz="America/Los_Angeles")
+ new = dt + pendulum.duration(days=1)
+
+ assert_datetime(new, 2017, 3, 12, 10, 45)
+
+ dt = pendulum.datetime(2023, 11, 5, 0, 0, tz="America/Chicago")
+ new = dt + pendulum.duration(days=1)
+
+ assert_datetime(new, 2023, 11, 6, 0, 0)
+
+
+def test_interval_over_midnight_tz():
start = pendulum.datetime(2018, 2, 25, tz="Europe/Paris")
end = start.add(hours=1)
- period = end - start
- new_end = start + period
+ interval = end - start
+ new_end = start + interval
assert new_end == end
diff --git a/tests/datetime/test_behavior.py b/tests/datetime/test_behavior.py
index e02323a..76de1d6 100644
--- a/tests/datetime/test_behavior.py
+++ b/tests/datetime/test_behavior.py
@@ -158,6 +158,14 @@ def test_deepcopy():
assert dt == deepcopy(dt)
+def test_deepcopy_on_transition():
+ dt = pendulum.datetime(2023, 11, 5, 1, 0, 0, tz="US/Pacific")
+ clone = deepcopy(dt)
+
+ assert dt == clone
+ assert dt.offset == clone.offset
+
+
def test_pickle_timezone():
dt1 = pendulum.timezone("Europe/Amsterdam")
s = pickle.dumps(dt1)
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 9488c08..b083cbe 100644
--- a/tests/datetime/test_construct.py
+++ b/tests/datetime/test_construct.py
@@ -5,17 +5,15 @@ import os
from datetime import datetime
import pytest
-import pytz
-
-from dateutil import tz
import pendulum
from pendulum import DateTime
-from pendulum.tz import timezone
+from pendulum import timezone
from pendulum.utils._compat import PYPY
from tests.conftest import assert_datetime
+
if not PYPY:
import time_machine
else:
@@ -82,27 +80,6 @@ def test_yesterday():
assert now.diff(yesterday, False).in_days() == -1
-def test_instance_naive_datetime_defaults_to_utc():
- now = pendulum.instance(datetime.now())
- assert now.timezone_name == "UTC"
-
-
-def test_instance_timezone_aware_datetime():
- now = pendulum.instance(datetime.now(timezone("Europe/Paris")))
- assert now.timezone_name == "Europe/Paris"
-
-
-def test_instance_timezone_aware_datetime_pytz():
- now = pendulum.instance(datetime.now(pytz.timezone("Europe/Paris")))
- assert now.timezone_name == "Europe/Paris"
-
-
-def test_instance_timezone_aware_datetime_any_tzinfo():
- dt = datetime(2016, 8, 7, 12, 34, 56, tzinfo=tz.gettz("Europe/Paris"))
- now = pendulum.instance(dt)
- assert now.timezone_name == "+02:00"
-
-
def test_now():
now = pendulum.now("America/Toronto")
in_paris = pendulum.now("Europe/Paris")
diff --git a/tests/datetime/test_day_of_week_modifiers.py b/tests/datetime/test_day_of_week_modifiers.py
index 46de84e..eb4bc4f 100644
--- a/tests/datetime/test_day_of_week_modifiers.py
+++ b/tests/datetime/test_day_of_week_modifiers.py
@@ -49,7 +49,7 @@ def test_next_monday():
def test_next_saturday():
- d = pendulum.datetime(1975, 5, 21).next(6)
+ d = pendulum.datetime(1975, 5, 21).next(5)
assert_datetime(d, 1975, 5, 24, 0, 0, 0)
@@ -79,7 +79,7 @@ def test_previous_monday():
def test_previous_saturday():
- d = pendulum.datetime(1975, 5, 21).previous(6)
+ d = pendulum.datetime(1975, 5, 21).previous(5)
assert_datetime(d, 1975, 5, 17, 0, 0, 0)
@@ -109,7 +109,7 @@ def test_first_wednesday_of_month():
def test_first_friday_of_month():
- d = pendulum.datetime(1975, 11, 21).first_of("month", 5)
+ d = pendulum.datetime(1975, 11, 21).first_of("month", 4)
assert_datetime(d, 1975, 11, 7, 0, 0, 0)
@@ -124,7 +124,7 @@ def test_last_tuesday_of_month():
def test_last_friday_of_month():
- d = pendulum.datetime(1975, 12, 5).last_of("month", 5)
+ d = pendulum.datetime(1975, 12, 5).last_of("month", 4)
assert_datetime(d, 1975, 12, 26, 0, 0, 0)
@@ -155,7 +155,7 @@ def test_2nd_monday_of_month():
def test_3rd_wednesday_of_month():
- d = pendulum.datetime(1975, 12, 5).nth_of("month", 3, 3)
+ d = pendulum.datetime(1975, 12, 5).nth_of("month", 3, 2)
assert_datetime(d, 1975, 12, 17, 0, 0, 0)
@@ -171,7 +171,7 @@ def test_first_wednesday_of_quarter():
def test_first_friday_of_quarter():
- d = pendulum.datetime(1975, 11, 21).first_of("quarter", 5)
+ d = pendulum.datetime(1975, 11, 21).first_of("quarter", 4)
assert_datetime(d, 1975, 10, 3, 0, 0, 0)
@@ -231,7 +231,7 @@ def test_2nd_monday_of_quarter():
def test_3rd_wednesday_of_quarter():
- d = pendulum.datetime(1975, 8, 5).nth_of("quarter", 3, 3)
+ d = pendulum.datetime(1975, 8, 5).nth_of("quarter", 3, 2)
assert_datetime(d, 1975, 7, 16, 0, 0, 0)
@@ -246,7 +246,7 @@ def test_first_wednesday_of_year():
def test_first_friday_of_year():
- d = pendulum.datetime(1975, 11, 21).first_of("year", 5)
+ d = pendulum.datetime(1975, 11, 21).first_of("year", 4)
assert_datetime(d, 1975, 1, 3, 0, 0, 0)
@@ -261,7 +261,7 @@ def test_last_tuesday_of_year():
def test_last_friday_of_year():
- d = pendulum.datetime(1975, 8, 5).last_of("year", 5)
+ d = pendulum.datetime(1975, 8, 5).last_of("year", 4)
assert_datetime(d, 1975, 12, 26, 0, 0, 0)
diff --git a/tests/datetime/test_from_format.py b/tests/datetime/test_from_format.py
index 10c4a23..184c0a0 100644
--- a/tests/datetime/test_from_format.py
+++ b/tests/datetime/test_from_format.py
@@ -81,9 +81,8 @@ def test_from_format_with_invalid_padded_day():
("12/02/1999", "DD/MM/YYYY", "1999-02-12T00:00:00+00:00", None),
("12_02_1999", "DD_MM_YYYY", "1999-02-12T00:00:00+00:00", None),
("12:02:1999", "DD:MM:YYYY", "1999-02-12T00:00:00+00:00", None),
- ("2-2-99", "D-M-YY", "2099-02-02T00:00:00+00:00", None),
- ("2-2-99", "D-M-YY", "1999-02-02T00:00:00+00:00", "1990-01-01"),
- ("99", "YY", "2099-01-01T00:00:00+00:00", None),
+ ("2-2-99", "D-M-YY", "1999-02-02T00:00:00+00:00", None),
+ ("99", "YY", "1999-01-01T00:00:00+00:00", None),
("300-1999", "DDD-YYYY", "1999-10-27T00:00:00+00:00", None),
("12-02-1999 2:45:10", "DD-MM-YYYY h:m:s", "1999-02-12T02:45:10+00:00", None),
("12-02-1999 12:45:10", "DD-MM-YYYY h:m:s", "1999-02-12T12:45:10+00:00", None),
@@ -100,7 +99,8 @@ def test_from_format_with_invalid_padded_day():
("Monday", "dddd", "2018-01-29T00:00:00+00:00", "2018-02-02"),
("Mon", "ddd", "2018-01-29T00:00:00+00:00", "2018-02-02"),
("Mo", "dd", "2018-01-29T00:00:00+00:00", "2018-02-02"),
- ("0", "d", "2018-02-04T00:00:00+00:00", "2018-02-02"),
+ ("0", "d", "2018-01-29T00:00:00+00:00", "2018-02-02"),
+ ("6", "d", "2018-02-04T00:00:00+00:00", "2018-02-02"),
("1", "E", "2018-01-29T00:00:00+00:00", "2018-02-02"),
("March", "MMMM", "2018-03-01T00:00:00+00:00", "2018-02-02"),
("Mar", "MMM", "2018-03-01T00:00:00+00:00", "2018-02-02"),
@@ -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
@@ -201,3 +198,25 @@ def test_strptime():
assert_datetime(d, 1975, 5, 21, 22, 32, 11)
assert isinstance(d, pendulum.DateTime)
assert d.timezone_name == "UTC"
+
+
+def test_from_format_2_digit_year():
+ """
+ Complies with open group spec for 2 digit years
+ https://pubs.opengroup.org/onlinepubs/9699919799/
+
+ "If century is not specified, then values in the range [69,99] shall
+ refer to years 1969 to 1999 inclusive, and values in the
+ range [00,68] shall refer to years 2000 to 2068 inclusive."
+ """
+ d = pendulum.from_format("00", "YY")
+ assert d.year == 2000
+
+ d = pendulum.from_format("68", "YY")
+ assert d.year == 2068
+
+ d = pendulum.from_format("69", "YY")
+ assert d.year == 1969
+
+ d = pendulum.from_format("99", "YY")
+ assert d.year == 1999
diff --git a/tests/datetime/test_getters.py b/tests/datetime/test_getters.py
index 5074623..8f4c1ae 100644
--- a/tests/datetime/test_getters.py
+++ b/tests/datetime/test_getters.py
@@ -7,7 +7,7 @@ import pytest
import pendulum
from pendulum import DateTime
-from pendulum.tz import timezone
+from pendulum import timezone
from tests.conftest import assert_date
from tests.conftest import assert_time
@@ -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()
@@ -194,6 +195,14 @@ def test_week_of_year_last_week():
assert pendulum.datetime(2012, 12, 31).week_of_year == 1
+def test_week_of_month_edge_case():
+ assert pendulum.datetime(2020, 1, 1).week_of_month == 1
+ assert pendulum.datetime(2020, 1, 7).week_of_month == 2
+ assert pendulum.datetime(2020, 1, 14).week_of_month == 3
+ assert pendulum.datetime(2023, 1, 1).week_of_month == 1
+ assert pendulum.datetime(2023, 1, 31).week_of_month == 6
+
+
def test_timezone():
d = pendulum.datetime(2000, 1, 1, tz="America/Toronto")
assert d.timezone.name == "America/Toronto"
@@ -246,3 +255,41 @@ def test_time():
t = dt.time()
assert isinstance(t, pendulum.Time)
assert_time(t, 10, 40, 34, 123456)
+
+
+@pytest.mark.parametrize(
+ "date, expected",
+ [
+ (pendulum.Date(2000, 1, 1), 1),
+ (pendulum.Date(2000, 1, 3), 2),
+ (pendulum.Date(2019, 12, 29), 5),
+ (pendulum.Date(2019, 12, 30), 6),
+ (pendulum.Date(2019, 12, 31), 6),
+ (pendulum.Date(2020, 1, 7), 2),
+ (pendulum.Date(2020, 1, 14), 3),
+ (pendulum.Date(2021, 1, 1), 1),
+ (pendulum.Date(2021, 1, 2), 1),
+ (pendulum.Date(2021, 1, 9), 2),
+ (pendulum.Date(2021, 1, 10), 2),
+ (pendulum.Date(2021, 1, 11), 3),
+ (pendulum.Date(2021, 1, 15), 3),
+ (pendulum.Date(2021, 1, 16), 3),
+ (pendulum.Date(2021, 1, 17), 3),
+ (pendulum.Date(2021, 1, 23), 4),
+ (pendulum.Date(2021, 1, 31), 5),
+ (pendulum.Date(2021, 12, 19), 3),
+ (pendulum.Date(2021, 12, 25), 4),
+ (pendulum.Date(2021, 12, 26), 4),
+ (pendulum.Date(2021, 12, 29), 5),
+ (pendulum.Date(2021, 12, 30), 5),
+ (pendulum.Date(2021, 12, 31), 5),
+ (pendulum.Date(2022, 1, 1), 1),
+ (pendulum.Date(2022, 1, 3), 2),
+ (pendulum.Date(2022, 1, 10), 3),
+ (pendulum.Date(2023, 1, 1), 1),
+ (pendulum.Date(2023, 1, 2), 2),
+ (pendulum.Date(2029, 12, 31), 6),
+ ],
+)
+def test_week_of_month_negative(date, expected):
+ assert date.week_of_month == expected
diff --git a/tests/datetime/test_start_end_of.py b/tests/datetime/test_start_end_of.py
index 597dbeb..1937e74 100644
--- a/tests/datetime/test_start_end_of.py
+++ b/tests/datetime/test_start_end_of.py
@@ -277,9 +277,49 @@ def test_start_of_with_transition():
assert d.start_of("year").offset == 3600
+def test_start_of_on_date_before_transition():
+ d = pendulum.datetime(2013, 10, 27, 0, 59, 59, tz="UTC").in_timezone("Europe/Paris")
+ assert d.offset == 7200
+ assert d.start_of("minute").offset == 7200
+ assert d.start_of("hour").offset == 7200
+ assert d.start_of("day").offset == 7200
+ assert d.start_of("month").offset == 7200
+ assert d.start_of("year").offset == 3600
+
+
+def test_start_of_on_date_after_transition():
+ d = pendulum.datetime(2013, 10, 27, 1, 59, 59, tz="UTC").in_timezone("Europe/Paris")
+ assert d.offset == 3600
+ assert d.start_of("minute").offset == 3600
+ assert d.start_of("hour").offset == 3600
+ assert d.start_of("day").offset == 7200
+ assert d.start_of("month").offset == 7200
+ assert d.start_of("year").offset == 3600
+
+
def test_end_of_with_transition():
d = pendulum.datetime(2013, 3, 31, tz="Europe/Paris")
assert d.offset == 3600
assert d.end_of("month").offset == 7200
assert d.end_of("day").offset == 7200
assert d.end_of("year").offset == 3600
+
+
+def test_end_of_on_date_before_transition():
+ d = pendulum.datetime(2013, 10, 27, 0, 0, 0, tz="UTC").in_timezone("Europe/Paris")
+ assert d.offset == 7200
+ assert d.end_of("minute").offset == 7200
+ assert d.end_of("hour").offset == 7200
+ assert d.end_of("day").offset == 3600
+ assert d.end_of("month").offset == 3600
+ assert d.end_of("year").offset == 3600
+
+
+def test_end_of_on_date_after_transition():
+ d = pendulum.datetime(2013, 10, 27, 1, 0, 0, tz="UTC").in_timezone("Europe/Paris")
+ assert d.offset == 3600
+ assert d.end_of("minute").offset == 3600
+ assert d.end_of("hour").offset == 3600
+ assert d.end_of("day").offset == 3600
+ assert d.end_of("month").offset == 3600
+ assert d.end_of("year").offset == 3600
diff --git a/tests/datetime/test_strings.py b/tests/datetime/test_strings.py
index 0de340d..e78dbc8 100644
--- a/tests/datetime/test_strings.py
+++ b/tests/datetime/test_strings.py
@@ -7,9 +7,9 @@ import pendulum
def test_to_string():
d = pendulum.datetime(1975, 12, 25, 0, 0, 0, 0, tz="local")
- assert str(d) == d.to_iso8601_string()
+ assert str(d) == "1975-12-25 00:00:00-05:00"
d = pendulum.datetime(1975, 12, 25, 0, 0, 0, 123456, tz="local")
- assert str(d) == d.to_iso8601_string()
+ assert str(d) == "1975-12-25 00:00:00.123456-05:00"
def test_to_date_string():
@@ -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
@@ -135,7 +135,7 @@ def test_for_json():
def test_format():
d = pendulum.datetime(1975, 12, 25, 14, 15, 16, tz="Europe/Paris")
- assert f"{d}" == "1975-12-25T14:15:16+01:00"
+ assert f"{d}" == "1975-12-25 14:15:16+01:00"
assert f"{d:YYYY}" == "1975"
assert f"{d:%Y}" == "1975"
assert f"{d:%H:%M %d.%m.%Y}" == "14:15 25.12.1975"
diff --git a/tests/duration/test_behavior.py b/tests/duration/test_behavior.py
index a97bbde..2442534 100644
--- a/tests/duration/test_behavior.py
+++ b/tests/duration/test_behavior.py
@@ -2,12 +2,15 @@ from __future__ import annotations
import pickle
+from copy import deepcopy
from datetime import timedelta
import pendulum
+from tests.conftest import assert_duration
-def test_pickle():
+
+def test_pickle() -> None:
it = pendulum.duration(days=3, seconds=2456, microseconds=123456)
s = pickle.dumps(it)
it2 = pickle.loads(s)
@@ -15,7 +18,15 @@ def test_pickle():
assert it == it2
-def test_comparison_to_timedelta():
+def test_comparison_to_timedelta() -> None:
duration = pendulum.duration(days=3)
assert duration < timedelta(days=4)
+
+
+def test_deepcopy() -> None:
+ duration = pendulum.duration(months=1)
+ copied_duration = deepcopy(duration)
+
+ assert copied_duration == duration
+ assert_duration(copied_duration, months=1)
diff --git a/tests/formatting/test_formatter.py b/tests/formatting/test_formatter.py
index 76c7a7f..4c4b008 100644
--- a/tests/formatting/test_formatter.py
+++ b/tests/formatting/test_formatter.py
@@ -113,6 +113,20 @@ def test_day_of_week():
assert f.format(d, "do") == "0th"
+def test_localized_day_of_week():
+ f = Formatter()
+ d = pendulum.datetime(2016, 8, 28)
+ assert f.format(d, "e") == "0"
+ assert f.format(d, "e", locale="en-gb") == "6"
+ assert f.format(d.add(days=2), "e") == "2"
+ assert f.format(d.add(days=2), "e", locale="en-gb") == "1"
+
+ assert f.format(d, "eo") == "1st"
+ assert f.format(d, "eo", locale="en-gb") == "7th"
+ assert f.format(d.add(days=2), "eo") == "3rd"
+ assert f.format(d.add(days=2), "eo", locale="en-gb") == "2nd"
+
+
def test_day_of_iso_week():
f = Formatter()
d = pendulum.datetime(2016, 8, 28)
diff --git a/tests/interval/test_add_subtract.py b/tests/interval/test_add_subtract.py
index 88525a3..d1f6036 100644
--- a/tests/interval/test_add_subtract.py
+++ b/tests/interval/test_add_subtract.py
@@ -6,8 +6,8 @@ import pendulum
def test_dst_add():
start = pendulum.datetime(2017, 3, 7, tz="America/Toronto")
end = start.add(days=6)
- period = end - start
- new_end = start + period
+ interval = end - start
+ new_end = start + interval
assert new_end == end
@@ -15,8 +15,8 @@ def test_dst_add():
def test_dst_add_non_variable_units():
start = pendulum.datetime(2013, 3, 31, 1, 30, tz="Europe/Paris")
end = start.add(hours=1)
- period = end - start
- new_end = start + period
+ interval = end - start
+ new_end = start + interval
assert new_end == end
@@ -24,8 +24,8 @@ def test_dst_add_non_variable_units():
def test_dst_subtract():
start = pendulum.datetime(2017, 3, 7, tz="America/Toronto")
end = start.add(days=6)
- period = end - start
- new_start = end - period
+ interval = end - start
+ new_start = end - interval
assert new_start == start
@@ -33,8 +33,8 @@ def test_dst_subtract():
def test_naive_subtract():
start = pendulum.naive(2013, 3, 31, 1, 30)
end = start.add(hours=1)
- period = end - start
- new_end = start + period
+ interval = end - start
+ new_end = start + interval
assert new_end == end
@@ -43,7 +43,7 @@ def test_negative_difference_subtract():
start = pendulum.datetime(2018, 5, 28, 12, 34, 56, 123456)
end = pendulum.datetime(2018, 1, 1)
- period = end - start
- new_end = start + period
+ interval = end - start
+ new_end = start + interval
assert new_end == end
diff --git a/tests/interval/test_behavior.py b/tests/interval/test_behavior.py
index b5e057a..96a1f42 100644
--- a/tests/interval/test_behavior.py
+++ b/tests/interval/test_behavior.py
@@ -40,15 +40,28 @@ def test_comparison_to_timedelta():
dt1 = pendulum.datetime(2016, 11, 18)
dt2 = pendulum.datetime(2016, 11, 20)
- period = dt2 - dt1
+ interval = dt2 - dt1
- assert period < timedelta(days=4)
+ assert interval < timedelta(days=4)
def test_equality_to_timedelta():
dt1 = pendulum.datetime(2016, 11, 18)
dt2 = pendulum.datetime(2016, 11, 20)
- period = dt2 - dt1
+ interval = dt2 - dt1
- assert period == timedelta(days=2)
+ assert interval == timedelta(days=2)
+
+
+def test_inequality():
+ dt1 = pendulum.datetime(2016, 11, 18)
+ dt2 = pendulum.datetime(2016, 11, 20)
+ dt3 = pendulum.datetime(2016, 11, 22)
+
+ interval1 = dt2 - dt1
+ interval2 = dt3 - dt2
+ interval3 = dt3 - dt1
+
+ assert interval1 != interval2
+ assert interval1 != interval3
diff --git a/tests/interval/test_construct.py b/tests/interval/test_construct.py
index 024e741..550b808 100644
--- a/tests/interval/test_construct.py
+++ b/tests/interval/test_construct.py
@@ -72,17 +72,17 @@ def test_accuracy():
def test_dst_transition():
start = pendulum.datetime(2017, 3, 7, tz="America/Toronto")
end = start.add(days=6)
- period = end - start
+ interval = end - start
- assert period.days == 5
- assert period.seconds == 82800
+ assert interval.days == 5
+ assert interval.seconds == 82800
- assert period.remaining_days == 6
- assert period.hours == 0
- assert period.remaining_seconds == 0
+ assert interval.remaining_days == 6
+ assert interval.hours == 0
+ assert interval.remaining_seconds == 0
- assert period.in_days() == 6
- assert period.in_hours() == 5 * 24 + 23
+ assert interval.in_days() == 6
+ assert interval.in_hours() == 5 * 24 + 23
def test_timedelta_behavior():
@@ -108,14 +108,14 @@ def test_timedelta_behavior():
def test_different_timezones_same_time():
dt1 = pendulum.datetime(2013, 3, 31, 1, 30, tz="Europe/Paris")
dt2 = pendulum.datetime(2013, 4, 1, 1, 30, tz="Europe/Paris")
- period = dt2 - dt1
+ interval = dt2 - dt1
- assert period.in_words() == "1 day"
- assert period.in_hours() == 23
+ assert interval.in_words() == "1 day"
+ assert interval.in_hours() == 23
dt1 = pendulum.datetime(2013, 3, 31, 1, 30, tz="Europe/Paris")
dt2 = pendulum.datetime(2013, 4, 1, 1, 30, tz="America/Toronto")
- period = dt2 - dt1
+ interval = dt2 - dt1
- assert period.in_words() == "1 day 5 hours"
- assert period.in_hours() == 29
+ assert interval.in_words() == "1 day 5 hours"
+ assert interval.in_hours() == 29
diff --git a/tests/interval/test_hashing.py b/tests/interval/test_hashing.py
index c18502f..ca9ef9c 100644
--- a/tests/interval/test_hashing.py
+++ b/tests/interval/test_hashing.py
@@ -3,21 +3,21 @@ from __future__ import annotations
import pendulum
-def test_periods_with_same_duration_and_different_dates():
+def test_intervals_with_same_duration_and_different_dates():
day1 = pendulum.DateTime(2018, 1, 1)
day2 = pendulum.DateTime(2018, 1, 2)
day3 = pendulum.DateTime(2018, 1, 2)
- period1 = day2 - day1
- period2 = day3 - day2
+ interval1 = day2 - day1
+ interval2 = day3 - day2
- assert period1 != period2
- assert len({period1, period2}) == 2
+ assert interval1 != interval2
+ assert len({interval1, interval2}) == 2
-def test_periods_with_same_dates():
- period1 = pendulum.DateTime(2018, 1, 2) - pendulum.DateTime(2018, 1, 1)
- period2 = pendulum.DateTime(2018, 1, 2) - pendulum.DateTime(2018, 1, 1)
+def test_intervals_with_same_dates():
+ interval1 = pendulum.DateTime(2018, 1, 2) - pendulum.DateTime(2018, 1, 1)
+ interval2 = pendulum.DateTime(2018, 1, 2) - pendulum.DateTime(2018, 1, 1)
- assert period1 == period2
- assert len({period1, period2}) == 1
+ assert interval1 == interval2
+ assert len({interval1, interval2}) == 1
diff --git a/tests/interval/test_in_words.py b/tests/interval/test_in_words.py
index 410e11f..f60e20d 100644
--- a/tests/interval/test_in_words.py
+++ b/tests/interval/test_in_words.py
@@ -5,66 +5,66 @@ import pendulum
def test_week():
start_date = pendulum.datetime(2012, 1, 1)
- period = pendulum.interval(start=start_date, end=start_date.add(weeks=1))
- assert period.in_words() == "1 week"
+ interval = pendulum.interval(start=start_date, end=start_date.add(weeks=1))
+ assert interval.in_words() == "1 week"
def test_week_and_day():
start_date = pendulum.datetime(2012, 1, 1)
- period = pendulum.interval(start=start_date, end=start_date.add(weeks=1, days=1))
- assert period.in_words() == "1 week 1 day"
+ interval = pendulum.interval(start=start_date, end=start_date.add(weeks=1, days=1))
+ assert interval.in_words() == "1 week 1 day"
def test_all():
start_date = pendulum.datetime(2012, 1, 1)
- period = pendulum.interval(
+ interval = pendulum.interval(
start=start_date,
end=start_date.add(years=1, months=1, days=1, seconds=1, microseconds=1),
)
- assert period.in_words() == "1 year 1 month 1 day 1 second"
+ assert interval.in_words() == "1 year 1 month 1 day 1 second"
def test_in_french():
start_date = pendulum.datetime(2012, 1, 1)
- period = pendulum.interval(
+ interval = pendulum.interval(
start=start_date,
end=start_date.add(years=1, months=1, days=1, seconds=1, microseconds=1),
)
- assert period.in_words(locale="fr") == "1 an 1 mois 1 jour 1 seconde"
+ assert interval.in_words(locale="fr") == "1 an 1 mois 1 jour 1 seconde"
def test_singular_negative_values():
start_date = pendulum.datetime(2012, 1, 1)
- period = pendulum.interval(start=start_date, end=start_date.subtract(days=1))
- assert period.in_words() == "-1 day"
+ interval = pendulum.interval(start=start_date, end=start_date.subtract(days=1))
+ assert interval.in_words() == "-1 day"
def test_separator():
start_date = pendulum.datetime(2012, 1, 1)
- period = pendulum.interval(
+ interval = pendulum.interval(
start=start_date,
end=start_date.add(years=1, months=1, days=1, seconds=1, microseconds=1),
)
- assert period.in_words(separator=", ") == "1 year, 1 month, 1 day, 1 second"
+ assert interval.in_words(separator=", ") == "1 year, 1 month, 1 day, 1 second"
def test_subseconds():
start_date = pendulum.datetime(2012, 1, 1)
- period = pendulum.interval(
+ interval = pendulum.interval(
start=start_date, end=start_date.add(microseconds=123456)
)
- assert period.in_words() == "0.12 second"
+ assert interval.in_words() == "0.12 second"
def test_subseconds_with_seconds():
start_date = pendulum.datetime(2012, 1, 1)
- period = pendulum.interval(
+ interval = pendulum.interval(
start=start_date, end=start_date.add(seconds=12, microseconds=123456)
)
- assert period.in_words() == "12 seconds"
+ assert interval.in_words() == "12 seconds"
-def test_zero_period():
+def test_zero_interval():
start_date = pendulum.datetime(2012, 1, 1)
- period = pendulum.interval(start=start_date, end=start_date)
- assert period.in_words() == "0 microseconds"
+ interval = pendulum.interval(start=start_date, end=start_date)
+ assert interval.in_words() == "0 microseconds"
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..c15b9bd 100644
--- a/tests/parsing/test_parse_iso8601.py
+++ b/tests/parsing/test_parse_iso8601.py
@@ -8,83 +8,86 @@ import pytest
from pendulum.parsing import parse_iso8601
+
try:
- from pendulum.parsing._extension import TZFixedOffset as FixedTimezone
+ from pendulum._pendulum import FixedTimezone
except ImportError:
from pendulum.tz.timezone import FixedTimezone
-def test_parse_iso8601():
- # Date
- assert date(2016, 1, 1) == parse_iso8601("2016")
- assert date(2016, 10, 1) == parse_iso8601("2016-10")
- assert date(2016, 10, 6) == parse_iso8601("2016-10-06")
- assert date(2016, 10, 6) == parse_iso8601("20161006")
-
- # Time
- assert time(20, 16, 10, 0) == parse_iso8601("201610")
-
- # Datetime
- assert datetime(2016, 10, 6, 12, 34, 56, 123456) == parse_iso8601(
- "2016-10-06T12:34:56.123456"
- )
- assert datetime(2016, 10, 6, 12, 34, 56, 123000) == parse_iso8601(
- "2016-10-06T12:34:56.123"
- )
- assert datetime(2016, 10, 6, 12, 34, 56, 123) == parse_iso8601(
- "2016-10-06T12:34:56.000123"
- )
- assert datetime(2016, 10, 6, 12, 0, 0, 0) == parse_iso8601("2016-10-06T12")
- assert datetime(2016, 10, 6, 12, 34, 56, 0) == parse_iso8601("2016-10-06T123456")
- assert datetime(2016, 10, 6, 12, 34, 56, 123456) == parse_iso8601(
- "2016-10-06T123456.123456"
- )
- assert datetime(2016, 10, 6, 12, 34, 56, 123456) == parse_iso8601(
- "20161006T123456.123456"
- )
- assert datetime(2016, 10, 6, 12, 34, 56, 123456) == parse_iso8601(
- "20161006 123456.123456"
- )
-
- # Datetime with offset
- assert datetime(
- 2016, 10, 6, 12, 34, 56, 123456, FixedTimezone(19800)
- ) == parse_iso8601("2016-10-06T12:34:56.123456+05:30")
- assert datetime(
- 2016, 10, 6, 12, 34, 56, 123456, FixedTimezone(19800)
- ) == parse_iso8601("2016-10-06T12:34:56.123456+0530")
- assert datetime(
- 2016, 10, 6, 12, 34, 56, 123456, FixedTimezone(-19800)
- ) == parse_iso8601("2016-10-06T12:34:56.123456-05:30")
- assert datetime(
- 2016, 10, 6, 12, 34, 56, 123456, FixedTimezone(-19800)
- ) == parse_iso8601("2016-10-06T12:34:56.123456-0530")
- assert datetime(
- 2016, 10, 6, 12, 34, 56, 123456, FixedTimezone(18000)
- ) == parse_iso8601("2016-10-06T12:34:56.123456+05")
- assert datetime(
- 2016, 10, 6, 12, 34, 56, 123456, FixedTimezone(-18000)
- ) == parse_iso8601("2016-10-06T12:34:56.123456-05")
- assert datetime(
- 2016, 10, 6, 12, 34, 56, 123456, FixedTimezone(-18000)
- ) == parse_iso8601("20161006T123456,123456-05")
- assert datetime(
- 2016, 10, 6, 12, 34, 56, 123456, FixedTimezone(+19800)
- ) == parse_iso8601("2016-10-06T12:34:56.123456789+05:30")
-
- # Ordinal date
- assert date(2012, 1, 7) == parse_iso8601("2012-007")
- assert date(2012, 1, 7) == parse_iso8601("2012007")
- assert date(2017, 3, 20) == parse_iso8601("2017-079")
-
- # Week date
- assert date(2012, 1, 30) == parse_iso8601("2012-W05")
- assert date(2008, 9, 27) == parse_iso8601("2008-W39-6")
- assert date(2010, 1, 3) == parse_iso8601("2009-W53-7")
- assert date(2008, 12, 29) == parse_iso8601("2009-W01-1")
-
- # Week date wth time
- assert datetime(2008, 9, 27, 9, 0, 0, 0) == parse_iso8601("2008-W39-6T09")
+@pytest.mark.parametrize(
+ ["text", "expected"],
+ [
+ ("2016-10", date(2016, 10, 1)),
+ ("2016-10-06", date(2016, 10, 6)),
+ # Ordinal date
+ ("2012-007", date(2012, 1, 7)),
+ ("2012007", date(2012, 1, 7)),
+ ("2017-079", date(2017, 3, 20)),
+ # Week date
+ ("2012-W05", date(2012, 1, 30)),
+ ("2008-W39-6", date(2008, 9, 27)),
+ ("2009-W53-7", date(2010, 1, 3)),
+ ("2009-W01-1", date(2008, 12, 29)),
+ # Time
+ ("12:34", time(12, 34, 0)),
+ ("12:34:56", time(12, 34, 56)),
+ ("12:34:56.123", time(12, 34, 56, 123000)),
+ ("12:34:56.123456", time(12, 34, 56, 123456)),
+ ("12:34+05:30", time(12, 34, 0, tzinfo=FixedTimezone(19800))),
+ ("12:34:56+05:30", time(12, 34, 56, tzinfo=FixedTimezone(19800))),
+ ("12:34:56.123+05:30", time(12, 34, 56, 123000, tzinfo=FixedTimezone(19800))),
+ (
+ "12:34:56.123456+05:30",
+ time(12, 34, 56, 123456, tzinfo=FixedTimezone(19800)),
+ ),
+ # Datetime
+ ("2016-10-06T12:34:56.123456", datetime(2016, 10, 6, 12, 34, 56, 123456)),
+ ("2016-10-06T12:34:56.123", datetime(2016, 10, 6, 12, 34, 56, 123000)),
+ ("2016-10-06T12:34:56.000123", datetime(2016, 10, 6, 12, 34, 56, 123)),
+ ("20161006T12", datetime(2016, 10, 6, 12, 0, 0, 0)),
+ ("20161006T123456", datetime(2016, 10, 6, 12, 34, 56, 0)),
+ ("20161006T123456.123456", datetime(2016, 10, 6, 12, 34, 56, 123456)),
+ ("20161006 123456.123456", datetime(2016, 10, 6, 12, 34, 56, 123456)),
+ # Datetime with offset
+ (
+ "2016-10-06T12:34:56.123456+05:30",
+ datetime(2016, 10, 6, 12, 34, 56, 123456, FixedTimezone(19800)),
+ ),
+ (
+ "2016-10-06T12:34:56.123456+0530",
+ datetime(2016, 10, 6, 12, 34, 56, 123456, FixedTimezone(19800)),
+ ),
+ (
+ "2016-10-06T12:34:56.123456-05:30",
+ datetime(2016, 10, 6, 12, 34, 56, 123456, FixedTimezone(-19800)),
+ ),
+ (
+ "2016-10-06T12:34:56.123456-0530",
+ datetime(2016, 10, 6, 12, 34, 56, 123456, FixedTimezone(-19800)),
+ ),
+ (
+ "2016-10-06T12:34:56.123456+05",
+ datetime(2016, 10, 6, 12, 34, 56, 123456, FixedTimezone(18000)),
+ ),
+ (
+ "2016-10-06T12:34:56.123456-05",
+ datetime(2016, 10, 6, 12, 34, 56, 123456, FixedTimezone(-18000)),
+ ),
+ (
+ "20161006T123456,123456-05",
+ datetime(2016, 10, 6, 12, 34, 56, 123456, FixedTimezone(-18000)),
+ ),
+ (
+ "2016-10-06T12:34:56.123456789+05:30",
+ datetime(2016, 10, 6, 12, 34, 56, 123456, FixedTimezone(+19800)),
+ ),
+ # Week date with time
+ ("2008-W39-6T09", datetime(2008, 9, 27, 9, 0, 0, 0)),
+ ],
+)
+def test_parse_iso8601(text: str, expected: date) -> None:
+ assert parse_iso8601(text) == expected
def test_parse_ios8601_invalid():
@@ -165,301 +168,43 @@ def test_parse_ios8601_invalid():
parse_iso8601("2012-W123") # Missing separator
-def test_parse_ios8601_duration():
- text = "P2Y3M4DT5H6M7S"
- parsed = parse_iso8601(text)
-
- assert parsed.years == 2
- assert parsed.months == 3
- assert parsed.weeks == 0
- assert parsed.remaining_days == 4
- assert parsed.hours == 5
- assert parsed.minutes == 6
- assert parsed.remaining_seconds == 7
- assert parsed.microseconds == 0
-
- text = "P1Y2M3DT4H5M6.5S"
- parsed = parse_iso8601(text)
-
- assert parsed.years == 1
- assert parsed.months == 2
- assert parsed.weeks == 0
- assert parsed.remaining_days == 3
- assert parsed.hours == 4
- assert parsed.minutes == 5
- assert parsed.remaining_seconds == 6
- assert parsed.microseconds == 500000
-
- text = "P1Y2M3DT4H5M6,5S"
- parsed = parse_iso8601(text)
-
- assert parsed.years == 1
- assert parsed.months == 2
- assert parsed.weeks == 0
- assert parsed.remaining_days == 3
- assert parsed.hours == 4
- assert parsed.minutes == 5
- assert parsed.remaining_seconds == 6
- assert parsed.microseconds == 500000
-
- text = "P1Y2M3D"
- parsed = parse_iso8601(text)
-
- assert parsed.years == 1
- assert parsed.months == 2
- assert parsed.weeks == 0
- assert parsed.remaining_days == 3
- assert parsed.hours == 0
- assert parsed.minutes == 0
- assert parsed.remaining_seconds == 0
- assert parsed.microseconds == 0
-
- text = "P1Y2M3.5D"
- parsed = parse_iso8601(text)
-
- assert parsed.years == 1
- assert parsed.months == 2
- assert parsed.weeks == 0
- assert parsed.remaining_days == 3
- assert parsed.hours == 12
- assert parsed.minutes == 0
- assert parsed.remaining_seconds == 0
- assert parsed.microseconds == 0
-
- text = "P1Y2M3,5D"
- parsed = parse_iso8601(text)
-
- assert parsed.years == 1
- assert parsed.months == 2
- assert parsed.weeks == 0
- assert parsed.remaining_days == 3
- assert parsed.hours == 12
- assert parsed.minutes == 0
- assert parsed.remaining_seconds == 0
- assert parsed.microseconds == 0
-
- text = "PT4H54M6.5S"
- parsed = parse_iso8601(text)
-
- assert parsed.years == 0
- assert parsed.months == 0
- assert parsed.weeks == 0
- assert parsed.remaining_days == 0
- assert parsed.hours == 4
- assert parsed.minutes == 54
- assert parsed.remaining_seconds == 6
- assert parsed.microseconds == 500000
-
- text = "PT4H54M6,5S"
- parsed = parse_iso8601(text)
-
- assert parsed.years == 0
- assert parsed.months == 0
- assert parsed.weeks == 0
- assert parsed.remaining_days == 0
- assert parsed.hours == 4
- assert parsed.minutes == 54
- assert parsed.remaining_seconds == 6
- assert parsed.microseconds == 500000
-
- text = "P1Y"
- parsed = parse_iso8601(text)
-
- assert parsed.years == 1
- assert parsed.months == 0
- assert parsed.weeks == 0
- assert parsed.remaining_days == 0
- assert parsed.hours == 0
- assert parsed.minutes == 0
- assert parsed.remaining_seconds == 0
- assert parsed.microseconds == 0
-
- text = "P1.5Y"
- with pytest.raises(ValueError):
- parse_iso8601(text)
-
- text = "P1,5Y"
- with pytest.raises(ValueError):
- parse_iso8601(text)
-
- text = "P1M"
- parsed = parse_iso8601(text)
-
- assert parsed.years == 0
- assert parsed.months == 1
- assert parsed.weeks == 0
- assert parsed.remaining_days == 0
- assert parsed.hours == 0
- assert parsed.minutes == 0
- assert parsed.remaining_seconds == 0
- assert parsed.microseconds == 0
-
- text = "P1.5M"
- with pytest.raises(ValueError):
- parse_iso8601(text)
-
- text = "P1,5M"
- with pytest.raises(ValueError):
- parse_iso8601(text)
-
- text = "P1W"
- parsed = parse_iso8601(text)
-
- assert parsed.years == 0
- assert parsed.months == 0
- assert parsed.weeks == 1
- assert parsed.remaining_days == 0
- assert parsed.hours == 0
- assert parsed.minutes == 0
- assert parsed.remaining_seconds == 0
- assert parsed.microseconds == 0
-
- text = "P1.5W"
- parsed = parse_iso8601(text)
-
- assert parsed.years == 0
- assert parsed.months == 0
- assert parsed.weeks == 1
- assert parsed.remaining_days == 3
- assert parsed.hours == 12
- assert parsed.minutes == 0
- assert parsed.remaining_seconds == 0
- assert parsed.microseconds == 0
-
- text = "P1,5W"
- parsed = parse_iso8601(text)
-
- assert parsed.years == 0
- assert parsed.months == 0
- assert parsed.weeks == 1
- assert parsed.remaining_days == 3
- assert parsed.hours == 12
- assert parsed.minutes == 0
- assert parsed.remaining_seconds == 0
- assert parsed.microseconds == 0
-
- text = "P1D"
- parsed = parse_iso8601(text)
-
- assert parsed.years == 0
- assert parsed.months == 0
- assert parsed.weeks == 0
- assert parsed.remaining_days == 1
- assert parsed.hours == 0
- assert parsed.minutes == 0
- assert parsed.remaining_seconds == 0
- assert parsed.microseconds == 0
-
- text = "P1.5D"
- parsed = parse_iso8601(text)
-
- assert parsed.years == 0
- assert parsed.months == 0
- assert parsed.weeks == 0
- assert parsed.remaining_days == 1
- assert parsed.hours == 12
- assert parsed.minutes == 0
- assert parsed.remaining_seconds == 0
- assert parsed.microseconds == 0
-
- text = "P1,5D"
- parsed = parse_iso8601(text)
-
- assert parsed.years == 0
- assert parsed.months == 0
- assert parsed.weeks == 0
- assert parsed.remaining_days == 1
- assert parsed.hours == 12
- assert parsed.minutes == 0
- assert parsed.remaining_seconds == 0
- assert parsed.microseconds == 0
-
- text = "PT1H"
- parsed = parse_iso8601(text)
-
- assert parsed.years == 0
- assert parsed.months == 0
- assert parsed.weeks == 0
- assert parsed.remaining_days == 0
- assert parsed.hours == 1
- assert parsed.minutes == 0
- assert parsed.remaining_seconds == 0
- assert parsed.microseconds == 0
-
- text = "PT1.5H"
- parsed = parse_iso8601(text)
-
- assert parsed.years == 0
- assert parsed.months == 0
- assert parsed.weeks == 0
- assert parsed.remaining_days == 0
- assert parsed.hours == 1
- assert parsed.minutes == 30
- assert parsed.remaining_seconds == 0
- assert parsed.microseconds == 0
-
- text = "PT1,5H"
- parsed = parse_iso8601(text)
-
- assert parsed.years == 0
- assert parsed.months == 0
- assert parsed.weeks == 0
- assert parsed.remaining_days == 0
- assert parsed.hours == 1
- assert parsed.minutes == 30
- assert parsed.remaining_seconds == 0
- assert parsed.microseconds == 0
-
- # Double digit with 0
- text = "P2Y30M4DT5H6M7S"
- parsed = parse_iso8601(text)
-
- assert parsed.years == 2
- assert parsed.months == 30
- assert parsed.weeks == 0
- assert parsed.remaining_days == 4
- assert parsed.hours == 5
- assert parsed.minutes == 6
- assert parsed.remaining_seconds == 7
- assert parsed.microseconds == 0
-
- # No P operator
- with pytest.raises(ValueError):
- parse_iso8601("2Y3M4DT5H6M7S")
-
- # Week and other units combined
- with pytest.raises(ValueError):
- parse_iso8601("P1Y2W")
-
- # Invalid units order
- with pytest.raises(ValueError):
- parse_iso8601("P1S")
-
- with pytest.raises(ValueError):
- parse_iso8601("P1D1S")
-
- with pytest.raises(ValueError):
- parse_iso8601("1Y2M3D1SPT1M")
-
- with pytest.raises(ValueError):
- parse_iso8601("P1Y2M3D2MT1S")
-
- with pytest.raises(ValueError):
- parse_iso8601("P2M3D1ST1Y1M")
-
- with pytest.raises(ValueError):
- parse_iso8601("P1Y2M2MT3D1S")
-
- with pytest.raises(ValueError):
- parse_iso8601("P1D1Y1M")
-
- with pytest.raises(ValueError):
- parse_iso8601("PT1S1H")
-
- # Invalid
- with pytest.raises(ValueError):
- parse_iso8601("P1Dasdfasdf")
-
- # Invalid fractional
- with pytest.raises(ValueError):
- parse_iso8601("P2Y3M4DT5.5H6M7S")
+@pytest.mark.parametrize(
+ ["text", "expected"],
+ [
+ ("P2Y3M4DT5H6M7S", (2, 3, 0, 4, 5, 6, 7, 0)),
+ ("P1Y2M3DT4H5M6.5S", (1, 2, 0, 3, 4, 5, 6, 500_000)),
+ ("P1Y2M3DT4H5M6,5S", (1, 2, 0, 3, 4, 5, 6, 500_000)),
+ ("P1Y2M3D", (1, 2, 0, 3, 0, 0, 0, 0)),
+ ("P1Y2M3.5D", (1, 2, 0, 3, 12, 0, 0, 0)),
+ ("P1Y2M3,5D", (1, 2, 0, 3, 12, 0, 0, 0)),
+ ("PT4H54M6.5S", (0, 0, 0, 0, 4, 54, 6, 500_000)),
+ ("PT4H54M6,5S", (0, 0, 0, 0, 4, 54, 6, 500_000)),
+ ("P1Y", (1, 0, 0, 0, 0, 0, 0, 0)),
+ ("P1M", (0, 1, 0, 0, 0, 0, 0, 0)),
+ ("P1W", (0, 0, 1, 0, 0, 0, 0, 0)),
+ ("P1.5W", (0, 0, 1, 3, 12, 0, 0, 0)),
+ ("P1,5W", (0, 0, 1, 3, 12, 0, 0, 0)),
+ ("P1D", (0, 0, 0, 1, 0, 0, 0, 0)),
+ ("P1.5D", (0, 0, 0, 1, 12, 0, 0, 0)),
+ ("P1,5D", (0, 0, 0, 1, 12, 0, 0, 0)),
+ ("PT1H", (0, 0, 0, 0, 1, 0, 0, 0)),
+ ("PT1.5H", (0, 0, 0, 0, 1, 30, 0, 0)),
+ ("PT1,5H", (0, 0, 0, 0, 1, 30, 0, 0)),
+ ("P2Y30M4DT5H6M7S", (2, 30, 0, 4, 5, 6, 7, 0)),
+ ],
+)
+def test_parse_ios8601_duration(
+ text: str, expected: tuple[int, int, int, int, int, int, int, int]
+) -> None:
+ parsed = parse_iso8601(text)
+
+ assert (
+ parsed.years,
+ parsed.months,
+ parsed.weeks,
+ parsed.remaining_days,
+ parsed.hours,
+ parsed.minutes,
+ parsed.remaining_seconds,
+ parsed.microseconds,
+ ) == expected
diff --git a/tests/parsing/test_parsing.py b/tests/parsing/test_parsing.py
index 35dcf86..d57b82f 100644
--- a/tests/parsing/test_parsing.py
+++ b/tests/parsing/test_parsing.py
@@ -276,19 +276,6 @@ def test_iso8601_datetime():
assert parsed.microsecond == 0
assert parsed.utcoffset().total_seconds() == 19800
- text = "20161001T1430,4+0530"
-
- parsed = parse(text)
-
- assert parsed.year == 2016
- assert parsed.month == 10
- assert parsed.day == 1
- assert parsed.hour == 14
- assert parsed.minute == 30
- assert parsed.second == 0
- assert parsed.microsecond == 400000
- assert parsed.utcoffset().total_seconds() == 19800
-
text = "2008-09-03T20:56:35.450686+01"
parsed = parse(text)
@@ -480,7 +467,7 @@ def test_iso8601_ordinal():
def test_iso8601_time():
now = pendulum.datetime(2015, 11, 12)
- text = "201205"
+ text = "T201205"
parsed = parse(text, now=now)
diff --git a/tests/test_helpers.py b/tests/test_helpers.py
index e3daeac..e9317b9 100644
--- a/tests/test_helpers.py
+++ b/tests/test_helpers.py
@@ -8,14 +8,22 @@ import pytz
import pendulum
from pendulum import timezone
+from pendulum.helpers import PreciseDiff
from pendulum.helpers import days_in_year
from pendulum.helpers import precise_diff
from pendulum.helpers import week_day
def assert_diff(
- diff, years=0, months=0, days=0, hours=0, minutes=0, seconds=0, microseconds=0
-):
+ diff: PreciseDiff,
+ years: int = 0,
+ months: int = 0,
+ days: int = 0,
+ hours: int = 0,
+ minutes: int = 0,
+ seconds: int = 0,
+ microseconds: int = 0,
+) -> None:
assert diff.years == years
assert diff.months == months
assert diff.days == days
@@ -25,7 +33,7 @@ def assert_diff(
assert diff.microseconds == microseconds
-def test_precise_diff():
+def test_precise_diff() -> None:
dt1 = datetime(2003, 3, 1, 0, 0, 0)
dt2 = datetime(2003, 1, 31, 23, 59, 59)
@@ -40,6 +48,7 @@ def test_precise_diff():
diff = precise_diff(dt1, dt2)
assert_diff(diff, months=-1, seconds=-1)
+ assert diff.total_days == -30
diff = precise_diff(dt2, dt1)
assert_diff(diff, months=1, seconds=1)
@@ -76,7 +85,7 @@ def test_precise_diff():
assert_diff(diff, days=6, hours=0)
-def test_precise_diff_timezone():
+def test_precise_diff_timezone() -> None:
paris = pendulum.timezone("Europe/Paris")
toronto = pendulum.timezone("America/Toronto")
@@ -85,23 +94,26 @@ def test_precise_diff_timezone():
diff = precise_diff(dt1, dt2)
assert_diff(diff, days=1, hours=0)
+ assert diff.total_days == 1
dt2 = toronto.datetime(2013, 4, 1, 1, 30)
diff = precise_diff(dt1, dt2)
assert_diff(diff, days=1, hours=5)
+ assert diff.total_days == 1
# pytz
- paris = pytz.timezone("Europe/Paris")
- toronto = pytz.timezone("America/Toronto")
+ paris_pytz = pytz.timezone("Europe/Paris")
+ toronto_pytz = pytz.timezone("America/Toronto")
- dt1 = paris.localize(datetime(2013, 3, 31, 1, 30))
- dt2 = paris.localize(datetime(2013, 4, 1, 1, 30))
+ dt1 = paris_pytz.localize(datetime(2013, 3, 31, 1, 30))
+ dt2 = paris_pytz.localize(datetime(2013, 4, 1, 1, 30))
diff = precise_diff(dt1, dt2)
assert_diff(diff, days=1, hours=0)
+ assert diff.total_days == 1
- dt2 = toronto.localize(datetime(2013, 4, 1, 1, 30))
+ dt2 = toronto_pytz.localize(datetime(2013, 4, 1, 1, 30))
diff = precise_diff(dt1, dt2)
assert_diff(diff, days=1, hours=5)
@@ -111,19 +123,20 @@ def test_precise_diff_timezone():
dt2 = timezone("Europe/Paris").datetime(2018, 6, 20, 3, 40) # UTC+2
diff = precise_diff(dt1, dt2)
assert_diff(diff, minutes=10)
+ assert diff.total_days == 0
-def test_week_day():
+def test_week_day() -> None:
assert week_day(2017, 6, 2) == 5
assert week_day(2017, 1, 1) == 7
-def test_days_in_years():
+def test_days_in_years() -> None:
assert days_in_year(2017) == 365
assert days_in_year(2016) == 366
-def test_locale():
+def test_locale() -> None:
dt = pendulum.datetime(2000, 11, 10, 12, 34, 56, 123456)
pendulum.set_locale("fr")
@@ -133,7 +146,7 @@ def test_locale():
assert dt.date().format("MMMM") == "novembre"
-def test_set_locale_invalid():
+def test_set_locale_invalid() -> None:
with pytest.raises(ValueError):
pendulum.set_locale("invalid")
@@ -141,13 +154,13 @@ def test_set_locale_invalid():
@pytest.mark.parametrize(
"locale", ["DE", "pt-BR", "pt-br", "PT-br", "PT-BR", "pt_br", "PT_BR", "PT_BR"]
)
-def test_set_locale_malformed_locale(locale):
+def test_set_locale_malformed_locale(locale: str) -> None:
pendulum.set_locale(locale)
pendulum.set_locale("en")
-def test_week_starts_at():
+def test_week_starts_at() -> None:
pendulum.week_starts_at(pendulum.SATURDAY)
dt = pendulum.now().start_of("week")
@@ -155,15 +168,15 @@ def test_week_starts_at():
assert dt.date().day_of_week == pendulum.SATURDAY
-def test_week_starts_at_invalid_value():
+def test_week_starts_at_invalid_value() -> None:
with pytest.raises(ValueError):
- pendulum.week_starts_at(-1)
+ pendulum.week_starts_at(-1) # type: ignore[arg-type]
with pytest.raises(ValueError):
- pendulum.week_starts_at(11)
+ pendulum.week_starts_at(11) # type: ignore[arg-type]
-def test_week_ends_at():
+def test_week_ends_at() -> None:
pendulum.week_ends_at(pendulum.SATURDAY)
dt = pendulum.now().end_of("week")
@@ -171,9 +184,9 @@ def test_week_ends_at():
assert dt.date().day_of_week == pendulum.SATURDAY
-def test_week_ends_at_invalid_value():
+def test_week_ends_at_invalid_value() -> None:
with pytest.raises(ValueError):
- pendulum.week_ends_at(-1)
+ pendulum.week_ends_at(-1) # type: ignore[arg-type]
with pytest.raises(ValueError):
- pendulum.week_ends_at(11)
+ pendulum.week_ends_at(11) # type: ignore[arg-type]
diff --git a/tests/test_main.py b/tests/test_main.py
index 1710bf2..011a6b1 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -1,12 +1,62 @@
from __future__ import annotations
+from datetime import date
+from datetime import datetime
+from datetime import time
+
import pytz
+from dateutil import tz
+
+import pendulum
+
from pendulum import _safe_timezone
+from pendulum import timezone
from pendulum.tz.timezone import Timezone
-def test_safe_timezone_with_tzinfo_objects():
+def test_instance_with_naive_datetime_defaults_to_utc() -> None:
+ now = pendulum.instance(datetime.now())
+ assert now.timezone_name == "UTC"
+
+
+def test_instance_with_aware_datetime() -> None:
+ now = pendulum.instance(datetime.now(timezone("Europe/Paris")))
+ assert now.timezone_name == "Europe/Paris"
+
+
+def test_instance_with_aware_datetime_pytz() -> None:
+ now = pendulum.instance(datetime.now(pytz.timezone("Europe/Paris")))
+ assert now.timezone_name == "Europe/Paris"
+
+
+def test_instance_with_aware_datetime_any_tzinfo() -> None:
+ dt = datetime(2016, 8, 7, 12, 34, 56, tzinfo=tz.gettz("Europe/Paris"))
+ now = pendulum.instance(dt)
+ assert now.timezone_name == "+02:00"
+
+
+def test_instance_with_date() -> None:
+ dt = pendulum.instance(date(2022, 12, 23))
+
+ assert isinstance(dt, pendulum.Date)
+
+
+def test_instance_with_naive_time() -> None:
+ dt = pendulum.instance(time(12, 34, 56, 123456))
+
+ assert isinstance(dt, pendulum.Time)
+
+
+def test_instance_with_aware_time() -> None:
+ dt = pendulum.instance(time(12, 34, 56, 123456, tzinfo=timezone("Europe/Paris")))
+
+ assert isinstance(dt, pendulum.Time)
+ assert isinstance(dt.tzinfo, Timezone)
+ assert dt.tzinfo.name == "Europe/Paris"
+
+
+def test_safe_timezone_with_tzinfo_objects() -> None:
tz = _safe_timezone(pytz.timezone("Europe/Paris"))
assert isinstance(tz, Timezone)
diff --git a/tests/test_parsing.py b/tests/test_parsing.py
index 0e5308c..f01653a 100644
--- a/tests/test_parsing.py
+++ b/tests/test_parsing.py
@@ -8,13 +8,14 @@ from tests.conftest import assert_duration
from tests.conftest import assert_time
-def test_parse():
+def test_parse() -> None:
text = "2016-10-16T12:34:56.123456+01:30"
dt = pendulum.parse(text)
assert isinstance(dt, pendulum.DateTime)
assert_datetime(dt, 2016, 10, 16, 12, 34, 56, 123456)
+ assert dt.tz is not None
assert dt.tz.name == "+01:30"
assert dt.offset == 5400
@@ -36,16 +37,18 @@ def test_parse():
assert dt.offset == 0
-def test_parse_with_timezone():
+def test_parse_with_timezone() -> None:
text = "2016-10-16T12:34:56.123456"
dt = pendulum.parse(text, tz="Europe/Paris")
+ assert isinstance(dt, pendulum.DateTime)
assert_datetime(dt, 2016, 10, 16, 12, 34, 56, 123456)
+ assert dt.tz is not None
assert dt.tz.name == "Europe/Paris"
assert dt.offset == 7200
-def test_parse_exact():
+def test_parse_exact() -> None:
text = "2016-10-16T12:34:56.123456+01:30"
dt = pendulum.parse(text, exact=True)
@@ -76,7 +79,7 @@ def test_parse_exact():
assert_time(dt, 13, 0, 0)
-def test_parse_duration():
+def test_parse_duration() -> None:
text = "P2Y3M4DT5H6M7S"
duration = pendulum.parse(text)
@@ -92,39 +95,39 @@ def test_parse_duration():
assert_duration(duration, 0, 0, 2, 0, 0, 0, 0)
-def test_parse_interval():
+def test_parse_interval() -> None:
text = "2008-05-11T15:30:00Z/P1Y2M10DT2H30M"
- period = pendulum.parse(text)
+ interval = pendulum.parse(text)
- assert isinstance(period, pendulum.Interval)
- assert_datetime(period.start, 2008, 5, 11, 15, 30, 0, 0)
- assert period.start.offset == 0
- assert_datetime(period.end, 2009, 7, 21, 18, 0, 0, 0)
- assert period.end.offset == 0
+ assert isinstance(interval, pendulum.Interval)
+ assert_datetime(interval.start, 2008, 5, 11, 15, 30, 0, 0)
+ assert interval.start.offset == 0
+ assert_datetime(interval.end, 2009, 7, 21, 18, 0, 0, 0)
+ assert interval.end.offset == 0
text = "P1Y2M10DT2H30M/2008-05-11T15:30:00Z"
- period = pendulum.parse(text)
+ interval = pendulum.parse(text)
- assert isinstance(period, pendulum.Interval)
- assert_datetime(period.start, 2007, 3, 1, 13, 0, 0, 0)
- assert period.start.offset == 0
- assert_datetime(period.end, 2008, 5, 11, 15, 30, 0, 0)
- assert period.end.offset == 0
+ assert isinstance(interval, pendulum.Interval)
+ assert_datetime(interval.start, 2007, 3, 1, 13, 0, 0, 0)
+ assert interval.start.offset == 0
+ assert_datetime(interval.end, 2008, 5, 11, 15, 30, 0, 0)
+ assert interval.end.offset == 0
text = "2007-03-01T13:00:00Z/2008-05-11T15:30:00Z"
- period = pendulum.parse(text)
+ interval = pendulum.parse(text)
- assert isinstance(period, pendulum.Interval)
- assert_datetime(period.start, 2007, 3, 1, 13, 0, 0, 0)
- assert period.start.offset == 0
- assert_datetime(period.end, 2008, 5, 11, 15, 30, 0, 0)
- assert period.end.offset == 0
+ assert isinstance(interval, pendulum.Interval)
+ assert_datetime(interval.start, 2007, 3, 1, 13, 0, 0, 0)
+ assert interval.start.offset == 0
+ assert_datetime(interval.end, 2008, 5, 11, 15, 30, 0, 0)
+ assert interval.end.offset == 0
-def test_parse_now():
+def test_parse_now() -> None:
dt = pendulum.parse("now")
assert dt.timezone_name == "America/Toronto"
@@ -135,7 +138,7 @@ def test_parse_now():
assert pendulum.parse("now") == mock_now
-def test_parse_with_utc_timezone():
+def test_parse_with_utc_timezone() -> None:
dt = pendulum.parse("2020-02-05T20:05:37.364951Z")
assert dt.to_iso8601_string() == "2020-02-05T20:05:37.364951Z"
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_helpers.py b/tests/tz/test_helpers.py
index edec6fd..9402372 100644
--- a/tests/tz/test_helpers.py
+++ b/tests/tz/test_helpers.py
@@ -2,7 +2,7 @@ from __future__ import annotations
import pytest
-from pendulum.tz import timezone
+from pendulum import timezone
from pendulum.tz.exceptions import InvalidTimezone
from pendulum.tz.timezone import FixedTimezone
from pendulum.tz.timezone import Timezone
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")