diff options
Diffstat (limited to '')
-rw-r--r-- | .flake8 | 28 | ||||
-rw-r--r-- | .github/workflows/codspeed.yml | 52 | ||||
-rw-r--r-- | .github/workflows/release.yml | 98 | ||||
-rw-r--r-- | .github/workflows/tests.yml | 31 | ||||
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | .pre-commit-config.yaml | 62 | ||||
-rw-r--r-- | CHANGELOG.md | 39 | ||||
-rw-r--r-- | Makefile | 47 | ||||
-rw-r--r-- | README.rst | 22 | ||||
-rwxr-xr-x | build-wheels.sh | 27 | ||||
-rw-r--r-- | build.py | 33 | ||||
-rwxr-xr-x | clock | 12 | ||||
-rw-r--r-- | codecov.yml | 7 | ||||
-rw-r--r-- | docs/docs/difference.md | 2 | ||||
-rw-r--r-- | docs/docs/index.md | 2 | ||||
-rw-r--r-- | docs/docs/installation.md | 20 | ||||
-rw-r--r-- | docs/docs/interval.md (renamed from docs/docs/period.md) | 72 | ||||
-rw-r--r-- | docs/docs/introduction.md | 2 | ||||
-rw-r--r-- | docs/docs/limitations.md | 4 | ||||
-rw-r--r-- | docs/docs/testing.md | 94 | ||||
-rw-r--r-- | docs/docs/timezones.md | 2 | ||||
-rw-r--r-- | meson.build | 20 | ||||
-rw-r--r-- | pendulum/__version__.py | 1 | ||||
-rw-r--r-- | pendulum/_extensions/_helpers.c | 931 | ||||
-rw-r--r-- | pendulum/exceptions.py | 8 | ||||
-rw-r--r-- | pendulum/parsing/_iso8601.c | 1361 | ||||
-rw-r--r-- | pendulum/parsing/_iso8601.pyi | 22 | ||||
-rw-r--r-- | pendulum/testing/traveller.py | 139 | ||||
-rw-r--r-- | pendulum/utils/_compat.py | 13 | ||||
-rw-r--r-- | poetry.lock | 1662 | ||||
-rw-r--r-- | pyproject.toml | 176 | ||||
-rw-r--r-- | rust/.cargo/config.toml | 15 | ||||
-rw-r--r-- | rust/Cargo.lock | 318 | ||||
-rw-r--r-- | rust/Cargo.toml | 22 | ||||
-rw-r--r-- | rust/src/constants.rs | 56 | ||||
-rw-r--r-- | rust/src/helpers.rs | 122 | ||||
-rw-r--r-- | rust/src/lib.rs | 12 | ||||
-rw-r--r-- | rust/src/parsing.rs | 905 | ||||
-rw-r--r-- | rust/src/python/helpers.rs | 388 | ||||
-rw-r--r-- | rust/src/python/mod.rs | 27 | ||||
-rw-r--r-- | rust/src/python/parsing.rs | 117 | ||||
-rw-r--r-- | rust/src/python/types/duration.rs | 59 | ||||
-rw-r--r-- | rust/src/python/types/interval.rs | 46 | ||||
-rw-r--r-- | rust/src/python/types/mod.rs | 7 | ||||
-rw-r--r-- | rust/src/python/types/precise_diff.rs | 53 | ||||
-rw-r--r-- | rust/src/python/types/timezone.rs | 52 | ||||
-rw-r--r-- | src/pendulum/__init__.py (renamed from pendulum/__init__.py) | 121 | ||||
-rw-r--r-- | src/pendulum/__version__.py | 4 | ||||
-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.py | 13 | ||||
-rw-r--r-- | src/pendulum/duration.py (renamed from pendulum/duration.py) | 79 | ||||
-rw-r--r-- | src/pendulum/exceptions.py | 13 | ||||
-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.py | 25 | ||||
-rw-r--r-- | src/pendulum/locales/en_gb/locale.py | 240 | ||||
-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.py | 25 | ||||
-rw-r--r-- | src/pendulum/locales/en_us/locale.py | 240 | ||||
-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.py | 24 | ||||
-rw-r--r-- | src/pendulum/locales/tr/locale.py | 225 | ||||
-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.py | 172 | ||||
-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__.py | 0 | ||||
-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__.py | 0 | ||||
-rw-r--r-- | src/pendulum/utils/_compat.py | 15 | ||||
-rw-r--r-- | src/pendulum/utils/_zoneinfo.py | 80 | ||||
-rw-r--r-- | tests/benchmarks/__init__.py | 0 | ||||
-rw-r--r-- | tests/benchmarks/test_parse_8601.py | 51 | ||||
-rw-r--r-- | tests/conftest.py | 51 | ||||
-rw-r--r-- | tests/date/test_comparison.py | 2 | ||||
-rw-r--r-- | tests/date/test_day_of_week_modifiers.py | 18 | ||||
-rw-r--r-- | tests/date/test_diff.py | 2 | ||||
-rw-r--r-- | tests/datetime/test_add.py | 20 | ||||
-rw-r--r-- | tests/datetime/test_behavior.py | 8 | ||||
-rw-r--r-- | tests/datetime/test_comparison.py | 2 | ||||
-rw-r--r-- | tests/datetime/test_construct.py | 27 | ||||
-rw-r--r-- | tests/datetime/test_day_of_week_modifiers.py | 18 | ||||
-rw-r--r-- | tests/datetime/test_from_format.py | 35 | ||||
-rw-r--r-- | tests/datetime/test_getters.py | 51 | ||||
-rw-r--r-- | tests/datetime/test_start_end_of.py | 40 | ||||
-rw-r--r-- | tests/datetime/test_strings.py | 10 | ||||
-rw-r--r-- | tests/duration/test_behavior.py | 15 | ||||
-rw-r--r-- | tests/formatting/test_formatter.py | 14 | ||||
-rw-r--r-- | tests/interval/test_add_subtract.py | 20 | ||||
-rw-r--r-- | tests/interval/test_behavior.py | 21 | ||||
-rw-r--r-- | tests/interval/test_construct.py | 28 | ||||
-rw-r--r-- | tests/interval/test_hashing.py | 20 | ||||
-rw-r--r-- | tests/interval/test_in_words.py | 38 | ||||
-rw-r--r-- | tests/interval/test_range.py | 2 | ||||
-rw-r--r-- | tests/localization/test_cs.py | 1 | ||||
-rw-r--r-- | tests/localization/test_da.py | 1 | ||||
-rw-r--r-- | tests/localization/test_de.py | 1 | ||||
-rw-r--r-- | tests/localization/test_es.py | 1 | ||||
-rw-r--r-- | tests/localization/test_fa.py | 1 | ||||
-rw-r--r-- | tests/localization/test_fo.py | 1 | ||||
-rw-r--r-- | tests/localization/test_fr.py | 1 | ||||
-rw-r--r-- | tests/localization/test_he.py | 1 | ||||
-rw-r--r-- | tests/localization/test_id.py | 1 | ||||
-rw-r--r-- | tests/localization/test_it.py | 1 | ||||
-rw-r--r-- | tests/localization/test_ja.py | 1 | ||||
-rw-r--r-- | tests/localization/test_ko.py | 1 | ||||
-rw-r--r-- | tests/localization/test_lt.py | 1 | ||||
-rw-r--r-- | tests/localization/test_nb.py | 1 | ||||
-rw-r--r-- | tests/localization/test_nl.py | 1 | ||||
-rw-r--r-- | tests/localization/test_nn.py | 1 | ||||
-rw-r--r-- | tests/localization/test_pl.py | 1 | ||||
-rw-r--r-- | tests/localization/test_ru.py | 1 | ||||
-rw-r--r-- | tests/localization/test_sk.py | 1 | ||||
-rw-r--r-- | tests/localization/test_sv.py | 1 | ||||
-rw-r--r-- | tests/localization/test_tr.py | 66 | ||||
-rw-r--r-- | tests/parsing/test_parse_iso8601.py | 485 | ||||
-rw-r--r-- | tests/parsing/test_parsing.py | 15 | ||||
-rw-r--r-- | tests/test_helpers.py | 57 | ||||
-rw-r--r-- | tests/test_main.py | 52 | ||||
-rw-r--r-- | tests/test_parsing.py | 53 | ||||
-rw-r--r-- | tests/testing/test_time_travel.py | 1 | ||||
-rw-r--r-- | tests/time/test_comparison.py | 2 | ||||
-rw-r--r-- | tests/tz/test_helpers.py | 2 | ||||
-rw-r--r-- | tests/tz/test_timezone.py | 10 |
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: | @@ -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 @@ -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 @@ -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({}) @@ -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, µseconds, &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, µsecond)) - { - 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, µseconds)) - 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") |