From d6d80a17444c90259c5bfdacb84c61e6bfece655 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 5 Jan 2023 11:38:41 +0100 Subject: Merging upstream version 3.0.0~a1. Signed-off-by: Daniel Baumann --- .flake8 | 28 + .github/FUNDING.yml | 1 + .github/ISSUE_TEMPLATE/---bug-report.md | 31 + .github/ISSUE_TEMPLATE/---documentation.md | 22 + .github/ISSUE_TEMPLATE/---everything-else.md | 19 + .github/ISSUE_TEMPLATE/---feature-request.md | 23 + .github/PULL_REQUEST_TEMPLATE.md | 15 + .github/workflows/release.yml | 79 + .github/workflows/tests.yml | 76 + .gitignore | 37 + .pre-commit-config.yaml | 71 + CHANGELOG.md | 177 ++ LICENSE | 40 +- Makefile | 53 + PKG-INFO | 251 -- README.rst | 400 ++- build-wheels.sh | 27 + build.py | 120 +- clock | 274 ++ codecov.yml | 7 + docs/Makefile | 2 + docs/docs/addition_subtraction.md | 87 + docs/docs/attributes_properties.md | 87 + docs/docs/comparison.md | 77 + docs/docs/difference.md | 115 + docs/docs/duration.md | 177 ++ docs/docs/fluent_helpers.md | 67 + docs/docs/index.md | 17 + docs/docs/installation.md | 13 + docs/docs/instantiation.md | 144 + docs/docs/introduction.md | 21 + docs/docs/limitations.md | 43 + docs/docs/localization.md | 36 + docs/docs/modifiers.md | 86 + docs/docs/parsing.md | 114 + docs/docs/period.md | 168 ++ docs/docs/string_formatting.md | 175 ++ docs/docs/testing.md | 59 + docs/docs/timezones.md | 193 ++ docs/mkdocs.yml | 17 + docs/theme/main.html | 33 + meson.build | 20 + pendulum/__init__.py | 678 ++--- pendulum/__version__.py | 2 +- pendulum/_extensions/_helpers.c | 1861 ++++++------- pendulum/_extensions/_helpers.pyi | 31 + pendulum/_extensions/helpers.py | 722 ++--- pendulum/constants.py | 216 +- pendulum/date.py | 1651 +++++------ pendulum/datetime.py | 2944 +++++++++----------- pendulum/duration.py | 981 +++---- pendulum/exceptions.py | 14 +- pendulum/formatting/__init__.py | 9 +- pendulum/formatting/difference_formatter.py | 299 +- pendulum/formatting/formatter.py | 1368 +++++---- pendulum/helpers.py | 447 ++- pendulum/interval.py | 448 +++ pendulum/locales/cs/__init__.py | 0 pendulum/locales/cs/custom.py | 23 + pendulum/locales/cs/locale.py | 266 ++ pendulum/locales/da/custom.py | 40 +- pendulum/locales/da/locale.py | 297 +- pendulum/locales/de/custom.py | 76 +- pendulum/locales/de/locale.py | 291 +- pendulum/locales/en/custom.py | 50 +- pendulum/locales/en/locale.py | 303 +- pendulum/locales/es/custom.py | 50 +- pendulum/locales/es/locale.py | 285 +- pendulum/locales/fa/custom.py | 40 +- pendulum/locales/fa/locale.py | 273 +- pendulum/locales/fo/custom.py | 44 +- pendulum/locales/fo/locale.py | 267 +- pendulum/locales/fr/__init__.py | 1 - pendulum/locales/fr/custom.py | 50 +- pendulum/locales/fr/locale.py | 269 +- pendulum/locales/he/__init__.py | 0 pendulum/locales/he/custom.py | 23 + pendulum/locales/he/locale.py | 269 ++ pendulum/locales/id/custom.py | 42 +- pendulum/locales/id/locale.py | 285 +- pendulum/locales/it/custom.py | 51 +- pendulum/locales/it/locale.py | 293 +- pendulum/locales/ja/__init__.py | 0 pendulum/locales/ja/custom.py | 21 + pendulum/locales/ja/locale.py | 194 ++ pendulum/locales/ko/custom.py | 40 +- pendulum/locales/ko/locale.py | 213 +- pendulum/locales/locale.py | 206 +- pendulum/locales/lt/custom.py | 240 +- pendulum/locales/lt/locale.py | 513 ++-- pendulum/locales/nb/custom.py | 44 +- pendulum/locales/nb/locale.py | 303 +- pendulum/locales/nl/custom.py | 50 +- pendulum/locales/nl/locale.py | 271 +- pendulum/locales/nn/custom.py | 44 +- pendulum/locales/nn/locale.py | 285 +- pendulum/locales/pl/custom.py | 46 +- pendulum/locales/pl/locale.py | 561 ++-- pendulum/locales/pt_br/custom.py | 40 +- pendulum/locales/pt_br/locale.py | 289 +- pendulum/locales/ru/custom.py | 44 +- pendulum/locales/ru/locale.py | 543 ++-- pendulum/locales/sk/__init__.py | 0 pendulum/locales/sk/custom.py | 20 + pendulum/locales/sk/locale.py | 266 ++ pendulum/locales/sv/__init__.py | 0 pendulum/locales/sv/custom.py | 20 + pendulum/locales/sv/locale.py | 222 ++ pendulum/locales/zh/custom.py | 40 +- pendulum/locales/zh/locale.py | 229 +- pendulum/mixins/__init__.py | 1 - pendulum/mixins/default.py | 79 +- pendulum/parser.py | 245 +- pendulum/parsing/__init__.py | 467 ++-- pendulum/parsing/_iso8601.c | 2732 +++++++++--------- pendulum/parsing/_iso8601.pyi | 22 + pendulum/parsing/exceptions/__init__.py | 9 +- pendulum/parsing/iso8601.py | 901 +++--- pendulum/period.py | 390 --- pendulum/testing/__init__.py | 0 pendulum/testing/traveller.py | 139 + pendulum/time.py | 587 ++-- pendulum/tz/__init__.py | 140 +- pendulum/tz/data/windows.py | 276 +- pendulum/tz/exceptions.py | 55 +- pendulum/tz/local_timezone.py | 517 ++-- pendulum/tz/timezone.py | 594 ++-- pendulum/tz/zoneinfo/__init__.py | 16 - pendulum/tz/zoneinfo/exceptions.py | 18 - pendulum/tz/zoneinfo/posix_timezone.py | 270 -- pendulum/tz/zoneinfo/reader.py | 224 -- pendulum/tz/zoneinfo/timezone.py | 128 - pendulum/tz/zoneinfo/transition.py | 77 - pendulum/tz/zoneinfo/transition_type.py | 35 - pendulum/utils/_compat.py | 67 +- poetry.lock | 1172 ++++++++ pyproject.toml | 264 +- tests/__init__.py | 0 tests/conftest.py | 101 + tests/date/__init__.py | 0 tests/date/test_add.py | 88 + tests/date/test_behavior.py | 73 + tests/date/test_comparison.py | 247 ++ tests/date/test_construct.py | 16 + tests/date/test_day_of_week_modifiers.py | 298 ++ tests/date/test_diff.py | 365 +++ tests/date/test_fluent_setters.py | 29 + tests/date/test_getters.py | 87 + tests/date/test_start_end_of.py | 252 ++ tests/date/test_strings.py | 48 + tests/date/test_sub.py | 90 + tests/datetime/__init__.py | 0 tests/datetime/test_add.py | 268 ++ tests/datetime/test_behavior.py | 172 ++ tests/datetime/test_comparison.py | 394 +++ tests/datetime/test_construct.py | 182 ++ tests/datetime/test_create_from_timestamp.py | 24 + tests/datetime/test_day_of_week_modifiers.py | 314 +++ tests/datetime/test_diff.py | 880 ++++++ tests/datetime/test_fluent_setters.py | 181 ++ tests/datetime/test_from_format.py | 203 ++ tests/datetime/test_getters.py | 248 ++ tests/datetime/test_naive.py | 78 + tests/datetime/test_replace.py | 61 + tests/datetime/test_start_end_of.py | 285 ++ tests/datetime/test_strings.py | 141 + tests/datetime/test_sub.py | 251 ++ tests/datetime/test_timezone.py | 38 + tests/duration/__init__.py | 0 tests/duration/test_add_sub.py | 54 + tests/duration/test_arithmetic.py | 85 + tests/duration/test_behavior.py | 21 + tests/duration/test_construct.py | 99 + tests/duration/test_in_methods.py | 28 + tests/duration/test_in_words.py | 77 + tests/duration/test_total_methods.py | 28 + tests/fixtures/__init__.py | 0 tests/fixtures/tz/Paris | Bin 0 -> 2971 bytes tests/fixtures/tz/clock/etc/sysconfig/clock | 1 + tests/fixtures/tz/symlink/etc/localtime | 1 + .../tz/symlink/usr/share/zoneinfo/Europe/Paris | Bin 0 -> 2971 bytes tests/fixtures/tz/timezone_dir/etc/localtime | 1 + .../fixtures/tz/timezone_dir/etc/timezone/blank.md | 5 + .../timezone_dir/usr/share/zoneinfo/Europe/Paris | Bin 0 -> 2971 bytes tests/formatting/__init__.py | 0 tests/formatting/test_formatter.py | 263 ++ tests/helpers/__init__.py | 0 tests/helpers/test_local_time.py | 31 + tests/interval/__init__.py | 0 tests/interval/test_add_subtract.py | 49 + tests/interval/test_arithmetic.py | 53 + tests/interval/test_behavior.py | 54 + tests/interval/test_construct.py | 121 + tests/interval/test_hashing.py | 23 + tests/interval/test_in_words.py | 70 + tests/interval/test_range.py | 119 + tests/localization/__init__.py | 0 tests/localization/test_cs.py | 109 + tests/localization/test_da.py | 65 + tests/localization/test_de.py | 65 + tests/localization/test_es.py | 65 + tests/localization/test_fa.py | 65 + tests/localization/test_fo.py | 65 + tests/localization/test_fr.py | 84 + tests/localization/test_he.py | 65 + tests/localization/test_id.py | 68 + tests/localization/test_it.py | 85 + tests/localization/test_ja.py | 68 + tests/localization/test_ko.py | 65 + tests/localization/test_lt.py | 68 + tests/localization/test_nb.py | 84 + tests/localization/test_nl.py | 83 + tests/localization/test_nn.py | 84 + tests/localization/test_pl.py | 109 + tests/localization/test_ru.py | 86 + tests/localization/test_sk.py | 112 + tests/localization/test_sv.py | 86 + tests/parsing/__init__.py | 0 tests/parsing/test_parse_iso8601.py | 465 ++++ tests/parsing/test_parsing.py | 687 +++++ tests/parsing/test_parsing_duration.py | 298 ++ tests/test_helpers.py | 179 ++ tests/test_main.py | 13 + tests/test_parsing.py | 141 + tests/testing/__init__.py | 0 tests/testing/test_time_travel.py | 85 + tests/time/__init__.py | 0 tests/time/test_add.py | 78 + tests/time/test_behavior.py | 49 + tests/time/test_comparison.py | 185 ++ tests/time/test_construct.py | 22 + tests/time/test_diff.py | 350 +++ tests/time/test_fluent_setters.py | 12 + tests/time/test_strings.py | 39 + tests/time/test_sub.py | 112 + tests/tz/__init__.py | 0 tests/tz/test_helpers.py | 27 + tests/tz/test_local_timezone.py | 52 + tests/tz/test_timezone.py | 447 +++ tests/tz/test_timezones.py | 16 + tox.ini | 18 + 241 files changed, 29866 insertions(+), 14517 deletions(-) create mode 100644 .flake8 create mode 100644 .github/FUNDING.yml create mode 100644 .github/ISSUE_TEMPLATE/---bug-report.md create mode 100644 .github/ISSUE_TEMPLATE/---documentation.md create mode 100644 .github/ISSUE_TEMPLATE/---everything-else.md create mode 100644 .github/ISSUE_TEMPLATE/---feature-request.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/tests.yml create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 CHANGELOG.md create mode 100644 Makefile delete mode 100644 PKG-INFO create mode 100755 build-wheels.sh create mode 100755 clock create mode 100644 codecov.yml create mode 100644 docs/Makefile create mode 100644 docs/docs/addition_subtraction.md create mode 100644 docs/docs/attributes_properties.md create mode 100644 docs/docs/comparison.md create mode 100644 docs/docs/difference.md create mode 100644 docs/docs/duration.md create mode 100644 docs/docs/fluent_helpers.md create mode 100644 docs/docs/index.md create mode 100644 docs/docs/installation.md create mode 100644 docs/docs/instantiation.md create mode 100644 docs/docs/introduction.md create mode 100644 docs/docs/limitations.md create mode 100644 docs/docs/localization.md create mode 100644 docs/docs/modifiers.md create mode 100644 docs/docs/parsing.md create mode 100644 docs/docs/period.md create mode 100644 docs/docs/string_formatting.md create mode 100644 docs/docs/testing.md create mode 100644 docs/docs/timezones.md create mode 100644 docs/mkdocs.yml create mode 100644 docs/theme/main.html create mode 100644 meson.build create mode 100644 pendulum/_extensions/_helpers.pyi create mode 100644 pendulum/interval.py create mode 100644 pendulum/locales/cs/__init__.py create mode 100644 pendulum/locales/cs/custom.py create mode 100644 pendulum/locales/cs/locale.py create mode 100644 pendulum/locales/he/__init__.py create mode 100644 pendulum/locales/he/custom.py create mode 100644 pendulum/locales/he/locale.py create mode 100644 pendulum/locales/ja/__init__.py create mode 100644 pendulum/locales/ja/custom.py create mode 100644 pendulum/locales/ja/locale.py create mode 100644 pendulum/locales/sk/__init__.py create mode 100644 pendulum/locales/sk/custom.py create mode 100644 pendulum/locales/sk/locale.py create mode 100644 pendulum/locales/sv/__init__.py create mode 100644 pendulum/locales/sv/custom.py create mode 100644 pendulum/locales/sv/locale.py create mode 100644 pendulum/parsing/_iso8601.pyi delete mode 100644 pendulum/period.py create mode 100644 pendulum/testing/__init__.py create mode 100644 pendulum/testing/traveller.py delete mode 100644 pendulum/tz/zoneinfo/__init__.py delete mode 100644 pendulum/tz/zoneinfo/exceptions.py delete mode 100644 pendulum/tz/zoneinfo/posix_timezone.py delete mode 100644 pendulum/tz/zoneinfo/reader.py delete mode 100644 pendulum/tz/zoneinfo/timezone.py delete mode 100644 pendulum/tz/zoneinfo/transition.py delete mode 100644 pendulum/tz/zoneinfo/transition_type.py create mode 100644 poetry.lock create mode 100644 tests/__init__.py create mode 100644 tests/conftest.py create mode 100644 tests/date/__init__.py create mode 100644 tests/date/test_add.py create mode 100644 tests/date/test_behavior.py create mode 100644 tests/date/test_comparison.py create mode 100644 tests/date/test_construct.py create mode 100644 tests/date/test_day_of_week_modifiers.py create mode 100644 tests/date/test_diff.py create mode 100644 tests/date/test_fluent_setters.py create mode 100644 tests/date/test_getters.py create mode 100644 tests/date/test_start_end_of.py create mode 100644 tests/date/test_strings.py create mode 100644 tests/date/test_sub.py create mode 100644 tests/datetime/__init__.py create mode 100644 tests/datetime/test_add.py create mode 100644 tests/datetime/test_behavior.py create mode 100644 tests/datetime/test_comparison.py create mode 100644 tests/datetime/test_construct.py create mode 100644 tests/datetime/test_create_from_timestamp.py create mode 100644 tests/datetime/test_day_of_week_modifiers.py create mode 100644 tests/datetime/test_diff.py create mode 100644 tests/datetime/test_fluent_setters.py create mode 100644 tests/datetime/test_from_format.py create mode 100644 tests/datetime/test_getters.py create mode 100644 tests/datetime/test_naive.py create mode 100644 tests/datetime/test_replace.py create mode 100644 tests/datetime/test_start_end_of.py create mode 100644 tests/datetime/test_strings.py create mode 100644 tests/datetime/test_sub.py create mode 100644 tests/datetime/test_timezone.py create mode 100644 tests/duration/__init__.py create mode 100644 tests/duration/test_add_sub.py create mode 100644 tests/duration/test_arithmetic.py create mode 100644 tests/duration/test_behavior.py create mode 100644 tests/duration/test_construct.py create mode 100644 tests/duration/test_in_methods.py create mode 100644 tests/duration/test_in_words.py create mode 100644 tests/duration/test_total_methods.py create mode 100644 tests/fixtures/__init__.py create mode 100644 tests/fixtures/tz/Paris create mode 100644 tests/fixtures/tz/clock/etc/sysconfig/clock create mode 120000 tests/fixtures/tz/symlink/etc/localtime create mode 100644 tests/fixtures/tz/symlink/usr/share/zoneinfo/Europe/Paris create mode 120000 tests/fixtures/tz/timezone_dir/etc/localtime create mode 100644 tests/fixtures/tz/timezone_dir/etc/timezone/blank.md create mode 100644 tests/fixtures/tz/timezone_dir/usr/share/zoneinfo/Europe/Paris create mode 100644 tests/formatting/__init__.py create mode 100644 tests/formatting/test_formatter.py create mode 100644 tests/helpers/__init__.py create mode 100644 tests/helpers/test_local_time.py create mode 100644 tests/interval/__init__.py create mode 100644 tests/interval/test_add_subtract.py create mode 100644 tests/interval/test_arithmetic.py create mode 100644 tests/interval/test_behavior.py create mode 100644 tests/interval/test_construct.py create mode 100644 tests/interval/test_hashing.py create mode 100644 tests/interval/test_in_words.py create mode 100644 tests/interval/test_range.py create mode 100644 tests/localization/__init__.py create mode 100644 tests/localization/test_cs.py create mode 100644 tests/localization/test_da.py create mode 100644 tests/localization/test_de.py create mode 100644 tests/localization/test_es.py create mode 100644 tests/localization/test_fa.py create mode 100644 tests/localization/test_fo.py create mode 100644 tests/localization/test_fr.py create mode 100644 tests/localization/test_he.py create mode 100644 tests/localization/test_id.py create mode 100644 tests/localization/test_it.py create mode 100644 tests/localization/test_ja.py create mode 100644 tests/localization/test_ko.py create mode 100644 tests/localization/test_lt.py create mode 100644 tests/localization/test_nb.py create mode 100644 tests/localization/test_nl.py create mode 100644 tests/localization/test_nn.py create mode 100644 tests/localization/test_pl.py create mode 100644 tests/localization/test_ru.py create mode 100644 tests/localization/test_sk.py create mode 100644 tests/localization/test_sv.py create mode 100644 tests/parsing/__init__.py create mode 100644 tests/parsing/test_parse_iso8601.py create mode 100644 tests/parsing/test_parsing.py create mode 100644 tests/parsing/test_parsing_duration.py create mode 100644 tests/test_helpers.py create mode 100644 tests/test_main.py create mode 100644 tests/test_parsing.py create mode 100644 tests/testing/__init__.py create mode 100644 tests/testing/test_time_travel.py create mode 100644 tests/time/__init__.py create mode 100644 tests/time/test_add.py create mode 100644 tests/time/test_behavior.py create mode 100644 tests/time/test_comparison.py create mode 100644 tests/time/test_construct.py create mode 100644 tests/time/test_diff.py create mode 100644 tests/time/test_fluent_setters.py create mode 100644 tests/time/test_strings.py create mode 100644 tests/time/test_sub.py create mode 100644 tests/tz/__init__.py create mode 100644 tests/tz/test_helpers.py create mode 100644 tests/tz/test_local_timezone.py create mode 100644 tests/tz/test_timezone.py create mode 100644 tests/tz/test_timezones.py create mode 100644 tox.ini diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..d571285 --- /dev/null +++ b/.flake8 @@ -0,0 +1,28 @@ +[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/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..fca881b --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [sdispater] diff --git a/.github/ISSUE_TEMPLATE/---bug-report.md b/.github/ISSUE_TEMPLATE/---bug-report.md new file mode 100644 index 0000000..1f8b618 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/---bug-report.md @@ -0,0 +1,31 @@ +--- +name: "\U0001F41E Bug Report" +about: Did you find a bug? +title: '' +labels: 'Bug' +assignees: '' + +--- + + + + +- [ ] I am on the [latest](https://github.com/sdispater/pendulum/releases/latest) Pendulum version. +- [ ] I have searched the [issues](https://github.com/sdispater/pendulum/issues) of this repo and believe that this is not a duplicate. + + + +- **OS version and name**: +- **Pendulum version**: + +## Issue + diff --git a/.github/ISSUE_TEMPLATE/---documentation.md b/.github/ISSUE_TEMPLATE/---documentation.md new file mode 100644 index 0000000..0d4ac41 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/---documentation.md @@ -0,0 +1,22 @@ +--- +name: "\U0001F4DA Documentation" +about: Did you find errors, problems, or anything unintelligible in the docs (https://pendulum.eustace.io/docs)? +title: '' +labels: 'Documentation' +assignees: '' + +--- + + + + +- [ ] I have searched the [issues](https://github.com/sdispater/pendulum/issues) of this repo and believe that this is not a duplicate. + +## Issue + diff --git a/.github/ISSUE_TEMPLATE/---everything-else.md b/.github/ISSUE_TEMPLATE/---everything-else.md new file mode 100644 index 0000000..1fc60fa --- /dev/null +++ b/.github/ISSUE_TEMPLATE/---everything-else.md @@ -0,0 +1,19 @@ +--- +name: "\U0001F5C3 Everything Else" +about: For questions and issues that do not fall in any of the other categories. This + can include questions about Pendulum's roadmap. +title: '' +labels: '' +assignees: '' + +--- + + +- [ ] I have searched the [issues](https://github.com/sdispater/pendulum/issues) of this repo and believe that this is not a duplicate. +- [ ] I have searched the [documentation](https://pendulum.eustace.io/docs/) and believe that my question is not covered. + +## Issue + diff --git a/.github/ISSUE_TEMPLATE/---feature-request.md b/.github/ISSUE_TEMPLATE/---feature-request.md new file mode 100644 index 0000000..4605055 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/---feature-request.md @@ -0,0 +1,23 @@ +--- +name: "\U0001F381 Feature Request" +about: Do you have ideas for new features and improvements? +title: '' +labels: 'Feature' +assignees: '' + +--- + + + + +- [ ] I have searched the [issues](https://github.com/sdispater/pendulum/issues) of this repo and believe that this is not a duplicate. +- [ ] I have searched the [documentation](https://pendulum.eustace.io/docs/) and believe that my question is not covered. + +## Feature Request + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..4c0ce41 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,15 @@ +## Pull Request Check List + + + +- [ ] Added **tests** for changed code. +- [ ] Updated **documentation** for changed code. + + diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..59062ae --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,79 @@ +name: Release + +on: + push: + tags: + - '*.*.*' + +jobs: + + build: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }}-latest + strategy: + matrix: + os: [ ubuntu, windows, macos ] + + steps: + - uses: actions/checkout@v3 + + - name: Build wheels + uses: pypa/cibuildwheel@v2.10.1 + env: + CIBW_PROJECT_REQUIRES_PYTHON: ">=3.7" + with: + package-dir: . + output-dir: dist + + - uses: actions/upload-artifact@v3 + with: + name: dist + path: ./dist/* + + Release: + needs: [ build ] + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Download artifacts + uses: actions/download-artifact@v3 + with: + name: dist + path: dist + + - name: Install Poetry + run: | + curl -fsS https://install.python-poetry.org | python - -y + + - 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 + + - name: Check Version + id: check-version + run: | + [[ "${GITHUB_REF#refs/tags/}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] \ + || echo ::set-output name=prerelease::true + + - name: Create Release + uses: ncipollo/release-action@v1 + with: + artifacts: "dist/*" + token: ${{ secrets.GITHUB_TOKEN }} + draft: false + prerelease: steps.check-version.outputs.prerelease == 'true' + + - name: Publish to PyPI + env: + POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_TOKEN }} + run: | + poetry publish diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..341859e --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,76 @@ +name: Tests + +on: + push: + paths-ignore: + - 'docs/**' + branches: + - master + pull_request: + paths-ignore: + - 'docs/**' + branches: + - '**' + +jobs: + 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"] + defaults: + run: + shell: bash + + steps: + - uses: actions/checkout@v3 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - 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 - --preview -y + + - name: Update PATH + if: ${{ matrix.os != 'Windows' }} + run: echo "$HOME/.local/bin" >> $GITHUB_PATH + + - name: Update Path for Windows + if: ${{ matrix.os == 'Windows' }} + run: echo "$APPDATA\Python\Scripts" >> $GITHUB_PATH + + - name: Configure poetry + run: poetry config virtualenvs.in-project true + + - name: Set up cache + uses: actions/cache@v3 + id: cache + with: + path: .venv + key: venv-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('**/poetry.lock') }} + + - name: Ensure cache is healthy + # MacOS does not come with `timeout` command out of the box + 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: Test Pure Python + run: | + PENDULUM_EXTENSIONS=0 poetry run pytest -q tests + + - name: Test + run: | + poetry run pytest -q tests diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bb25f8b --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +*.pyc + +# Packages +*.egg +*.egg-info +dist +build +_build +.cache +*.so + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox +nosetests.xml +.pytest_cache + +.DS_Store +.idea/* +.python-version + +/test.py +/test_*.* +/benchmark/* +benchmark.py +results.json +profile.html +/wheelhouse +/docs/site/* +setup.py + +# editor + +.vscode diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..0e8d094 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,71 @@ +ci: + autofix_prs: false + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: trailing-whitespace + exclude: ^tests/.*/fixtures/.* + - id: end-of-file-fixer + 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 + 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 + hooks: + - id: pyupgrade + exclude: ^build\.py$ + args: + - --py37-plus + + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v0.982 + hooks: + - id: mypy + 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 new file mode 100644 index 0000000..ae71991 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,177 @@ +# Change Log + +## [3.0.0a1] - 2022-11-23 + +### Added + +- Added new testing helpers to time travel. [#626](https://github.com/sdispater/pendulum/pull/626) + +### Changed + +- Dropped support for Python 2.7, 3.5 and 3.6. [#569](https://github.com/sdispater/pendulum/pull/569) +- The `Timezone` class now relies on the native `zoneinfo.ZoneInfo` class. [#569](https://github.com/sdispater/pendulum/pull/569) +- Renamed the `Period` class to `Interval`. [#676](https://github.com/sdispater/pendulum/pull/676) +- Renamed the `period` helper to `interval`. [#676](https://github.com/sdispater/pendulum/pull/676) +- Removed existing testing helpers: `test()` and `set_test_now()`. [#626](https://github.com/sdispater/pendulum/pull/626) + +### Locales + +- Added the `sk` locale. [#575](https://github.com/sdispater/pendulum/pull/575) +- Added the `ja` locale. [#610](https://github.com/sdispater/pendulum/pull/610) +- Added the `he` locale. [#585](https://github.com/sdispater/pendulum/pull/585) +- Added the `sv` locale. [#562](https://github.com/sdispater/pendulum/pull/562) + + +## [2.1.1] - 2020-07-13 + +### Fixed + +- Fixed errors where invalid timezones were matched in `from_format()` ([#374](https://github.com/sdispater/pendulum/pull/374)). +- Fixed errors when subtracting negative timedeltas ([#419](https://github.com/sdispater/pendulum/pull/419)). +- Fixed errors in total units computation for durations with years and months ([#482](https://github.com/sdispater/pendulum/pull/482)). +- Fixed an error where the `fold` attribute was overridden when using `replace()` ([#414](https://github.com/sdispater/pendulum/pull/414)). +- Fixed an error where `now()` was not returning the correct result on DST transitions ([#483](https://github.com/sdispater/pendulum/pull/483)). +- Fixed inconsistent typing annotation for the `parse()` function ([#452](https://github.com/sdispater/pendulum/pull/452)). + +### Locales + +- Added the `pl` locale ([#459](https://github.com/sdispater/pendulum/pull/459)). + + +## [2.1.0] - 2020-03-07 + +### Added + +- Added better typing and PEP-561 compliance ([#320](https://github.com/sdispater/pendulum/pull/320)). +- Added the `is_anniversary()` method as an alias of `is_birthday()` ([#298](https://github.com/sdispater/pendulum/pull/298)). + +### Changed + +- Dropped support for Python 3.4. +- `is_utc()` will now return `True` for any datetime with an offset of 0, similar to the behavior in the `1.*` versions ([#295](https://github.com/sdispater/pendulum/pull/295)) +- `Duration.in_words()` will now return `0 milliseconds` for empty durations. + +### Fixed + +- Fixed various issues with timezone transitions for some edge cases ([#321](https://github.com/sdispater/pendulum/pull/321), ([#350](https://github.com/sdispater/pendulum/pull/350))). +- Fixed out of bound detection for `nth_of("month")` ([#357](https://github.com/sdispater/pendulum/pull/357)). +- Fixed an error where extra text was accepted in `from_format()` ([#372](https://github.com/sdispater/pendulum/pull/372)). +- Fixed a recursion error when adding time to a `DateTime` with a fixed timezone ([#431](https://github.com/sdispater/pendulum/pull/431)). +- Fixed errors where `Period` instances were not properly compared to other classes, especially `timedelta` instances ([#427](https://github.com/sdispater/pendulum/pull/427)). +- Fixed deprecation warnings due to internal regexps ([#427](https://github.com/sdispater/pendulum/pull/427)). +- Fixed an error where the `test()` helper would not unset the test instance when an exception was raised ([#445](https://github.com/sdispater/pendulum/pull/445)). +- Fixed an error where the `week_of_month` attribute was not returning the correct value ([#446](https://github.com/sdispater/pendulum/pull/446)). +- Fixed an error in the way the `Z` ISO-8601 UTC designator was not parsed as UTC ([#448](https://github.com/sdispater/pendulum/pull/448)). + +### Locales + +- Added the `nl` locale. +- Added the `it` locale. +- Added the `id` locale. +- Added the `nb` locale. +- Added the `nn` locale. + + +## [2.0.5] - 2019-07-03 + +### Fixed + +- Fixed ISO week dates not being parsed properly in `from_format()`. +- Fixed loading of some timezones with empty posix spec. +- Fixed deprecation warnings. + +### Locales + +- Added RU locale. + + +## [2.0.4] - 2018-10-30 + +### Fixed + +- Fixed `from_format()` not recognizing input strings when the specified pattern had escaped elements. +- Fixed missing `x` token for string formatting. +- Fixed reading timezone files. +- Added support for parsing padded 2-digit days of the month with `from_format()` +- Fixed `from_format()` trying to parse escaped tokens. +- Fixed the `z` token timezone parsing in `from_format()` to allow underscores. +- Fixed C extensions build errors. +- Fixed `age` calculation for future dates. + + +## [2.0.3] - 2018-07-30 + +### Fixed + +- Fixed handling of `pytz` timezones. +- Fixed some formatter's tokens handling. +- Fixed errors on some systems when retrieving timezone from localtime files. +- Fixed `diff` methods. +- Fixed `closest()/farthest()` methods. + + +## [2.0.2] - 2018-05-29 + +### Fixed + +- Fixed the `weeks` property for negative `Period` instances. +- Fixed `start_of()` methods not setting microseconds to 0. +- Fixed errors on some systems when retrieving timezone from clock files. +- Fixed parsing of partial time. +- Fixed parsing not raising an error for week 53 for ordinary years. +- Fixed string formatting not supporting `strftime` format. + + +## [2.0.1] - 2018-05-10 + +### Fixed + +- Fixed behavior of the `YY` token in `from_format()`. +- Fixed errors on some systems when retrieving timezone from clock files. + + +## [2.0.0] - 2018-05-08 + +### Added + +- Added years and months support to durations. +- Added the `test_local_timezone()` and `set_local_timezone()` helpers to ease testing. +- Added support of ISO 8601 duration parsing. +- Added support of ISO 8601 interval parsing. +- Added a `local()` helper. +- Added a `naive()` helper and a `naive()` method. +- Added support for POSIX specification to extend timezones DST transitions. + +### Changed + +- `Pendulum` class has been renamed to `DateTime`. +- `Interval` class has been renamed to `Duration`. +- Changed and improved the timezone system. +- Removed the `create()` helper. +- Removed the `utcnow()` helper. +- `strict` keyword argument for `parse` has been renamed to `exact`. +- `at()` now supports setting partial time. +- `local`, `utc` and `is_dst` are now methods rather than properties (`is_local()`, `is_utc()`, `is_dst()`). +- Changed the `repr` of most common objects. +- Made the `strict` keyword argument for `parse` false by default, which means it will not fallback on the `dateutil` parser. +- Improved performances of the `precise_diff()` helper. +- The `alternative` formatter is now the default one. +- `set_to_string_format()/reset_to_string_format()` methods have been removed. +- `from_format()` now uses the alternative formatter tokens. +- Removed `xrange()` method of the `Period` class and made `range()` a generator. +- New locale system which uses CLDR data for most of the translations. +- `diff_for_humans()` now returns `a few seconds` where appropriate. +- Removed `Period.intersect()`. + + + +[Unreleased]: https://github.com/sdispater/pendulum/compare/3.0.0a1...master +[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 +[2.0.5]: https://github.com/sdispater/pendulum/releases/tag/2.0.5 +[2.0.4]: https://github.com/sdispater/pendulum/releases/tag/2.0.4 +[2.0.3]: https://github.com/sdispater/pendulum/releases/tag/2.0.3 +[2.0.2]: https://github.com/sdispater/pendulum/releases/tag/2.0.2 +[2.0.1]: https://github.com/sdispater/pendulum/releases/tag/2.0.1 +[2.0.0]: https://github.com/sdispater/pendulum/releases/tag/2.0.0 diff --git a/LICENSE b/LICENSE index b9cd466..701df22 100644 --- a/LICENSE +++ b/LICENSE @@ -1,20 +1,20 @@ -Copyright (c) 2015 Sébastien Eustace - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Copyright (c) 2015 Sébastien Eustace + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e68b94c --- /dev/null +++ b/Makefile @@ -0,0 +1,53 @@ +# 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: + @sh -c "$(MAKE) -p no_targets__ | \ + awk -F':' '/^[a-zA-Z0-9][^\$$#\/\\t=]*:([^=]|$$)/ {\ + split(\$$1,A,/ /);for(i in A)print A[i]\ + }' | grep -v '__\$$' | grep -v 'make\[1\]' | grep -v 'Makefile' | sort" +# 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 + +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 diff --git a/PKG-INFO b/PKG-INFO deleted file mode 100644 index ff329a6..0000000 --- a/PKG-INFO +++ /dev/null @@ -1,251 +0,0 @@ -Metadata-Version: 2.1 -Name: pendulum -Version: 2.1.2 -Summary: Python datetimes made easy -Home-page: https://pendulum.eustace.io -License: MIT -Keywords: datetime,date,time -Author: Sébastien Eustace -Author-email: sebastien@eustace.io -Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* -Classifier: License :: OSI Approved :: MIT License -Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.5 -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 -Requires-Dist: python-dateutil (>=2.6,<3.0) -Requires-Dist: pytzdata (>=2020.1) -Requires-Dist: typing (>=3.6,<4.0); python_version < "3.5" -Project-URL: Documentation, https://pendulum.eustace.io/docs -Project-URL: Repository, https://github.com/sdispater/pendulum -Description-Content-Type: text/x-rst - -Pendulum -######## - -.. image:: https://img.shields.io/pypi/v/pendulum.svg - :target: https://pypi.python.org/pypi/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://travis-ci.org/sdispater/pendulum.svg - :alt: Pendulum Build status - :target: https://travis-ci.org/sdispater/pendulum - -Python datetimes made easy. - -Supports Python **2.7** and **3.4+**. - - -.. code-block:: python - - >>> import pendulum - - >>> now_in_paris = pendulum.now('Europe/Paris') - >>> now_in_paris - '2016-07-04T00:49:58.502116+02:00' - - # Seamless timezone switching - >>> now_in_paris.in_timezone('UTC') - '2016-07-03T22:49:58.502116+00:00' - - >>> tomorrow = pendulum.now().add(days=1) - >>> last_week = pendulum.now().subtract(weeks=1) - - >>> past = pendulum.now().subtract(minutes=2) - >>> past.diff_for_humans() - >>> '2 minutes ago' - - >>> delta = past - last_week - >>> delta.hours - 23 - >>> delta.in_words(locale='en') - '6 days 23 hours 58 minutes' - - # Proper handling of datetime normalization - >>> pendulum.datetime(2013, 3, 31, 2, 30, tz='Europe/Paris') - '2013-03-31T03:30:00+02:00' # 2:30 does not exist (Skipped time) - - # Proper handling of dst transitions - >>> just_before = pendulum.datetime(2013, 3, 31, 1, 59, 59, 999999, tz='Europe/Paris') - '2013-03-31T01:59:59.999999+01:00' - >>> just_before.add(microseconds=1) - '2013-03-31T03:00:00+02:00' - - -Why Pendulum? -============= - -Native ``datetime`` instances are enough for basic cases but when you face more complex use-cases -they often show limitations and are not so intuitive to work with. -``Pendulum`` provides a cleaner and more easy to use API while still relying on the standard library. -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 -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 -and by default in ``UTC`` for ease of use. - -Pendulum also improves the standard ``timedelta`` class by providing more intuitive methods and properties. - - -Why not Arrow? -============== - -Arrow is the most popular datetime library for Python right now, however its behavior -and API can be erratic and unpredictable. The ``get()`` method can receive pretty much anything -and it will try its best to return something while silently failing to handle some cases: - -.. code-block:: python - - arrow.get('2016-1-17') - # - - pendulum.parse('2016-1-17') - # - - arrow.get('20160413') - # - - pendulum.parse('20160413') - # - - arrow.get('2016-W07-5') - # - - pendulum.parse('2016-W07-5') - # - - # Working with DST - just_before = arrow.Arrow(2013, 3, 31, 1, 59, 59, 999999, 'Europe/Paris') - just_after = just_before.replace(microseconds=1) - '2013-03-31T02:00:00+02:00' - # Should be 2013-03-31T03:00:00+02:00 - - (just_after.to('utc') - just_before.to('utc')).total_seconds() - -3599.999999 - # Should be 1e-06 - - just_before = pendulum.datetime(2013, 3, 31, 1, 59, 59, 999999, 'Europe/Paris') - just_after = just_before.add(microseconds=1) - '2013-03-31T03:00:00+02:00' - - (just_after.in_timezone('utc') - just_before.in_timezone('utc')).total_seconds() - 1e-06 - -Those are a few examples showing that Arrow cannot always be trusted to have a consistent -behavior with the data you are passing to it. - - -Limitations -=========== - -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 ``type()`` function to determine the type of the object by default. To work around it you can register a new adapter: - -.. code-block:: python - - from pendulum import DateTime - from sqlite3 import register_adapter - - register_adapter(DateTime, lambda val: val.isoformat(' ')) - -* ``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: - -.. code-block:: python - - import MySQLdb.converters - import pymysql.converters - - from pendulum import DateTime - - MySQLdb.converters.conversions[DateTime] = MySQLdb.converters.DateTime2literal - pymysql.converters.conversions[DateTime] = pymysql.converters.escape_datetime - -* ``django`` will use the ``isoformat()`` method to store datetimes in the database. However since ``pendulum`` is always timezone aware the offset information will always be returned by ``isoformat()`` raising an error, at least for MySQL databases. To work around it you can either create your own ``DateTimeField`` or use the previous workaround for ``MySQLdb``: - -.. code-block:: python - - from django.db.models import DateTimeField as BaseDateTimeField - from pendulum import DateTime - - - class DateTimeField(BaseDateTimeField): - - def value_to_string(self, obj): - val = self.value_from_object(obj) - - if isinstance(value, DateTime): - return value.to_datetime_string() - - return '' if val is None else val.isoformat() - - -Resources -========= - -* `Official Website `_ -* `Documentation `_ -* `Issue Tracker `_ - - -Contributing -============ - -Contributions are welcome, especially with localization. - -Getting started ---------------- - -To work on the Pendulum codebase, you'll want to clone the project locally -and install the required depedendencies via `poetry `_. - -.. code-block:: bash - - $ git clone git@github.com:sdispater/pendulum.git - $ poetry install - -Localization ------------- - -If you want to help with localization, there are two different cases: the locale already exists -or not. - -If the locale does not exist you will need to create it by using the ``clock`` utility: - -.. code-block:: bash - - ./clock locale create - -It will generate a directory in ``pendulum/locales`` named after your locale, with the following -structure: - -.. code-block:: text - - / - - custom.py - - locale.py - -The ``locale.py`` file must not be modified. It contains the translations provided by -the CLDR database. - -The ``custom.py`` file is the one you want to modify. It contains the data needed -by Pendulum that are not provided by the CLDR database. You can take the `en `_ -data as a reference to see which data is needed. - -You should also add tests for the created or modified locale. - diff --git a/README.rst b/README.rst index d65fb47..78437e2 100644 --- a/README.rst +++ b/README.rst @@ -1,224 +1,176 @@ -Pendulum -######## - -.. image:: https://img.shields.io/pypi/v/pendulum.svg - :target: https://pypi.python.org/pypi/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://travis-ci.org/sdispater/pendulum.svg - :alt: Pendulum Build status - :target: https://travis-ci.org/sdispater/pendulum - -Python datetimes made easy. - -Supports Python **2.7** and **3.4+**. - - -.. code-block:: python - - >>> import pendulum - - >>> now_in_paris = pendulum.now('Europe/Paris') - >>> now_in_paris - '2016-07-04T00:49:58.502116+02:00' - - # Seamless timezone switching - >>> now_in_paris.in_timezone('UTC') - '2016-07-03T22:49:58.502116+00:00' - - >>> tomorrow = pendulum.now().add(days=1) - >>> last_week = pendulum.now().subtract(weeks=1) - - >>> past = pendulum.now().subtract(minutes=2) - >>> past.diff_for_humans() - >>> '2 minutes ago' - - >>> delta = past - last_week - >>> delta.hours - 23 - >>> delta.in_words(locale='en') - '6 days 23 hours 58 minutes' - - # Proper handling of datetime normalization - >>> pendulum.datetime(2013, 3, 31, 2, 30, tz='Europe/Paris') - '2013-03-31T03:30:00+02:00' # 2:30 does not exist (Skipped time) - - # Proper handling of dst transitions - >>> just_before = pendulum.datetime(2013, 3, 31, 1, 59, 59, 999999, tz='Europe/Paris') - '2013-03-31T01:59:59.999999+01:00' - >>> just_before.add(microseconds=1) - '2013-03-31T03:00:00+02:00' - - -Why Pendulum? -============= - -Native ``datetime`` instances are enough for basic cases but when you face more complex use-cases -they often show limitations and are not so intuitive to work with. -``Pendulum`` provides a cleaner and more easy to use API while still relying on the standard library. -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 -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 -and by default in ``UTC`` for ease of use. - -Pendulum also improves the standard ``timedelta`` class by providing more intuitive methods and properties. - - -Why not Arrow? -============== - -Arrow is the most popular datetime library for Python right now, however its behavior -and API can be erratic and unpredictable. The ``get()`` method can receive pretty much anything -and it will try its best to return something while silently failing to handle some cases: - -.. code-block:: python - - arrow.get('2016-1-17') - # - - pendulum.parse('2016-1-17') - # - - arrow.get('20160413') - # - - pendulum.parse('20160413') - # - - arrow.get('2016-W07-5') - # - - pendulum.parse('2016-W07-5') - # - - # Working with DST - just_before = arrow.Arrow(2013, 3, 31, 1, 59, 59, 999999, 'Europe/Paris') - just_after = just_before.replace(microseconds=1) - '2013-03-31T02:00:00+02:00' - # Should be 2013-03-31T03:00:00+02:00 - - (just_after.to('utc') - just_before.to('utc')).total_seconds() - -3599.999999 - # Should be 1e-06 - - just_before = pendulum.datetime(2013, 3, 31, 1, 59, 59, 999999, 'Europe/Paris') - just_after = just_before.add(microseconds=1) - '2013-03-31T03:00:00+02:00' - - (just_after.in_timezone('utc') - just_before.in_timezone('utc')).total_seconds() - 1e-06 - -Those are a few examples showing that Arrow cannot always be trusted to have a consistent -behavior with the data you are passing to it. - - -Limitations -=========== - -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 ``type()`` function to determine the type of the object by default. To work around it you can register a new adapter: - -.. code-block:: python - - from pendulum import DateTime - from sqlite3 import register_adapter - - register_adapter(DateTime, lambda val: val.isoformat(' ')) - -* ``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: - -.. code-block:: python - - import MySQLdb.converters - import pymysql.converters - - from pendulum import DateTime - - MySQLdb.converters.conversions[DateTime] = MySQLdb.converters.DateTime2literal - pymysql.converters.conversions[DateTime] = pymysql.converters.escape_datetime - -* ``django`` will use the ``isoformat()`` method to store datetimes in the database. However since ``pendulum`` is always timezone aware the offset information will always be returned by ``isoformat()`` raising an error, at least for MySQL databases. To work around it you can either create your own ``DateTimeField`` or use the previous workaround for ``MySQLdb``: - -.. code-block:: python - - from django.db.models import DateTimeField as BaseDateTimeField - from pendulum import DateTime - - - class DateTimeField(BaseDateTimeField): - - def value_to_string(self, obj): - val = self.value_from_object(obj) - - if isinstance(value, DateTime): - return value.to_datetime_string() - - return '' if val is None else val.isoformat() - - -Resources -========= - -* `Official Website `_ -* `Documentation `_ -* `Issue Tracker `_ - - -Contributing -============ - -Contributions are welcome, especially with localization. - -Getting started ---------------- - -To work on the Pendulum codebase, you'll want to clone the project locally -and install the required depedendencies via `poetry `_. - -.. code-block:: bash - - $ git clone git@github.com:sdispater/pendulum.git - $ poetry install - -Localization ------------- - -If you want to help with localization, there are two different cases: the locale already exists -or not. - -If the locale does not exist you will need to create it by using the ``clock`` utility: - -.. code-block:: bash - - ./clock locale create - -It will generate a directory in ``pendulum/locales`` named after your locale, with the following -structure: - -.. code-block:: text - - / - - custom.py - - locale.py - -The ``locale.py`` file must not be modified. It contains the translations provided by -the CLDR database. - -The ``custom.py`` file is the one you want to modify. It contains the data needed -by Pendulum that are not provided by the CLDR database. You can take the `en `_ -data as a reference to see which data is needed. - -You should also add tests for the created or modified locale. +Pendulum +######## + +.. image:: https://img.shields.io/pypi/v/pendulum.svg + :target: https://pypi.python.org/pypi/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 + + +Python datetimes made easy. + +Supports Python **2.7** and **3.4+**. + + +.. code-block:: python + + >>> import pendulum + + >>> now_in_paris = pendulum.now('Europe/Paris') + >>> now_in_paris + '2016-07-04T00:49:58.502116+02:00' + + # Seamless timezone switching + >>> now_in_paris.in_timezone('UTC') + '2016-07-03T22:49:58.502116+00:00' + + >>> tomorrow = pendulum.now().add(days=1) + >>> last_week = pendulum.now().subtract(weeks=1) + + >>> past = pendulum.now().subtract(minutes=2) + >>> past.diff_for_humans() + '2 minutes ago' + + >>> delta = past - last_week + >>> delta.hours + 23 + >>> delta.in_words(locale='en') + '6 days 23 hours 58 minutes' + + # Proper handling of datetime normalization + >>> pendulum.datetime(2013, 3, 31, 2, 30, tz='Europe/Paris') + '2013-03-31T03:30:00+02:00' # 2:30 does not exist (Skipped time) + + # Proper handling of dst transitions + >>> just_before = pendulum.datetime(2013, 3, 31, 1, 59, 59, 999999, tz='Europe/Paris') + '2013-03-31T01:59:59.999999+01:00' + >>> just_before.add(microseconds=1) + '2013-03-31T03:00:00+02:00' + + +Why Pendulum? +============= + +Native ``datetime`` instances are enough for basic cases but when you face more complex use-cases +they often show limitations and are not so intuitive to work with. +``Pendulum`` provides a cleaner and more easy to use API while still relying on the standard library. +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 +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 +and by default in ``UTC`` for ease of use. + +Pendulum also improves the standard ``timedelta`` class by providing more intuitive methods and properties. + +Limitations +=========== + +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 ``type()`` function to determine the type of the object by default. To work around it you can register a new adapter: + +.. code-block:: python + + from pendulum import DateTime + from sqlite3 import register_adapter + + register_adapter(DateTime, lambda val: val.isoformat(' ')) + +* ``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: + +.. code-block:: python + + import MySQLdb.converters + import pymysql.converters + + from pendulum import DateTime + + MySQLdb.converters.conversions[DateTime] = MySQLdb.converters.DateTime2literal + pymysql.converters.conversions[DateTime] = pymysql.converters.escape_datetime + +* ``django`` will use the ``isoformat()`` method to store datetimes in the database. However since ``pendulum`` is always timezone aware the offset information will always be returned by ``isoformat()`` raising an error, at least for MySQL databases. To work around it you can either create your own ``DateTimeField`` or use the previous workaround for ``MySQLdb``: + +.. code-block:: python + + from django.db.models import DateTimeField as BaseDateTimeField + from pendulum import DateTime + + + class DateTimeField(BaseDateTimeField): + + def value_to_string(self, obj): + val = self.value_from_object(obj) + + if isinstance(value, DateTime): + return value.to_datetime_string() + + return '' if val is None else val.isoformat() + + +Resources +========= + +* `Official Website `_ +* `Documentation `_ +* `Issue Tracker `_ + + +Contributing +============ + +Contributions are welcome, especially with localization. + +Getting started +--------------- + +To work on the Pendulum codebase, you'll want to clone the project locally +and install the required dependencies via `poetry `_. + +.. code-block:: bash + + $ git clone git@github.com:sdispater/pendulum.git + $ poetry install + +Localization +------------ + +If you want to help with localization, there are two different cases: the locale already exists +or not. + +If the locale does not exist you will need to create it by using the ``clock`` utility: + +.. code-block:: bash + + ./clock locale create + +It will generate a directory in ``pendulum/locales`` named after your locale, with the following +structure: + +.. code-block:: text + + / + - custom.py + - locale.py + +The ``locale.py`` file must not be modified. It contains the translations provided by +the CLDR database. + +The ``custom.py`` file is the one you want to modify. It contains the data needed +by Pendulum that are not provided by the CLDR database. You can take the `en `_ +data as a reference to see which data is needed. + +You should also add tests for the created or modified locale. diff --git a/build-wheels.sh b/build-wheels.sh new file mode 100755 index 0000000..1c8f1cb --- /dev/null +++ b/build-wheels.sh @@ -0,0 +1,27 @@ +#!/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 index 07d277e..95e63e1 100644 --- a/build.py +++ b/build.py @@ -1,87 +1,33 @@ -import os -import shutil -import sys - -from distutils.command.build_ext import build_ext -from distutils.core import Distribution -from distutils.core import Extension -from distutils.errors import CCompilerError -from distutils.errors import DistutilsExecError -from distutils.errors import DistutilsPlatformError - - -# C Extensions -with_extensions = os.getenv("PENDULUM_EXTENSIONS", None) - -if with_extensions == "1" or with_extensions is None: - with_extensions = True - -if with_extensions == "0" or hasattr(sys, "pypy_version_info"): - with_extensions = False - -extensions = [] -if with_extensions: - extensions = [ - Extension("pendulum._extensions._helpers", ["pendulum/_extensions/_helpers.c"]), - Extension("pendulum.parsing._iso8601", ["pendulum/parsing/_iso8601.c"]), - ] - - -class BuildFailed(Exception): - - pass - - -class ExtBuilder(build_ext): - # This class allows C extension building to fail. - - built_extensions = [] - - def run(self): - try: - build_ext.run(self) - except (DistutilsPlatformError, FileNotFoundError): - print( - " Unable to build the C extensions, " - "Pendulum will use the pure python code instead." - ) - - def build_extension(self, ext): - try: - build_ext.build_extension(self, ext) - except (CCompilerError, DistutilsExecError, DistutilsPlatformError, ValueError): - print( - ' Unable to build the "{}" C extension, ' - "Pendulum will use the pure python version of the extension.".format( - ext.name - ) - ) - - -def build(setup_kwargs): - """ - This function is mandatory in order to build the extensions. - """ - distribution = Distribution({"name": "pendulum", "ext_modules": extensions}) - distribution.package_dir = "pendulum" - - cmd = ExtBuilder(distribution) - cmd.ensure_finalized() - cmd.run() - - # Copy built extensions back to the project - for output in cmd.get_outputs(): - relative_extension = os.path.relpath(output, cmd.build_lib) - if not os.path.exists(output): - continue - - shutil.copyfile(output, relative_extension) - mode = os.stat(relative_extension).st_mode - mode |= (mode & 0o444) >> 2 - os.chmod(relative_extension, mode) - - return setup_kwargs - - -if __name__ == "__main__": - build({}) +import subprocess + +from pathlib import Path + + +def meson(*args): + subprocess.call(["meson"] + list(args)) + + +def _build(): + build_dir = Path(__file__).parent.joinpath("build") + build_dir.mkdir(parents=True, exist_ok=True) + + meson("setup", build_dir.as_posix()) + meson("compile", "-C", build_dir.as_posix()) + meson("install", "-C", build_dir.as_posix()) + + +def build(setup_kwargs): + """ + This function is mandatory in order to build the extensions. + """ + try: + _build() + except Exception: + print( + " Unable to build C extensions, " + "Pendulum will use the pure python version of the extensions." + ) + + +if __name__ == "__main__": + build({}) diff --git a/clock b/clock new file mode 100755 index 0000000..17f35a3 --- /dev/null +++ b/clock @@ -0,0 +1,274 @@ +#!/usr/bin/env python + +from __future__ import annotations + +import glob +import json +import os + +from babel.core import get_global +from babel.dates import PATTERN_CHARS +from babel.dates import tokenize_pattern +from babel.localedata import LocaleDataDict +from babel.localedata import load +from babel.localedata import normalize_locale +from babel.plural import PluralRule +from babel.plural import _binary_compiler +from babel.plural import _GettextCompiler +from babel.plural import _unary_compiler +from babel.plural import compile_zero +from cleo.application import Application +from cleo.commands.command import Command +from cleo.helpers import argument + +from pendulum import __version__ + + +class _LambdaCompiler(_GettextCompiler): + """Compiles the expression to lambda function.""" + + compile_v = compile_zero + compile_w = compile_zero + compile_f = compile_zero + compile_t = compile_zero + compile_and = _binary_compiler("(%s and %s)") + compile_or = _binary_compiler("(%s or %s)") + compile_not = _unary_compiler("(not %s)") + compile_mod = _binary_compiler("(%s %% %s)") + + def compile_relation(self, method, expr, range_list): + code = _GettextCompiler.compile_relation(self, method, expr, range_list) + code = code.replace("&&", "and") + code = code.replace("||", "or") + if method == "in": + expr = self.compile(expr) + code = f"({expr} == {expr} and {code})" + return code + + +class LocaleCreate(Command): + + name = "locale create" + description = "Creates locale translations." + + arguments = [argument("locales", "Locales to dump.", optional=False, multiple=True)] + + TEMPLATE = """from .custom import translations as custom_translations + + +\"\"\" +{locale} locale file. + +It has been generated automatically and must not be modified directly. +\"\"\" + + +locale = {{ + 'plural': {plural}, + 'ordinal': {ordinal}, + 'translations': {translations}, + 'custom': custom_translations +}} +""" + + CUSTOM_TEMPLATE = """\"\"\" +{locale} custom locale file. +\"\"\" + +translations = {{}} +""" + + LOCALE_DIR = os.path.join("pendulum", "locales") + + def handle(self): + locales = self.argument("locales") + if not locales: + return + + for locale in locales: + data = {} + parts = locale.split("-") + if len(parts) > 1: + parts[1] = parts[1].upper() + + normalized = normalize_locale(locale.replace("-", "_")) + if not normalized: + self.line(f"Locale [{locale}] does not exist.") + continue + + self.line(f"Generating {locale} locale.") + + content = LocaleDataDict(load(normalized)) + + # Pluralization rule + rule = content["plural_form"] + plural = self.plural_rule_to_lambda(rule) + + # Ordinal rule + rule = content["ordinal_form"] + ordinal = self.plural_rule_to_lambda(rule) + + # Getting days names + days = content["days"]["format"] + data["days"] = {} + for fmt, names in days.items(): + data["days"][fmt] = {} + for value, name in names.items(): + data["days"][fmt][(value + 1) % 7] = name + + # Getting months names + months = content["months"]["format"] + data["months"] = months + + # Units + patterns = content["unit_patterns"] + units = [ + "year", + "month", + "week", + "day", + "hour", + "minute", + "second", + "microsecond", + ] + data["units"] = {} + for unit in units: + pattern = patterns[f"duration-{unit}"]["long"] + if "per" in pattern: + del pattern["per"] + + data["units"][unit] = pattern + + # Relative + data["relative"] = {} + for key in content["date_fields"]: + if key not in [ + "year", + "month", + "week", + "day", + "hour", + "minute", + "second", + ]: + continue + + data["relative"][key] = content["date_fields"][key] + + # Day periods + data["day_periods"] = content["day_periods"]["format"]["wide"] + + result = self.TEMPLATE.format( + locale=locale, + plural=plural, + ordinal=ordinal, + translations=self.format_dict(data, tab=2), + ) + + dest_dir = os.path.join(self.LOCALE_DIR, locale.replace("-", "_")) + if not os.path.exists(dest_dir): + os.mkdir(dest_dir) + + init = os.path.join(dest_dir, "__init__.py") + main = os.path.join(dest_dir, "locale.py") + custom = os.path.join(dest_dir, "custom.py") + + if not os.path.exists(init): + with open(init, "w"): + os.utime(init) + + with open(main, "w") as fw: + fw.write(result) + + if not os.path.exists(custom): + with open(custom, "w") as fw: + fw.write(self.CUSTOM_TEMPLATE.format(locale=locale)) + + def format_dict(self, d, tab=1): + s = ["{\n"] + for k, v in d.items(): + if isinstance(v, (dict, LocaleDataDict)): + v = self.format_dict(v, tab + 1) + else: + v = repr(v) + + s.append(f"{' ' * tab}{k!r}: {v},\n") + s.append(f'{" " * (tab - 1)}}}') + + return "".join(s) + + def plural_rule_to_lambda(self, rule): + to_py = _LambdaCompiler().compile + result = ["lambda n: "] + for tag, ast in PluralRule.parse(rule).abstract: + result.append(f"'{tag}' if {to_py(ast)} else ") + result.append("'other'") + return "".join(result) + + def convert_ldml_format(self, fmt): + result = [] + + for tok_type, tok_value in tokenize_pattern(fmt): + if tok_type == "chars": + result.append(tok_value.replace("%", "%%")) + elif tok_type == "field": + fieldchar, fieldnum = tok_value + limit = PATTERN_CHARS[fieldchar] + if limit and fieldnum not in limit: + raise ValueError( + f"Invalid length for field: {(fieldchar * fieldnum)!r}" + ) + result.append( + self.TOKENS_MAP.get(fieldchar * fieldnum, fieldchar * fieldnum) + ) + else: + raise NotImplementedError(f"Unknown token type: {tok_type}") + + return "".join(result) + + +class LocaleRecreate(Command): + + name = "locale recreate" + description = "Recreate existing locales." + + def handle(self): + # Listing locales + + locales_dir = os.path.join("pendulum", "locales") + 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)]) + + +class WindowsTzDump(Command): + + name = "windows dump-timezones" + description = "Dumps the mapping of Windows timezones to IANA timezones." + + MAPPING_DIR = os.path.join("pendulum", "tz", "data") + + def handle(self): + raw_tznames = get_global("windows_zone_mapping") + sorted_names = sorted(raw_tznames.keys()) + + tznames = {} + for name in sorted_names: + tznames[name] = raw_tznames[name] + + mapping = json.dumps(tznames, indent=4).replace('"', "'") + + with open(os.path.join(self.MAPPING_DIR, "windows.py"), "w") as f: + f.write(f"windows_timezones = {mapping}\n") + + +app = Application("clock", __version__) +app.add(LocaleCreate()) +app.add(LocaleRecreate()) +app.add(WindowsTzDump()) + + +if __name__ == "__main__": + app.run() diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..3cbba28 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,7 @@ +comment: false + +coverage: + status: + patch: + default: + enabled: false diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..9b3dd7c --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,2 @@ +html: + mkdocs build diff --git a/docs/docs/addition_subtraction.md b/docs/docs/addition_subtraction.md new file mode 100644 index 0000000..686f67f --- /dev/null +++ b/docs/docs/addition_subtraction.md @@ -0,0 +1,87 @@ +# Addition and Subtraction + +To easily add and subtract time, you can use the `add()` and `subtract()` +methods. +Each method returns a new `DateTime` instance. + +```python +>>> import pendulum + +>>> dt = pendulum.datetime(2012, 1, 31) + +>>> dt.to_datetime_string() +'2012-01-31 00:00:00' + +>>> dt = dt.add(years=5) +'2017-01-31 00:00:00' +>>> dt = dt.add(years=1) +'2018-01-31 00:00:00' +>>> dt = dt.subtract(years=1) +'2017-01-31 00:00:00' +>>> dt = dt.subtract(years=5) +'2012-01-31 00:00:00' + +>>> dt = dt.add(months=60) +'2017-01-31 00:00:00' +>>> dt = dt.add(months=1) +'2017-02-28 00:00:00' +>>> dt = dt.subtract(months=1) +'2017-01-28 00:00:00' +>>> dt = dt.subtract(months=60) +'2012-01-28 00:00:00' + +>>> dt = dt.add(days=29) +'2012-02-26 00:00:00' +>>> dt = dt.add(days=1) +'2012-02-27 00:00:00' +>>> dt = dt.subtract(days=1) +'2012-02-26 00:00:00' +>>> dt = dt.subtract(days=29) +'2012-01-28 00:00:00' + +>>> dt = dt.add(weeks=3) +'2012-02-18 00:00:00' +>>> dt = dt.add(weeks=1) +'2012-02-25 00:00:00' +>>> dt = dt.subtract(weeks=1) +'2012-02-18 00:00:00' +>>> dt = dt.subtract(weeks=3) +'2012-01-28 00:00:00' + +>>> dt = dt.add(hours=24) +'2012-01-29 00:00:00' +>>> dt = dt.add(hours=1) +'2012-02-25 01:00:00' +>>> dt = dt.subtract(hours=1) +'2012-02-29 00:00:00' +>>> dt = dt.subtract(hours=24) +'2012-01-28 00:00:00' + +>>> dt = dt.add(minutes=61) +'2012-01-28 01:01:00' +>>> dt = dt.add(minutes=1) +'2012-01-28 01:02:00' +>>> dt = dt.subtract(minutes=1) +'2012-01-28 01:01:00' +>>> dt = dt.subtract(minutes=24) +'2012-01-28 00:00:00' + +>>> dt = dt.add(seconds=61) +'2012-01-28 00:01:01' +>>> dt = dt.add(seconds=1) +'2012-01-28 00:01:02' +>>> dt = dt.subtract(seconds=1) +'2012-01-28 00:01:01' +>>> dt = dt.subtract(seconds=61) +'2012-01-28 00:00:00' + +>>> dt = dt.add(years=3, months=2, days=6, hours=12, minutes=31, seconds=43) +'2015-04-03 12:31:43' +>>> dt = dt.subtract(years=3, months=2, days=6, hours=12, minutes=31, seconds=43) +'2012-01-28 00:00:00' +``` + +!!!note + + Passing negative values to `add()` is also possible and will act exactly + like `subtract()` diff --git a/docs/docs/attributes_properties.md b/docs/docs/attributes_properties.md new file mode 100644 index 0000000..290891e --- /dev/null +++ b/docs/docs/attributes_properties.md @@ -0,0 +1,87 @@ +# Attributes and Properties + +Pendulum gives access to more attributes and properties than the default ``datetime`` class. + +```python +>>> import pendulum + +>>> dt = pendulum.parse('2012-09-05T23:26:11.123789') + +# These properties specifically return integers +>>> dt.year +2012 +>>> dt.month +9 +>>> dt.day +5 +>>> dt.hour +23 +>>> dt.minute +26 +>>> dt.second +11 +>>> dt.microsecond +123789 +>>> dt.day_of_week +3 +>>> dt.day_of_year +248 +>>> dt.week_of_month +1 +>>> dt.week_of_year +36 +>>> dt.days_in_month +30 +>>> dt.timestamp() +1346887571.123789 +>>> dt.float_timestamp +1346887571.123789 +>>> dt.int_timestamp +1346887571 + +>>> pendulum.datetime(1975, 5, 21).age +41 # calculated vs now in the same tz +>>> dt.quarter +3 + +# Returns an int of seconds difference from UTC (+/- sign included) +>>> pendulum.from_timestamp(0).offset +0 +>>> pendulum.from_timestamp(0, 'America/Toronto').offset +-18000 + +# Returns a float of hours difference from UTC (+/- sign included) +>>> pendulum.from_timestamp(0, 'America/Toronto').offset_hours +-5.0 +>>> pendulum.from_timestamp(0, 'Australia/Adelaide').offset_hours +9.5 + +# Gets the timezone instance +>>> pendulum.now().timezone +>>> pendulum.now().tz + +# Gets the timezone name +>>> pendulum.now().timezone_name + +# Indicates if daylight savings time is on +>>> dt = pendulum.datetime(2012, 1, 1, tz='America/Toronto') +>>> dt.is_dst() +False +>>> dt = pendulum.datetime(2012, 9, 1, tz='America/Toronto') +>>> dt.is_dst() +True + +# Indicates if the instance is in the same timezone as the local timezone +>>> pendulum.now().is_local() +True +>>> pendulum.now('Europe/London').is_local() +False + +# Indicates if the instance is in the UTC timezone +>>> pendulum.now().is_utc() +False +>>> pendulum.now('Europe/London').is_local() +False +>>> pendulum.now('UTC').is_utc() +True +``` diff --git a/docs/docs/comparison.md b/docs/docs/comparison.md new file mode 100644 index 0000000..6be8d0d --- /dev/null +++ b/docs/docs/comparison.md @@ -0,0 +1,77 @@ +# Comparison + +Simple comparison is offered up via the basic operators. +Remember that the comparison is done in the UTC timezone +so things aren't always as they seem. + +```python +>>> import pendulum + +>>> first = pendulum.datetime(2012, 9, 5, 23, 26, 11, 0, tz='America/Toronto') +>>> second = pendulum.datetime(2012, 9, 5, 20, 26, 11, 0, tz='America/Vancouver') + +>>> first.to_datetime_string() +'2012-09-05 23:26:11' +>>> first.timezone_name +'America/Toronto' +>>> second.to_datetime_string() +'2012-09-05 20:26:11' +>>> second.timezone_name +'America/Vancouver' + +>>> first == second +True +>>> first != second +False +>>> first > second +False +>>> first >= second +True +>>> first < second +False +>>> first <= second +True + +>>> first = first.on(2012, 1, 1).at(0, 0, 0) +>>> second = second.on(2012, 1, 1).at(0, 0, 0) +# tz is still America/Vancouver for second + +>>> first == second +False +>>> first != second +True +>>> first > second +False +>>> first >= second +False +>>> first < second +True +>>> first <= second +True +``` + +To handle the most used cases there are some simple helper functions. +For the methods that compare to `now()` (ex. `is_today()`) in some manner +the `now()` is created in the same timezone as the instance. + +```python +>>> import pendulum + +>>> dt = pendulum.now() + +>>> dt.is_past() +>>> dt.is_leap_year() + +>>> born = pendulum.datetime(1987, 4, 23) +>>> not_birthday = pendulum.datetime(2014, 9, 26) +>>> birthday = pendulum.datetime(2014, 4, 23) +>>> past_birthday = pendulum.now().subtract(years=50) + +>>> born.is_birthday(not_birthday) +False +>>> born.is_birthday(birthday) +True +>>> past_birthday.is_birthday() +# Compares to now by default +True +``` diff --git a/docs/docs/difference.md b/docs/docs/difference.md new file mode 100644 index 0000000..3a7f063 --- /dev/null +++ b/docs/docs/difference.md @@ -0,0 +1,115 @@ +# Difference + +The `diff()` method returns a [Period](#period) 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. + +The `diff()` method has a default first parameter which is the `DateTime` instance to compare to, +or `None` if you want to use `now()`. +The 2nd parameter is optional and indicates if you want the return value to be the absolute value +or a relative value that might have a `-` (negative) sign if the passed in date +is less than the current instance. +This will default to `True`, return the absolute value. + +```python +>>> import pendulum + +>>> dt_ottawa = pendulum.datetime(2000, 1, 1, tz='America/Toronto') +>>> dt_vancouver = pendulum.datetime(2000, 1, 1, tz='America/Vancouver') + +>>> dt_ottawa.diff(dt_vancouver).in_hours() +3 +>>> dt_ottawa.diff(dt_vancouver, False).in_hours() +3 +>>> dt_vancouver.diff(dt_ottawa, False).in_hours() +-3 + +>>> dt = pendulum.datetime(2012, 1, 31, 0) +>>> dt.diff(dt.add(months=1)).in_days() +29 +>>> dt.diff(dt.subtract(months=1), False).in_days() +-31 + +>>> dt = pendulum.datetime(2012, 4, 30, 0) +>>> dt.diff(dt.add(months=1)).in_days() +30 +>>> dt.diff(dt.add(weeks=1)).in_days() +7 + +>>> dt = pendulum.datetime(2012, 1, 1, 0) +>>> dt.diff(dt.add(seconds=59)).in_minutes() +0 +>>> dt.diff(dt.add(seconds=60)).in_minutes() +1 +>>> dt.diff(dt.add(seconds=119)).in_minutes() +1 +>>> dt.diff(dt.add(seconds=120)).in_minutes() +2 +``` + +Difference for Humans +--------------------- + +The `diff_for_humans()` method will add a phrase after the difference value relative +to the instance and the passed in instance. There are 4 possibilities: + +* When comparing a value in the past to default now: + * 1 hour ago + * 5 months ago + +* When comparing a value in the future to default now: + * 1 hour from now + * 5 months from now + +* When comparing a value in the past to another value: + * 1 hour before + * 5 months before + +* When comparing a value in the future to another value: + * 1 hour after + * 5 months after + +You may also pass `True` as a 2nd parameter to remove the modifiers `ago`, `from now`, etc. + +```python +>>> import pendulum + +# The most typical usage is for comments +# The instance is the date the comment was created +# and its being compared to default now() +>>> pendulum.now().subtract(days=1).diff_for_humans() +'1 day ago' + +>>> pendulum.now().diff_for_humans(pendulum.now().subtract(years=1)) +'1 year after' + +>>> dt = pendulum.datetime(2011, 8, 1) +>>> dt.diff_for_humans(dt.add(months=1)) +'1 month before' +>>> dt.diff_for_humans(dt.subtract(months=1)) +'1 month after' + +>>> pendulum.now().add(seconds=5).diff_for_humans() +'5 seconds from now' + +>>> pendulum.now().subtract(days=24).diff_for_humans() +'3 weeks ago' + +>>> pendulum.now().subtract(days=24).diff_for_humans(absolute=True) +'3 weeks' +``` + +You can also change the locale of the string either globally by using `pendulum.set_locale('fr')` +before the `diff_for_humans()` call or specifically for the call by passing the `locale` keyword +argument. See the [Localization](#localization) section for more detail. + +```python +>>> import pendulum + +>>> pendulum.set_locale('de') +>>> pendulum.now().add(years=1).diff_for_humans() +'in 1 Jahr' +>>> pendulum.now().add(years=1).diff_for_humans(locale='fr') +'dans 1 an' +``` diff --git a/docs/docs/duration.md b/docs/docs/duration.md new file mode 100644 index 0000000..0801d9e --- /dev/null +++ b/docs/docs/duration.md @@ -0,0 +1,177 @@ +# Duration + +The `Duration` class is inherited from the native `timedelta` class. +It has many improvements over the base class. + +!!!note + + Even though, it inherits from the `timedelta` class, its behavior is slightly different. + The more important to notice is that the native normalization does not happen, this is so that + it feels more intuitive. + + ```python + >>> import pendulum + >>> from datetime import datetime + + >>> d1 = datetime(2012, 1, 1, 1, 2, 3, tzinfo=pytz.UTC) + >>> d2 = datetime(2011, 12, 31, 22, 2, 3, tzinfo=pytz.UTC) + >>> delta = d2 - d1 + >>> delta.days + -1 + >>> delta.seconds + 75600 + + >>> d1 = pendulum.datetime(2012, 1, 1, 1, 2, 3) + >>> d2 = pendulum.datetime(2011, 12, 31, 22, 2, 3) + >>> delta = d2 - d1 + >>> delta.days + 0 + >>> delta.hours + -3 + ``` + +## Instantiation + +To create a `Duration` instance, you can use the `duration()` helper: + +```python +>>> import pendulum + +>>> it = pendulum.duration(days=1177, seconds=7284, microseconds=1234) +``` + +!!!note + + Unlike the native `timedelta` class, durations support specifying + years and months. + + ```python + >>> import pendulum + + >>> it = pendulum.duration(years=2, months=3) + ``` + + However, to maintain compatibility, native methods and properties will + make approximations: + + ```python + >>> it.days + 820 + + >>> it.total_seconds() + 70848000.0 + ``` + +## Properties and Duration Methods + +The `Duration` class brings more properties than the default `days`, `seconds` and +`microseconds`. + +```python +>>> import pendulum + +>>> it = pendulum.duration( +... years=2, months=3, +... days=1177, seconds=7284, microseconds=1234 +... ) + +>>> it.years +2 +>>> it.months +3 + +# Weeks are based on the total of days +# It does not take into account years and months +>>> it.weeks +168 + +# Days, just like in timedelta, represents the total of days +# in the duration. If years and/or months are specified +# it will use an approximation +>>> it.days +1997 + +# If you want the remaining days not included in full weeks +>>> it.remaining_days +1 + +>>> # The remaining number in each unit +>>> it.hours +2 +>>> it.minutes +1 + +# Seconds are, like days, a special case and the default +# property will return the whole value of remaining +# seconds just like the timedelta class for compatibility +>>> it.seconds +7284 + +# If you want the number of seconds not included +# in hours and minutes +>>> it.remaining_seconds +24 + +>>> it.microseconds +1234 +``` + +If you want to get the duration in each supported unit +you can use the appropriate methods. + +```python +# Each method returns a float like the native +# total_seconds() method +>>> it.total_weeks() +168.15490079569113 + +>>> it.total_days() +1177.0843055698379 + +>>> it.total_hours() +28250.02333367611 + +>>> it.total_minutes() +1695001.4000205665 + +>>> it.total_seconds() +101700084.001234 +``` + +Similarly, the `in_xxx()` methods return the total duration in each +supported unit as a truncated integer. + +```python +>>> it.in_weeks() +168 + +>>> it.in_days() +1997 + +>>> it.in_hours() +28250 + +>>> it.in_minutes() +1695001 + +>>> it.in_seconds() +101700084 +``` + +It also has a handy `in_words()` method, which determines the duration representation when printed. + +```python +>>> import pendulum + +>>> pendulum.set_locale('fr') + +>>> it = pendulum.duration(days=1177, seconds=7284, microseconds=1234) +>>> it.in_words() +'168 semaines 1 jour 2 heures 1 minute 24 secondes' + +>>> print(it) +'168 semaines 1 jour 2 heures 1 minute 24 secondes' + +>>> it.in_words(locale='de') +'168 Wochen 1 Tag 2 Stunden 1 Minute 24 Sekunden' +``` diff --git a/docs/docs/fluent_helpers.md b/docs/docs/fluent_helpers.md new file mode 100644 index 0000000..2ace491 --- /dev/null +++ b/docs/docs/fluent_helpers.md @@ -0,0 +1,67 @@ +# Fluent helpers + +Pendulum provides helpers that return a new instance with some attributes +modified compared to the original instance. +However, none of these helpers, with the exception of explicitly setting the +timezone, will change the timezone of the instance. Specifically, +setting the timestamp will not set the corresponding timezone to UTC. + +```python +>>> import pendulum + +>>> dt = pendulum.now() + +>>> dt.set(year=1975, month=5, day=21).to_datetime_string() +'1975-05-21 13:45:18' + +>>> dt.set(hour=22, minute=32, second=5).to_datetime_string() +'2016-11-16 22:32:05' +``` + +You can also use the `on()` and `at()` methods to change the date and the time +respectively + +```python +>>> dt.on(1975, 5, 21).at(22, 32, 5).to_datetime_string() +'1975-05-21 22:32:05' + +>>> dt.at(10).to_datetime_string() +'2016-11-16 10:00:00' + +>>> dt.at(10, 30).to_datetime_string() +'2016-11-16 10:30:00' +``` + +You can also modify the timezone. + +```python +>>> dt.set(tz='Europe/London') +``` + +Setting the timezone just modifies the timezone information without +making any conversion, while `in_timezone()` (or `in_tz()`) +converts the time in the appropriate timezone. + +```python +>>> import pendulum + +>>> dt = pendulum.datetime(2013, 3, 31, 2, 30) +>>> print(dt) +'2013-03-31T02:30:00+00:00' + +>>> dt = dt.set(tz='Europe/Paris') +>>> print(dt) +'2013-03-31T03:30:00+02:00' + +>>> dt = dt.in_tz('Europe/Paris') +>>> print(dt) +'2013-03-31T04:30:00+02:00' + +>>> dt = dt.set(tz='Europe/Paris').set(tz='UTC') +>>> print(dt) +'2013-03-31T03:30:00+00:00' + +>>> dt = dt.in_tz('Europe/Paris').in_tz('UTC') +>>> print(dt) +'2013-03-31T02:30:00+00:00' +``` diff --git a/docs/docs/index.md b/docs/docs/index.md new file mode 100644 index 0000000..daca205 --- /dev/null +++ b/docs/docs/index.md @@ -0,0 +1,17 @@ +{!docs/installation.md!} +{!docs/introduction.md!} +{!docs/instantiation.md!} +{!docs/parsing.md!} +{!docs/localization.md!} +{!docs/attributes_properties.md!} +{!docs/fluent_helpers.md!} +{!docs/string_formatting.md!} +{!docs/comparison.md!} +{!docs/addition_subtraction.md!} +{!docs/difference.md!} +{!docs/modifiers.md!} +{!docs/timezones.md!} +{!docs/duration.md!} +{!docs/period.md!} +{!docs/testing.md!} +{!docs/limitations.md!} diff --git a/docs/docs/installation.md b/docs/docs/installation.md new file mode 100644 index 0000000..bfa9641 --- /dev/null +++ b/docs/docs/installation.md @@ -0,0 +1,13 @@ +# Installation + +Installing `pendulum` is quite simple: + +```bash +$ pip install pendulum +``` + +or, if you are using [poetry](https://python-poetry.org): + +```bash +$ poetry add pendulum +``` diff --git a/docs/docs/instantiation.md b/docs/docs/instantiation.md new file mode 100644 index 0000000..a49c6a2 --- /dev/null +++ b/docs/docs/instantiation.md @@ -0,0 +1,144 @@ +# Instantiation + +There are several different methods available to create a new `DateTime` instance. + +First there is the main `datetime()` helper. + +```python +>>> import pendulum + +>>> dt = pendulum.datetime(2015, 2, 5) +>>> isinstance(dt, datetime) +True +>>> dt.timezone.name +'UTC' +``` + +`datetime()` sets the time to `00:00:00` if it's not specified, +and the timezone (the `tz` keyword argument) to `UTC`. +Otherwise it can be a `Timezone` instance or simply a string timezone value. + +```python +>>> import pendulum + +>>> pendulum.datetime(2015, 2, 5, tz='Europe/Paris') +>>> tz = pendulum.timezone('Europe/Paris') +>>> pendulum.datetime(2015, 2, 5, tz=tz) +``` + +!!!note + + Supported strings for timezones are the one provided + by the [IANA time zone database](https://www.iana.org/time-zones). + + The special `local` string is also supported and will return your current timezone. + +!!!warning + + The `tz` argument is keyword-only, unlike in version `1.x` + +The `local()` helper is similar to `datetime()` but automatically sets the +timezone to the local timezone. + +```python +>>> import pendulum + +>>> dt = pendulum.local(2015, 2, 5) +>>> print(dt.timezone.name) +'America/Toronto' +``` + +!!!note + + `local()` is just an alias for `datetime(..., tz='local')`. + +There is also the `now()` method. + +```python +>>> import pendulum + +>>> now = pendulum.now() + +>>> now_in_london_tz = pendulum.now('Europe/London') +>>> now_in_london_tz.timezone_name +'Europe/London' +``` + +To accompany `now()`, a few other static instantiation helpers exist to create known instances. +The only thing to really notice here is that `today()`, `tomorrow()` and `yesterday()`, +besides behaving as expected, all accept a timezone parameter +and each has their time value set to `00:00:00`. + +```python +>>> now = pendulum.now() +>>> print(now) +'2016-06-28T16:51:45.978473-05:00' + +>>> today = pendulum.today() +>>> print(today) +'2016-06-28T00:00:00-05:00' + +>>> tomorrow = pendulum.tomorrow('Europe/London') +>>> print(tomorrow) +'2016-06-29T00:00:00+01:00' + +>>> yesterday = pendulum.yesterday() +>>> print(yesterday) +'2016-06-27T00:00:00-05:00' +``` + +Pendulum enforces timezone aware datetimes, and using them is the preferred and recommended way +of using the library. However, if you really need a **naive** `DateTime` object, the `naive()` helper +is there for you. + +```python +>>> import pendulum + +>>> naive = pendulum.naive(2015, 2, 5) +>>> naive.timezone +None +``` + +The next helper, `from_format()`, is similar to the native `datetime.strptime()` function +but uses custom tokens to create a `DateTime` instance. + +```python +>>> dt = pendulum.from_format('1975-05-21 22', 'YYYY-MM-DD HH') +>>> print(dt) +'1975-05-21T22:00:00+00:00' +``` + +!!!note + + To see all the available tokens, you can check the [Formatter](#formatter) section. + +It also accepts a `tz` keyword argument to specify the timezone: + +```python +>>> dt = pendulum.from_format('1975-05-21 22', 'YYYY-MM-DD HH', tz='Europe/London') +'1975-05-21T22:00:00+01:00' +``` + +The final helper is for working with unix timestamps. +`from_timestamp()` will create a `DateTime` instance equal to the given timestamp +and will set the timezone as well or default it to `UTC`. + +```python +>>> dt = pendulum.from_timestamp(-1) +>>> print(dt) +'1969-12-31T23:59:59+00:00' + +>>> dt = pendulum.from_timestamp(-1, tz='Europe/London') +>>> print(dt) +'1970-01-01T00:59:59+01:00' +``` + +Finally, if you find yourself inheriting a `datetime.datetime` instance, +you can create a `DateTime` instance via the `instance()` function. + +```python +>>> dt = datetime(2008, 1, 1) +>>> p = pendulum.instance(dt) +>>> print(p) +'2008-01-01T00:00:00+00:00' +``` diff --git a/docs/docs/introduction.md b/docs/docs/introduction.md new file mode 100644 index 0000000..fbbab97 --- /dev/null +++ b/docs/docs/introduction.md @@ -0,0 +1,21 @@ +# Introduction + +Pendulum is a Python package to ease datetimes manipulation. + +It provides classes that are drop-in replacements for the native ones (they inherit from them). + +Special care has been taken to ensure timezones are handled correctly, +and are based on the underlying `tzinfo` implementation. +For example, all comparisons are done in `UTC` or in the timezone of the datetime being used. + +```python +>>> import pendulum + +>>> dt_toronto = pendulum.datetime(2012, 1, 1, tz='America/Toronto') +>>> dt_vancouver = pendulum.datetime(2012, 1, 1, tz='America/Vancouver') + +>>> print(dt_vancouver.diff(dt_toronto).in_hours()) +3 +``` + +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 new file mode 100644 index 0000000..7deff23 --- /dev/null +++ b/docs/docs/limitations.md @@ -0,0 +1,43 @@ +# Limitations + +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: + + ```python + import pendulum + from sqlite3 import register_adapter + + 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: + + ```python + import pendulum + import MySQLdb.converters + import pymysql.converters + + MySQLdb.converters.conversions[pendulum.DateTime] = MySQLdb.converters.DateTime2literal + pymysql.converters.conversions[pendulum.DateTime] = pymysql.converters.escape_datetime + ``` + +* `django` will use the `isoformat()` method to store datetimes in the database. However, since `pendulum` is always timezone aware, the offset information will always be returned by `isoformat()` raising an error, at least for MySQL databases. To work around it, you can either create your own `DateTimeField` or use the previous workaround for `MySQLdb`: + + ```python + import pendulum + from django.db.models import DateTimeField as BaseDateTimeField + + + class DateTimeField(BaseDateTimeField): + + def value_to_string(self, obj): + val = self.value_from_object(obj) + + if isinstance(value, pendulum.DateTime): + return value.format('YYYY-MM-DD HH:mm:ss') + + return '' if val is None else val.isoformat() + ``` diff --git a/docs/docs/localization.md b/docs/docs/localization.md new file mode 100644 index 0000000..7560dae --- /dev/null +++ b/docs/docs/localization.md @@ -0,0 +1,36 @@ +# Localization + +Localization occurs when using the `format()` method which accepts a `locale` keyword. + +```python +>>> import pendulum + +>>> dt = pendulum.datetime(1975, 5, 21) +>>> dt.format('dddd DD MMMM YYYY', locale='de') +'Mittwoch 21 Mai 1975' + +>>> dt.format('dddd DD MMMM YYYY') +'Wednesday 21 May 1975' +``` + +`diff_for_humans()` is also localized, you can set the locale +by using `pendulum.set_locale()`. + +```python +>>> import pendulum + +>>> pendulum.set_locale('de') +>>> pendulum.now().add(years=1).diff_for_humans() +'in 1 Jahr' +>>> pendulum.set_locale('en') +``` + +However, you might not want to set the locale globally. The `diff_for_humans()` +method accepts a `locale` keyword argument to use a locale for a specific call. + +```python +>>> pendulum.set_locale('de') +>>> dt = pendulum.now().add(years=1) +>>> dt.diff_for_humans(locale='fr') +'dans 1 an' +``` diff --git a/docs/docs/modifiers.md b/docs/docs/modifiers.md new file mode 100644 index 0000000..b440587 --- /dev/null +++ b/docs/docs/modifiers.md @@ -0,0 +1,86 @@ +# Modifiers + +This group of methods performs helpful modifications to a copy of the current instance. +You'll notice that the `start_of()`, `next()` and `previous()` methods +set the time to `00:00:00` and the `end_of()` methods set the time to `23:59:59.999999`. + +The only one slightly different is the `average()` method. +It returns the middle date between itself and the provided `DateTime` argument. + +```python +>>> import pendulum + +>>> dt = pendulum.datetime(2012, 1, 31, 12, 0, 0) + +>>> dt.start_of('day') +'2012-01-31 00:00:00' + +>>> dt.end_of('day') +'2012-01-31 23:59:59' + +>>> dt.start_of('month') +'2012-01-01 00:00:00' + +>>> dt.end_of('month') +'2012-01-31 23:59:59' + +>>> dt.start_of('year') +'2012-01-01 00:00:00' + +>>> dt.end_of('year') +'2012-12-31 23:59:59' + +>>> dt.start_of('decade') +'2010-01-01 00:00:00' + +>>> dt.end_of('decade') +'2019-12-31 23:59:59' + +>>> dt.start_of('century') +'2000-01-01 00:00:00' + +>>> dt.end_of('century') +'2099-12-31 23:59:59' + +>>> dt.start_of('week') +'2012-01-30 00:00:00' +>>> dt.day_of_week == pendulum.MONDAY +True # ISO8601 week starts on Monday + +>>> dt.end_of('week') +'2012-02-05 23:59:59' +>>> dt.day_of_week == pendulum.SUNDAY +True # ISO8601 week ends on SUNDAY + +>>> dt.next(pendulum.WEDNESDAY) +'2012-02-01 00:00:00' +>>> dt.day_of_week == pendulum.WEDNESDAY +True + +>>> dt = pendulum.datetime(2012, 1, 1, 12, 0, 0) +dt.next() +'2012-01-08 00:00:00' +>>> dt.next(keep_time=True) +'2012-01-08T12:00:00+00:00' + +>>> dt = pendulum.datetime(2012, 1, 31, 12, 0, 0) +>>> dt.previous(pendulum.WEDNESDAY) +'2012-01-25 00:00:00' +>>> dt.day_of_week == pendulum.WEDNESDAY +True + +>>> dt = pendulum.datetime(2012, 1, 1, 12, 0, 0) +>>> dt.previous() +'2011-12-25 00:00:00' +>>> dt.previous(keep_time=True) +'2011-12-25 12:00:00' + +>>> start = pendulum.datetime(2014, 1, 1) +>>> end = pendulum.datetime(2014, 1, 30) +>>> start.average(end) +'2014-01-15 12:00:00' + +# others that are defined that are similar +# and tha accept month, quarter and year units +# first_of(), last_of(), nth_of() +``` diff --git a/docs/docs/parsing.md b/docs/docs/parsing.md new file mode 100644 index 0000000..dd78fd4 --- /dev/null +++ b/docs/docs/parsing.md @@ -0,0 +1,114 @@ +# Parsing + +The library natively supports the RFC 3339 format, most ISO 8601 formats and some other common formats. + +```python +>>> import pendulum + +>>> dt = pendulum.parse('1975-05-21T22:00:00') +>>> print(dt) +'1975-05-21T22:00:00+00:00 + +# You can pass a tz keyword to specify the timezone +>>> dt = pendulum.parse('1975-05-21T22:00:00', tz='Europe/Paris') +>>> print(dt) +'1975-05-21T22:00:00+01:00' + +# Not ISO 8601 compliant but common +>>> dt = pendulum.parse('1975-05-21 22:00:00') +``` + +If you pass a non-standard or more complicated string, it will raise an exception, so it is advised to +use the `from_format()` helper instead. + +However, if you want the library to fall back on the [dateutil](https://dateutil.readthedocs.io) parser, +you have to pass `strict=False`. + +```python +>>> import pendulum + +>>> dt = pendulum.parse('31-01-01') +Traceback (most recent call last): +... +ParserError: Unable to parse string [31-01-01] + +>>> dt = pendulum.parse('31-01-01', strict=False) +>>> print(dt) +'2031-01-01T00:00:00+00:00' +``` + + +## RFC 3339 + +| String | Output | +| --------------------------------- | ------------------------------------------| +| 1996-12-19T16:39:57-08:00 | 1996-12-19T16:39:57-08:00 | +| 1990-12-31T23:59:59Z | 1990-12-31T23:59:59+00:00 | + +## ISO 8601 + +### Datetime + +| String | Output | +| --------------------------------- | ----------------------------------------- | +| 20161001T143028+0530 | 2016-10-01T14:30:28+05:30 | +| 20161001T14 | 2016-10-01T14:00:00+00:00 | + +### Date + +| String | Output | +| --------------------------------- | ----------------------------------------- | +| 2012 | 2012-01-01T00:00:00+00:00 | +| 2012-05-03 | 2012-05-03T00:00:00+00:00 | +| 20120503 | 2012-05-03T00:00:00+00:00 | +| 2012-05 | 2012-05-01T00:00:00+00:00 | + +### Ordinal day + +| String | Output | +| ---------------------------------- | ----------------------------------------- | +| 2012-007 | 2012-01-07T00:00:00+00:00 | +| 2012007 | 2012-01-07T00:00:00+00:00 | + +### Week number + +| String | Output | +| --------------------------------- | ----------------------------------------- | +| 2012-W05 | 2012-01-30T00:00:00+00:00 | +| 2012W05 | 2012-01-30T00:00:00+00:00 | +| 2012-W05-5 | 2012-02-03T00:00:00+00:00 | +| 2012W055 | 2012-02-03T00:00:00+00:00 | + +### Time + +When passing only time information the date will default to today. + +| String | Output | +| --------------------------------- | ------------------------------------------ | +| 00:00 | 2016-12-17T00:00:00+00:00 | +| 12:04:23 | 2016-12-17T12:04:23+00:00 | +| 120423 | 2016-12-17T12:04:23+00:00 | +| 12:04:23.45 | 2016-12-17T12:04:23.450000+00:00 | + +### Intervals + +| String | Output | +| ----------------------------------------- | ------------------------------------------------------ | +| 2007-03-01T13:00:00Z/2008-05-11T15:30:00Z | 2007-03-01T13:00:00+00:00 -> 2008-05-11T15:30:00+00:00 | +| 2008-05-11T15:30:00Z/P1Y2M10DT2H30M | 2008-05-11T15:30:00+00:00 -> 2009-07-21T18:00:00+00:00 | +| P1Y2M10DT2H30M/2008-05-11T15:30:00Z | 2007-03-01T13:00:00+00:00 -> 2008-05-11T15:30:00+00:00 | + +!!!note + + You can pass the ``exact`` keyword argument to ``parse()`` to get the exact type + that the string represents: + + ```python + >>> import pendulum + + >>> pendulum.parse('2012-05-03', exact=True) + Date(2012, 05, 03) + + >>> pendulum.parse('12:04:23', exact=True) + Time(12, 04, 23) + ``` diff --git a/docs/docs/period.md b/docs/docs/period.md new file mode 100644 index 0000000..a4dde16 --- /dev/null +++ b/docs/docs/period.md @@ -0,0 +1,168 @@ +# Period + +When you subtract a `DateTime` instance from another, or use the `diff()` method, it will return a `Period` 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: + +```python +>>> import pendulum + +>>> start = pendulum.datetime(2000, 11, 20) +>>> end = pendulum.datetime(2016, 11, 5) + +>>> period = end - start + +>>> period.years +15 +>>> period.months +11 +>>> period.in_years() +15 +>>> period.in_months() +191 + +# Note that the weeks property +# will change compared to the Duration class +>>> period.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 +5829 +``` + +Be aware that a period, just like an interval, 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: + +```python +>>> import pendulum + +>>> start = pendulum.datetime(2017, 3, 7, tz='America/Toronto') +>>> end = start.add(days=6) + +>>> period = end - start + +# timedelta properties +>>> period.days +5 +>>> period.seconds +82800 + +# period properties +>>> period.remaining_days +6 +>>> period.hours +0 +>>> period.remaining_seconds +0 +``` + +!!!warning + + Due to their nature (fixed duration between two datetimes), most arithmetic operations will + return a `Duration` instead of a `Period`. + + ```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 + Duration(weeks=1, days=5, minutes=1, seconds=8) + ``` + + +## Instantiation + +You can create an instance by using the `period()` helper: + +```python + +>>> import pendulum + +>>> start = pendulum.datetime(2000, 1, 1) +>>> end = pendulum.datetime(2000, 1, 31) + +>>> period = pendulum.period(start, end) +``` + +You can also make an inverted period: + +```python +>>> period = pendulum.period(end, start) +>>> period.remaining_days +-2 +``` + +If you have inverted dates but want to make sure that the period is positive, +you should set the `absolute` keyword argument to `True`: + +```python +>>> period = pendulum.period(end, start, absolute=True) +>>> period.remaining_days +2 +``` + +## Range + +If you want to iterate over a period, you can use the `range()` method: + +```python +>>> import pendulum + +>>> start = pendulum.datetime(2000, 1, 1) +>>> end = pendulum.datetime(2000, 1, 10) + +>>> period = pendulum.period(start, end) + +>>> for dt in period.range('days'): +>>> print(dt) + +'2000-01-01T00:00:00+00:00' +'2000-01-02T00:00:00+00:00' +'2000-01-03T00:00:00+00:00' +'2000-01-04T00:00:00+00:00' +'2000-01-05T00:00:00+00:00' +'2000-01-06T00:00:00+00:00' +'2000-01-07T00:00:00+00:00' +'2000-01-08T00:00:00+00:00' +'2000-01-09T00:00:00+00:00' +'2000-01-10T00:00:00+00:00' +``` + +!!!note + + Supported units for `range()` are: `years`, `months`, `weeks`, + `days`, `hours`, `minutes`, `seconds` and `microseconds` + +You can pass an amount for the passed unit to control the length of the gap: + +```python +>>> for dt in period.range('days', 2): +>>> print(dt) + +'2000-01-01T00:00:00+00:00' +'2000-01-03T00:00:00+00:00' +'2000-01-05T00:00:00+00:00' +'2000-01-07T00:00:00+00:00' +'2000-01-09T00:00:00+00:00' +``` + +You can also directly iterate over the `Period` instance, +the unit will be `days` in this case: + +```python +>>> for dt in period: +>>> print(dt) +``` + +You can check if a `DateTime` instance is inside a period using the `in` keyword: + +```python +>>> dt = pendulum.datetime(2000, 1, 4) +>>> dt in period +True +``` diff --git a/docs/docs/string_formatting.md b/docs/docs/string_formatting.md new file mode 100644 index 0000000..91b95fc --- /dev/null +++ b/docs/docs/string_formatting.md @@ -0,0 +1,175 @@ +# String formatting + +The `__str__` magic method is defined to allow `DateTime` instances to be printed +as a pretty date string when used in a string context. + +The default string representation is the same as the one returned by the `isoformat()` method. + +```python +>>> import pendulum + +>>> dt = pendulum.datetime(1975, 12, 25, 14, 15, 16) +>>> print(dt) +'1975-12-25T14:15:16+00:00' + +>>> dt.to_date_string() +'1975-12-25' + +>>> dt.to_formatted_date_string() +'Dec 25, 1975' + +>>> dt.to_time_string() +'14:15:16' + +>>> dt.to_datetime_string() +'1975-12-25 14:15:16' + +>>> dt.to_day_datetime_string() +'Thu, Dec 25, 1975 2:15 PM' + +# You can also use the format() method +>>> dt.format('dddd Do [of] MMMM YYYY HH:mm:ss A') +'Thursday 25th of December 1975 02:15:16 PM' + +# Of course, the strftime method is still available +>>> dt.strftime('%A %-d%t of %B %Y %I:%M:%S %p') +'Thursday 25th of December 1975 02:15:16 PM' +``` + +!!!note + + For localization support see the [Localization](#localization) section. + +## Common Formats + + +The following are methods to display a `DateTime` instance as a common format: + +```python +>>> import pendulum + +>>> dt = pendulum.now() + +>>> dt.to_atom_string() +'1975-12-25T14:15:16-05:00' + +>>> dt.to_cookie_string() +'Thursday, 25-Dec-1975 14:15:16 EST' + +>>> dt.to_iso8601_string() +'1975-12-25T14:15:16-0500' + +>>> dt.to_rfc822_string() +'Thu, 25 Dec 75 14:15:16 -0500' + +>>> dt.to_rfc850_string() +'Thursday, 25-Dec-75 14:15:16 EST' + +>>> dt.to_rfc1036_string() +'Thu, 25 Dec 75 14:15:16 -0500' + +>>> dt.to_rfc1123_string() +'Thu, 25 Dec 1975 14:15:16 -0500' + +>>> dt.to_rfc2822_string() +'Thu, 25 Dec 1975 14:15:16 -0500' + +>>> dt.to_rfc3339_string() +'1975-12-25T14:15:16-05:00' + +>>> dt.to_rss_string() +'Thu, 25 Dec 1975 14:15:16 -0500' + +>>> dt.to_w3c_string() +'1975-12-25T14:15:16-05:00' +``` + +## Formatter + +Pendulum uses its own formatter when using the `format()` method. + +This format is more intuitive to use than the one used with `strftime()` +and supports more directives. + +```python +>>> import pendulum + +>>> dt = pendulum.datetime(1975, 12, 25, 14, 15, 16) +>>> dt.format('YYYY-MM-DD HH:mm:ss') +'1975-12-25 14:15:16' +``` + +### Tokens + +The following tokens are currently supported: + + +| | Token | Output | +| ------------------------------ | ------------- | ------------------------------------------ | +| **Year** | YYYY | 2000, 2001, 2002 ... 2012, 2013 | +| | YY | 00, 01, 02 ... 12, 13 | +| | Y | 2000, 2001, 2002 ... 2012, 2013 | +| **Quarter** | Q | 1 2 3 4 | +| | Qo | 1st 2nd 3rd 4th | +| **Month** | MMMM | January, February, March ... | +| | MMM | Jan, Feb, Mar ... | +| | MM | 01, 02, 03 ... 11, 12 | +| | M | 1, 2, 3 ... 11, 12 | +| | Mo | 1st 2nd ... 11th 12th | +| **Day of Year** | DDDD | 001, 002, 003 ... 364, 365 | +| | DDD | 1, 2, 3 ... 4, 5 | +| **Day of Month** | DD | 01, 02, 03 ... 30, 31 | +| | D | 1, 2, 3 ... 30, 31 | +| | Do | 1st, 2nd, 3rd ... 30th, 31st | +| **Day of Week** | dddd | Monday, Tuesday, Wednesday ... | +| | ddd | Mon, Tue, Wed ... | +| | dd | Mo, Tu, We ... | +| | d | 0, 1, 2 ... 6 | +| **Days of ISO Week** | E | 1, 2, 3 ... 7 | +| **Hour** | HH | 00, 01, 02 ... 23 | +| | H | 0, 1, 2 ... 23 | +| | hh | 01, 02, 03 ... 11, 12 | +| | h | 1, 2, 3 ... 11, 12 | +| **Minute** | mm | 00, 01, 02 ... 58, 59 | +| | m | 0, 1, 2 ... 58, 59 | +| **Second** | ss | 00, 01, 02 ... 58, 59 | +| | s | 0, 1, 2 ... 58, 59 | +| **Fractional Second** | S | 0 1 ... 8 9 | +| | SS | 00, 01, 02 ... 98, 99 | +| | SSS | 000 001 ... 998 999 | +| | SSSS ... | 000[0..] 001[0..] ... 998[0..] 999[0..] | +| | SSSSSS | | +| **AM / PM** | A | AM, PM | +| **Timezone** | Z | -07:00, -06:00 ... +06:00, +07:00 | +| | ZZ | -0700, -0600 ... +0600, +0700 | +| | z | Asia/Baku, Europe/Warsaw, GMT ... | +| | zz | EST CST ... MST PST | +| **Seconds timestamp** | X | 1381685817, 1234567890.123 | +| **Milliseconds timestamp** | x | 1234567890123 | + + +### Localized Formats + +Because preferred formatting differs based on locale, +there are a few tokens that can be used to format an instance based on its locale. + +| | | | +| ------------------------------------------------------ | ------------- | ------------------------------------------ | +| **Time** | LT | 8:30 PM | +| **Time with seconds** | LTS | 8:30:25 PM | +| **Month numeral, day of month, year** | L | 09/04/1986 | +| **Month name, day of month, year** | LL | September 4 1986 | +| **Month name, day of month, year, time** | LLL | September 4 1986 8:30 PM | +| **Month name, day of month, day of week, year, time** | LLLL | Thursday, September 4 1986 8:30 PM | + +### Escaping Characters + +To escape characters in format strings, you can wrap the characters in square brackets. + +```python +>>> import pendulum + +>>> dt = pendulum.now() +>>> dt.format('[today] dddd') +'today Sunday' +``` diff --git a/docs/docs/testing.md b/docs/docs/testing.md new file mode 100644 index 0000000..dfca054 --- /dev/null +++ b/docs/docs/testing.md @@ -0,0 +1,59 @@ +# 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: + +* A call to the `now()` method, ex. `pendulum.now()`. +* When the string "now" is passed to the `parse()` method, ex. `pendulum.parse('now')` + +```python +>>> import pendulum + +# Create testing datetime +>>> known = pendulum.datetime(2001, 5, 21, 12) + +# Set the mock +>>> pendulum.set_test_now(known) + +>>> print(pendulum.now()) +'2001-05-21T12:00:00+00:00' + +>>> print(pendulum.parse('now')) +'2001-05-21T12:00:00+00:00' + +# Clear the mock +>>> pendulum.set_test_now() + +>>> print(pendulum.now()) +'2016-07-10T22:10:33.954851-05:00' +``` + +Related methods will also return values mocked according to the **now** instance. + +```python +>>> print(pendulum.today()) +'2001-05-21T00:00:00+00:00' + +>>> print(pendulum.tomorrow()) +'2001-05-22T00:00:00+00:00' + +>>> print(pendulum.yesterday()) +'2001-05-20T00:00:00+00:00' +``` + +If you don't want to manually clear the mock (or you are afraid of forgetting), +you can use the provided `test()` contextmanager. + +```python +>>> import pendulum + +>>> known = pendulum.datetime(2001, 5, 21, 12) + +>>> with pendulum.test(known): +>>> print(pendulum.now()) +'2001-05-21T12:00:00+00:00' + +>>> print(pendulum.now()) +'2016-07-10T22:10:33.954851-05:00' +``` diff --git a/docs/docs/timezones.md b/docs/docs/timezones.md new file mode 100644 index 0000000..85ff147 --- /dev/null +++ b/docs/docs/timezones.md @@ -0,0 +1,193 @@ +# Timezones + +Timezones are an important part of every datetime library, and `pendulum` +tries to provide an easy and accurate system to handle them properly. + +!!!note + + The timezone system works best inside the `pendulum` ecosystem but + can also be used with the standard ``datetime`` library with a few limitations. + See [Using the timezone library directly](#using-the-timezone-library-directly). + +## Normalization + +When you create a `DateTime` instance, the library will normalize it for the +given timezone to properly handle any transition that might have occurred. + +```python +>>> import pendulum + +>>> pendulum.datetime(2013, 3, 31, 2, 30, tz='Europe/Paris') +# 2:30 for the 31th of March 2013 does not exist +# so pendulum will return the actual time which is 3:30+02:00 +'2013-03-31T03:30:00+02:00' + +>>> pendulum.datetime(2013, 10, 27, 2, 30, tz='Europe/Paris') +# Here, 2:30 exists twice in the day so pendulum will +# assume that the transition already occurred +'2013-10-27T02:30:00+01:00' +``` + +You can, however, control the normalization behavior: + +```python +>>> import pendulum + +>>> pendulum.datetime(2013, 3, 31, 2, 30, 0, 0, tz='Europe/Paris', + dst_rule=pendulum.PRE_TRANSITION) +'2013-03-31T01:30:00+01:00' +>>> pendulum.datetime(2013, 10, 27, 2, 30, 0, 0, tz='Europe/Paris', + dst_rule=pendulum.PRE_TRANSITION) +'2013-10-27T02:30:00+02:00' + +>>> pendulum.datetime(2013, 3, 31, 2, 30, 0, 0, tz='Europe/Paris', + dst_rule=pendulum.TRANSITION_ERROR) +# NonExistingTime: The datetime 2013-03-31 02:30:00 does not exist +>>> pendulum.datetime(2013, 10, 27, 2, 30, 0, 0, tz='Europe/Paris', + dst_rule=pendulum.TRANSITION_ERROR) +# AmbiguousTime: The datetime 2013-10-27 02:30:00 is ambiguous. +``` + +Note that it only affects instances at creation time. Shifting time around +transition times still behaves the same. + +## Shifting time to transition + +So, what happens when you add time to a `DateTime` instance and stumble upon +a transition time? +Well `pendulum`, provided with the context of the previous instance, will +adopt the proper behavior and apply the transition accordingly. + +```python +>>> import pendulum + +>>> dt = pendulum.datetime(2013, 3, 31, 1, 59, 59, 999999, + tz='Europe/Paris') +'2013-03-31T01:59:59.999999+01:00' +>>> dt = dt.add(microseconds=1) +'2013-03-31T03:00:00+02:00' +>>> dt.subtract(microseconds=1) +'2013-03-31T01:59:59.999998+01:00' + +>>> dt = pendulum.datetime(2013, 10, 27, 2, 59, 59, 999999, + tz='Europe/Paris', + dst_rule=pendulum.PRE_TRANSITION) +'2013-10-27T02:59:59.999999+02:00' +>>> dt = dt.add(microseconds=1) +'2013-10-27T02:00:00+01:00' +>>> dt = dt.subtract(microseconds=1) +'2013-10-27T02:59:59.999999+02:00' +``` + +## Switching timezones + +You can easily change the timezone of a `DateTime` instance +with the `in_timezone()` method. + +!!!note + + You can also use the more concise ``in_tz()`` + +```python +>>> in_paris = pendulum.datetime(2016, 8, 7, 22, 24, 30, tz='Europe/Paris') +'2016-08-07T22:24:30+02:00' +>>> in_paris.in_timezone('America/New_York') +'2016-08-07T16:24:30-04:00' +>>> in_paris.in_tz('Asia/Tokyo') +'2016-08-08T05:24:30+09:00' +``` + +## Using the timezone library directly + +!!!warning + + **You should avoid using the timezone library in Python < 3.6.** + + This is due to the fact that Pendulum relies heavily on the presence + of the `fold` attribute which was introduced in Python 3.6. + + The reason it works inside the Pendulum ecosystem is that it + backports the `fold` attribute in the `DateTime` class. + +Like said in the introduction, you can use the timezone library +directly with standard `datetime` objects but with limitations, especially +when adding and subtracting time around transition times. + +The value of the `fold` attribute will be used +by default to determine the transition rule. + +```python +>>> from datetime import datetime +>>> from pendulum import timezone + +>>> paris = timezone('Europe/Paris') +>>> dt = datetime(2013, 3, 31, 2, 30) +# By default, fold is set to 0 +>>> dt = paris.convert(dt) +>>> dt.isoformat() +'2013-03-31T01:30:00+01:00' + +>>> dt = datetime(2013, 3, 31, 2, 30, fold=1) +>>> dt = paris.convert(dt) +>>> dt.isoformat() +'2013-03-31T03:30:00+02:00' +``` + +Instead of relying on the `fold` attribute, you can use the `dst_rule` +keyword argument. This is especially useful if you want to raise errors +on non-existing and ambiguous times. + +```python +>>> import pendulum + +>>> dt = datetime(2013, 3, 31, 2, 30) +# By default, fold is set to 0 +>>> dt = paris.convert(dt, dst_rule=pendulum.PRE_TRANSITION) +>>> dt.isoformat() +'2013-03-31T01:30:00+01:00' + +>>> dt = paris.convert(dt, dst_rule=pendulum.POST_TRANSITION) +>>> dt.isoformat() +'2013-03-31T03:30:00+02:00' + +>>> paris.convert(dt, dst_rule=pendulum.TRANSITION_ERROR) +# NonExistingTime: The datetime 2013-03-31 02:30:00 does not exist +``` + +This works as expected. However, whenever we add or subtract a `timedelta` +object, things get tricky. + +```python +>>> from datetime import datetime, timedelta +>>> from pendulum import timezone + +>>> dt = datetime(2013, 3, 31, 1, 59, 59, 999999) +>>> dt = paris.convert(dt) +>>> dt.isoformat() +'2013-03-31T01:59:59.999999+01:00' +>>> dt = dt + timedelta(microseconds=1) +>>> dt.isoformat() +'2013-03-31T02:00:00+01:00' +``` + +This is not what we expect. It should be `2013-03-31T03:00:00+02:00`. +It is actually easy to retrieve the proper datetime by using `convert()` +again. + +```python +>>> dt = tz.convert(dt) +>>> dt.isoformat() +'2013-03-31T03:00:00+02:00' +``` + +You can also get a normalized `datetime` object +from a `Timezone` by using the `datetime()` method: + +```python +>>> import pendulum + +>>> tz = pendulum.timezone('Europe/Paris') +>>> dt = tz.datetime(2013, 3, 31, 2, 30) +>>> dt.isoformat() +'2013-03-31T03:30:00+02:00' +``` diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml new file mode 100644 index 0000000..da64d75 --- /dev/null +++ b/docs/mkdocs.yml @@ -0,0 +1,17 @@ +site_name: Pendulum documentation + +theme: + name: null + custom_dir: theme + +extra: + version: 2.1 + +markdown_extensions: + - codehilite + - admonition + - pymdownx.superfences + - toc: + permalink:  + - markdown_include.include: + base_path: docs diff --git a/docs/theme/main.html b/docs/theme/main.html new file mode 100644 index 0000000..80caadf --- /dev/null +++ b/docs/theme/main.html @@ -0,0 +1,33 @@ +--- +layout: documentation +title: {{ config.site_name|striptags|e }} +version: 2.x +--- + + + +
+
+
+
+
Version {{config.extra.version}}
+

Documentation

+
+
+
+
+ + +
+
+
+
+
+
+ {{page.content}} +
+
+
+
+
+
diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..666c281 --- /dev/null +++ b/meson.build @@ -0,0 +1,20 @@ +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/__init__.py b/pendulum/__init__.py index a85ed88..4491244 100644 --- a/pendulum/__init__.py +++ b/pendulum/__init__.py @@ -1,315 +1,363 @@ -from __future__ import absolute_import - -import datetime as _datetime - -from typing import Optional -from typing import Union - -from .__version__ import __version__ -from .constants import DAYS_PER_WEEK -from .constants import FRIDAY -from .constants import HOURS_PER_DAY -from .constants import MINUTES_PER_HOUR -from .constants import MONDAY -from .constants import MONTHS_PER_YEAR -from .constants import SATURDAY -from .constants import SECONDS_PER_DAY -from .constants import SECONDS_PER_HOUR -from .constants import SECONDS_PER_MINUTE -from .constants import SUNDAY -from .constants import THURSDAY -from .constants import TUESDAY -from .constants import WEDNESDAY -from .constants import WEEKS_PER_YEAR -from .constants import YEARS_PER_CENTURY -from .constants import YEARS_PER_DECADE -from .date import Date -from .datetime import DateTime -from .duration import Duration -from .formatting import Formatter -from .helpers import format_diff -from .helpers import get_locale -from .helpers import get_test_now -from .helpers import has_test_now -from .helpers import locale -from .helpers import set_locale -from .helpers import set_test_now -from .helpers import test -from .helpers import week_ends_at -from .helpers import week_starts_at -from .parser import parse -from .period import Period -from .time import Time -from .tz import POST_TRANSITION -from .tz import PRE_TRANSITION -from .tz import TRANSITION_ERROR -from .tz import UTC -from .tz import local_timezone -from .tz import set_local_timezone -from .tz import test_local_timezone -from .tz import timezone -from .tz import timezones -from .tz.timezone import Timezone as _Timezone -from .utils._compat import _HAS_FOLD - - -_TEST_NOW = None # type: Optional[DateTime] -_LOCALE = "en" -_WEEK_STARTS_AT = MONDAY -_WEEK_ENDS_AT = SUNDAY - -_formatter = Formatter() - - -def _safe_timezone(obj): - # type: (Optional[Union[str, float, _datetime.tzinfo, _Timezone]]) -> _Timezone - """ - Creates a timezone instance - from a string, Timezone, TimezoneInfo or integer offset. - """ - if isinstance(obj, _Timezone): - return obj - - if obj is None or obj == "local": - return local_timezone() - - if isinstance(obj, (int, float)): - obj = int(obj * 60 * 60) - elif isinstance(obj, _datetime.tzinfo): - # pytz - if hasattr(obj, "localize"): - obj = obj.zone - elif obj.tzname(None) == "UTC": - return UTC - else: - offset = obj.utcoffset(None) - - if offset is None: - offset = _datetime.timedelta(0) - - obj = int(offset.total_seconds()) - - return timezone(obj) - - -# Public API -def datetime( - year, # type: int - month, # type: int - day, # type: int - hour=0, # type: int - minute=0, # type: int - second=0, # type: int - microsecond=0, # type: int - tz=UTC, # type: Optional[Union[str, float, _Timezone]] - dst_rule=POST_TRANSITION, # type: str -): # type: (...) -> DateTime - """ - Creates a new DateTime instance from a specific date and time. - """ - if tz is not None: - tz = _safe_timezone(tz) - - if not _HAS_FOLD: - dt = naive(year, month, day, hour, minute, second, microsecond) - else: - dt = _datetime.datetime(year, month, day, hour, minute, second, microsecond) - if tz is not None: - dt = tz.convert(dt, dst_rule=dst_rule) - - return DateTime( - dt.year, - dt.month, - dt.day, - dt.hour, - dt.minute, - dt.second, - dt.microsecond, - tzinfo=dt.tzinfo, - fold=dt.fold, - ) - - -def local( - year, month, day, hour=0, minute=0, second=0, microsecond=0 -): # type: (int, int, int, int, int, int, int) -> DateTime - """ - Return a DateTime in the local timezone. - """ - return datetime( - year, month, day, hour, minute, second, microsecond, tz=local_timezone() - ) - - -def naive( - year, month, day, hour=0, minute=0, second=0, microsecond=0 -): # type: (int, int, int, int, int, int, int) -> DateTime - """ - Return a naive DateTime. - """ - return DateTime(year, month, day, hour, minute, second, microsecond) - - -def date(year, month, day): # type: (int, int, int) -> Date - """ - Create a new Date instance. - """ - return Date(year, month, day) - - -def time(hour, minute=0, second=0, microsecond=0): # type: (int, int, int, int) -> Time - """ - Create a new Time instance. - """ - return Time(hour, minute, second, microsecond) - - -def instance( - dt, tz=UTC -): # type: (_datetime.datetime, Optional[Union[str, _Timezone]]) -> DateTime - """ - Create a DateTime instance from a datetime one. - """ - if not isinstance(dt, _datetime.datetime): - raise ValueError("instance() only accepts datetime objects.") - - if isinstance(dt, DateTime): - return dt - - tz = dt.tzinfo or tz - - # Checking for pytz/tzinfo - if isinstance(tz, _datetime.tzinfo) and not isinstance(tz, _Timezone): - # pytz - if hasattr(tz, "localize") and tz.zone: - tz = tz.zone - else: - # We have no sure way to figure out - # the timezone name, we fallback - # on a fixed offset - tz = tz.utcoffset(dt).total_seconds() / 3600 - - return datetime( - dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond, tz=tz - ) - - -def now(tz=None): # type: (Optional[Union[str, _Timezone]]) -> DateTime - """ - Get a DateTime instance for the current date and time. - """ - if has_test_now(): - test_instance = get_test_now() - _tz = _safe_timezone(tz) - - if tz is not None and _tz != test_instance.timezone: - test_instance = test_instance.in_tz(_tz) - - return test_instance - - if tz is None or tz == "local": - dt = _datetime.datetime.now(local_timezone()) - elif tz is UTC or tz == "UTC": - dt = _datetime.datetime.now(UTC) - else: - dt = _datetime.datetime.now(UTC) - tz = _safe_timezone(tz) - dt = tz.convert(dt) - - return DateTime( - dt.year, - dt.month, - dt.day, - dt.hour, - dt.minute, - dt.second, - dt.microsecond, - tzinfo=dt.tzinfo, - fold=dt.fold if _HAS_FOLD else 0, - ) - - -def today(tz="local"): # type: (Union[str, _Timezone]) -> DateTime - """ - Create a DateTime instance for today. - """ - return now(tz).start_of("day") - - -def tomorrow(tz="local"): # type: (Union[str, _Timezone]) -> DateTime - """ - Create a DateTime instance for today. - """ - return today(tz).add(days=1) - - -def yesterday(tz="local"): # type: (Union[str, _Timezone]) -> DateTime - """ - Create a DateTime instance for today. - """ - return today(tz).subtract(days=1) - - -def from_format( - string, fmt, tz=UTC, locale=None, # noqa -): # type: (str, str, Union[str, _Timezone], Optional[str]) -> DateTime - """ - Creates a DateTime instance from a specific format. - """ - parts = _formatter.parse(string, fmt, now(), locale=locale) - if parts["tz"] is None: - parts["tz"] = tz - - return datetime(**parts) - - -def from_timestamp( - timestamp, tz=UTC -): # type: (Union[int, float], Union[str, _Timezone]) -> DateTime - """ - Create a DateTime instance from a timestamp. - """ - dt = _datetime.datetime.utcfromtimestamp(timestamp) - - dt = datetime( - dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond - ) - - if tz is not UTC or tz != "UTC": - dt = dt.in_timezone(tz) - - return dt - - -def duration( - days=0, # type: float - seconds=0, # type: float - microseconds=0, # type: float - milliseconds=0, # type: float - minutes=0, # type: float - hours=0, # type: float - weeks=0, # type: float - years=0, # type: float - months=0, # type: float -): # type: (...) -> Duration - """ - Create a Duration instance. - """ - return Duration( - days=days, - seconds=seconds, - microseconds=microseconds, - milliseconds=milliseconds, - minutes=minutes, - hours=hours, - weeks=weeks, - years=years, - months=months, - ) - - -def period(start, end, absolute=False): # type: (DateTime, DateTime, bool) -> Period - """ - Create a Period instance. - """ - return Period(start, end, absolute=absolute) +from __future__ import annotations + +import datetime as _datetime + +from typing import Union +from typing import cast + +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.duration import Duration +from pendulum.formatting import Formatter +from pendulum.helpers import format_diff +from pendulum.helpers import get_locale +from pendulum.helpers import locale +from pendulum.helpers import set_locale +from pendulum.helpers import week_ends_at +from pendulum.helpers import week_starts_at +from pendulum.interval import Interval +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 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 + +_TEST_NOW: DateTime | None = None +_LOCALE = "en" +_WEEK_STARTS_AT = MONDAY +_WEEK_ENDS_AT = SUNDAY + +_formatter = Formatter() + + +def _safe_timezone( + obj: str | float | _datetime.tzinfo | Timezone | FixedTimezone | None, + dt: _datetime.datetime | None = None, +) -> Timezone | FixedTimezone: + """ + Creates a timezone instance + from a string, Timezone, TimezoneInfo or integer offset. + """ + if isinstance(obj, (Timezone, FixedTimezone)): + return obj + + if obj is None or obj == "local": + return local_timezone() + + if isinstance(obj, (int, float)): + obj = int(obj * 60 * 60) + elif isinstance(obj, _datetime.tzinfo): + # zoneinfo + if hasattr(obj, "key"): + obj = obj.key # type: ignore + # pytz + elif hasattr(obj, "localize"): + obj = obj.zone # type: ignore + elif obj.tzname(None) == "UTC": + return UTC + else: + offset = obj.utcoffset(dt) + + if offset is None: + offset = _datetime.timedelta(0) + + obj = int(offset.total_seconds()) + + obj = cast(Union[str, int], obj) + + return timezone(obj) + + +# Public API +def datetime( + year: int, + month: int, + day: int, + hour: int = 0, + minute: int = 0, + second: int = 0, + microsecond: int = 0, + tz: str | float | Timezone | FixedTimezone | _datetime.tzinfo | None = UTC, + fold: int = 1, + raise_on_unknown_times: bool = False, +) -> DateTime: + """ + Creates a new DateTime instance from a specific date and time. + """ + return DateTime.create( + year, + month, + day, + hour=hour, + minute=minute, + second=second, + microsecond=microsecond, + tz=tz, + fold=fold, + raise_on_unknown_times=raise_on_unknown_times, + ) + + +def local( + year: int, + month: int, + day: int, + hour: int = 0, + minute: int = 0, + second: int = 0, + microsecond: int = 0, +) -> DateTime: + """ + Return a DateTime in the local timezone. + """ + return datetime( + year, month, day, hour, minute, second, microsecond, tz=local_timezone() + ) + + +def naive( + year: int, + month: int, + day: int, + hour: int = 0, + minute: int = 0, + second: int = 0, + microsecond: int = 0, + fold: int = 1, +) -> DateTime: + """ + Return a naive DateTime. + """ + return DateTime(year, month, day, hour, minute, second, microsecond, fold=fold) + + +def date(year: int, month: int, day: int) -> Date: + """ + Create a new Date instance. + """ + return Date(year, month, day) + + +def time(hour: int, minute: int = 0, second: int = 0, microsecond: int = 0) -> Time: + """ + Create a new Time instance. + """ + return Time(hour, minute, second, microsecond) + + +def instance( + dt: _datetime.datetime, + tz: str | Timezone | FixedTimezone | _datetime.tzinfo | None = UTC, +) -> DateTime: + """ + Create a DateTime instance from a datetime one. + """ + if not isinstance(dt, _datetime.datetime): + raise ValueError("instance() only accepts datetime objects.") + + if isinstance(dt, DateTime): + return dt + + tz = dt.tzinfo or tz + + if tz is not None: + tz = _safe_timezone(tz, dt=dt) + + 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), + ) + + +def now(tz: str | Timezone | None = None) -> DateTime: + """ + Get a DateTime instance for the current date and time. + """ + return DateTime.now(tz) + + +def today(tz: str | Timezone = "local") -> DateTime: + """ + Create a DateTime instance for today. + """ + return now(tz).start_of("day") + + +def tomorrow(tz: str | Timezone = "local") -> DateTime: + """ + Create a DateTime instance for today. + """ + return today(tz).add(days=1) + + +def yesterday(tz: str | Timezone = "local") -> DateTime: + """ + Create a DateTime instance for today. + """ + return today(tz).subtract(days=1) + + +def from_format( + string: str, + fmt: str, + tz: str | Timezone = UTC, + locale: str | None = None, +) -> DateTime: + """ + Creates a DateTime instance from a specific format. + """ + parts = _formatter.parse(string, fmt, now(tz=tz), locale=locale) + if parts["tz"] is None: + parts["tz"] = tz + + return datetime(**parts) + + +def from_timestamp(timestamp: int | float, tz: str | Timezone = UTC) -> DateTime: + """ + Create a DateTime instance from a timestamp. + """ + dt = _datetime.datetime.utcfromtimestamp(timestamp) + + dt = datetime( + dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond + ) + + if tz is not UTC or tz != "UTC": + dt = dt.in_timezone(tz) + + return dt + + +def duration( + days: float = 0, + seconds: float = 0, + microseconds: float = 0, + milliseconds: float = 0, + minutes: float = 0, + hours: float = 0, + weeks: float = 0, + years: float = 0, + months: float = 0, +) -> Duration: + """ + Create a Duration instance. + """ + return Duration( + days=days, + seconds=seconds, + microseconds=microseconds, + milliseconds=milliseconds, + minutes=minutes, + hours=hours, + weeks=weeks, + years=years, + months=months, + ) + + +def interval(start: DateTime, end: DateTime, absolute: bool = False) -> Interval: + """ + Create an Interval instance. + """ + return Interval(start, end, absolute=absolute) + + +# Testing + +_traveller = Traveller(DateTime) + +freeze = _traveller.freeze +travel = _traveller.travel +travel_to = _traveller.travel_to +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", + "Date", + "DateTime", + "Duration", + "Formatter", + "date", + "datetime", + "duration", + "format_diff", + "freeze", + "from_format", + "from_timestamp", + "get_locale", + "instance", + "interval", + "local", + "locale", + "naive", + "now", + "set_locale", + "week_ends_at", + "week_starts_at", + "parse", + "Interval", + "Time", + "UTC", + "local_timezone", + "set_local_timezone", + "test_local_timezone", + "time", + "timezone", + "timezones", + "today", + "tomorrow", + "travel", + "travel_back", + "travel_to", + "FixedTimezone", + "Timezone", + "yesterday", +] diff --git a/pendulum/__version__.py b/pendulum/__version__.py index 62b3483..29e86a8 100644 --- a/pendulum/__version__.py +++ b/pendulum/__version__.py @@ -1 +1 @@ -__version__ = "2.1.2" +__version__ = "3.0.0a" diff --git a/pendulum/_extensions/_helpers.c b/pendulum/_extensions/_helpers.c index aa92ae5..a3114d9 100644 --- a/pendulum/_extensions/_helpers.c +++ b/pendulum/_extensions/_helpers.c @@ -1,930 +1,931 @@ -/* ------------------------------------------------------------------------- */ - -#include -#include -#include -#include -#include -#include -#include -#include - -/* ------------------------------------------------------------------------- */ - -#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, "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) -{ - char repr[82] = {0}; - - sprintf( - repr, - "%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); - - return PyUnicode_FromString(repr); -} - -/* - * 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; -} +/* ------------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* ------------------------------------------------------------------------- */ + +#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/_extensions/_helpers.pyi b/pendulum/_extensions/_helpers.pyi new file mode 100644 index 0000000..99a5397 --- /dev/null +++ b/pendulum/_extensions/_helpers.pyi @@ -0,0 +1,31 @@ +from __future__ import annotations + +from collections import namedtuple +from datetime import date +from datetime import datetime + +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 PreciseDiff( + namedtuple( + "PreciseDiff", + "years months days " "hours minutes seconds microseconds " "total_days", + ) +): + years: int + months: int + days: int + hours: int + minutes: int + seconds: int + microseconds: int + total_days: 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/_extensions/helpers.py b/pendulum/_extensions/helpers.py index 16d078c..01066a3 100644 --- a/pendulum/_extensions/helpers.py +++ b/pendulum/_extensions/helpers.py @@ -1,358 +1,364 @@ -import datetime -import math -import typing - -from collections import namedtuple - -from ..constants import DAY_OF_WEEK_TABLE -from ..constants import DAYS_PER_L_YEAR -from ..constants import DAYS_PER_MONTHS -from ..constants import DAYS_PER_N_YEAR -from ..constants import EPOCH_YEAR -from ..constants import MONTHS_OFFSETS -from ..constants import SECS_PER_4_YEARS -from ..constants import SECS_PER_100_YEARS -from ..constants import SECS_PER_400_YEARS -from ..constants import SECS_PER_DAY -from ..constants import SECS_PER_HOUR -from ..constants import SECS_PER_MIN -from ..constants import SECS_PER_YEAR -from ..constants import TM_DECEMBER -from ..constants import TM_JANUARY - - -class PreciseDiff( - namedtuple( - "PreciseDiff", - "years months days " "hours minutes seconds microseconds " "total_days", - ) -): - def __repr__(self): - return ( - "{years} years " - "{months} months " - "{days} days " - "{hours} hours " - "{minutes} minutes " - "{seconds} seconds " - "{microseconds} microseconds" - ).format( - years=self.years, - months=self.months, - days=self.days, - hours=self.hours, - minutes=self.minutes, - seconds=self.seconds, - microseconds=self.microseconds, - ) - - -def is_leap(year): # type: (int) -> bool - return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) - - -def is_long_year(year): # type: (int) -> bool - def p(y): - return y + y // 4 - y // 100 + y // 400 - - return p(year) % 7 == 4 or p(year - 1) % 7 == 3 - - -def week_day(year, month, day): # type: (int, int, int) -> int - if month < 3: - year -= 1 - - w = ( - year - + year // 4 - - year // 100 - + year // 400 - + DAY_OF_WEEK_TABLE[month - 1] - + day - ) % 7 - - if not w: - w = 7 - - return w - - -def days_in_year(year): # type: (int) -> int - if is_leap(year): - return DAYS_PER_L_YEAR - - return DAYS_PER_N_YEAR - - -def timestamp(dt): # type: (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, utc_offset, microseconds -): # type: (int, int, int) -> typing.Tuple[int, int, int, int, int, int, int] - """ - Returns a UNIX time as a broken down time - for a particular transition type. - - :type unix_time: int - :type utc_offset: int - :type microseconds: int - - :rtype: tuple - """ - year = EPOCH_YEAR - seconds = int(math.floor(unix_time)) - - # Shift to a base year that is 400-year aligned. - if seconds >= 0: - seconds -= 10957 * SECS_PER_DAY - year += 30 # == 2000 - else: - seconds += (146097 - 10957) * 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, seconds and microseconds - hour = seconds // SECS_PER_HOUR - seconds %= SECS_PER_HOUR - minute = seconds // SECS_PER_MIN - second = seconds % SECS_PER_MIN - - return (year, month, day, hour, minute, second, microseconds) - - -def precise_diff( - d1, d2 -): # type: (typing.Union[datetime.datetime, datetime.date], typing.Union[datetime.datetime, datetime.date]) -> PreciseDiff - """ - Calculate a precise difference between two datetimes. - - :param d1: The first datetime - :type d1: datetime.datetime or datetime.date - - :param d2: The second datetime - :type d2: datetime.datetime or datetime.date - - :rtype: PreciseDiff - """ - sign = 1 - - if d1 == d2: - return PreciseDiff(0, 0, 0, 0, 0, 0, 0, 0) - - tzinfo1 = d1.tzinfo if isinstance(d1, datetime.datetime) else None - tzinfo2 = d2.tzinfo if isinstance(d2, datetime.datetime) else None - - if ( - tzinfo1 is None - and tzinfo2 is not None - or tzinfo2 is None - and tzinfo1 is not None - ): - raise ValueError( - "Comparison between naive and aware datetimes is not supported" - ) - - if d1 > d2: - d1, d2 = d2, d1 - sign = -1 - - d_diff = 0 - hour_diff = 0 - min_diff = 0 - sec_diff = 0 - mic_diff = 0 - total_days = _day_number(d2.year, d2.month, d2.day) - _day_number( - d1.year, d1.month, d1.day - ) - in_same_tz = False - tz1 = None - tz2 = None - - # Trying to figure out the timezone names - # If we can't find them, we assume different timezones - if tzinfo1 and tzinfo2: - if hasattr(tzinfo1, "name"): - # Pendulum timezone - tz1 = tzinfo1.name - elif hasattr(tzinfo1, "zone"): - # pytz timezone - tz1 = tzinfo1.zone - - if hasattr(tzinfo2, "name"): - tz2 = tzinfo2.name - elif hasattr(tzinfo2, "zone"): - tz2 = tzinfo2.zone - - in_same_tz = tz1 == tz2 and tz1 is not None - - if isinstance(d2, datetime.datetime): - if isinstance(d1, datetime.datetime): - # If we are not in the same timezone - # we need to adjust - # - # We also need to adjust if we do not - # have variable-length units - if not in_same_tz or total_days == 0: - offset1 = d1.utcoffset() - offset2 = d2.utcoffset() - - if offset1: - d1 = d1 - offset1 - - if offset2: - d2 = d2 - offset2 - - hour_diff = d2.hour - d1.hour - min_diff = d2.minute - d1.minute - sec_diff = d2.second - d1.second - mic_diff = d2.microsecond - d1.microsecond - else: - hour_diff = d2.hour - min_diff = d2.minute - sec_diff = d2.second - mic_diff = d2.microsecond - - if mic_diff < 0: - mic_diff += 1000000 - sec_diff -= 1 - - if sec_diff < 0: - sec_diff += 60 - min_diff -= 1 - - if min_diff < 0: - min_diff += 60 - hour_diff -= 1 - - if hour_diff < 0: - hour_diff += 24 - d_diff -= 1 - - y_diff = d2.year - d1.year - m_diff = d2.month - d1.month - d_diff += d2.day - d1.day - - if d_diff < 0: - year = d2.year - month = d2.month - - if month == 1: - month = 12 - year -= 1 - else: - month -= 1 - - leap = int(is_leap(year)) - - days_in_last_month = DAYS_PER_MONTHS[leap][month] - days_in_month = DAYS_PER_MONTHS[int(is_leap(d2.year))][d2.month] - - if d_diff < days_in_month - days_in_last_month: - # We don't have a full month, we calculate days - if days_in_last_month < d1.day: - d_diff += d1.day - else: - d_diff += days_in_last_month - elif d_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 - d_diff = 0 - m_diff += 1 - else: - # We have a full month - d_diff += days_in_last_month - - m_diff -= 1 - - if m_diff < 0: - m_diff += 12 - y_diff -= 1 - - return PreciseDiff( - sign * y_diff, - sign * m_diff, - sign * d_diff, - sign * hour_diff, - sign * min_diff, - sign * sec_diff, - sign * mic_diff, - sign * total_days, - ) - - -def _day_number(year, month, day): # type: (int, int, int) -> int - month = (month + 9) % 12 - year = year - month // 10 - - return ( - 365 * year - + year // 4 - - year // 100 - + year // 400 - + (month * 306 + 5) // 10 - + (day - 1) - ) +from __future__ import annotations + +import datetime +import math + +from collections import namedtuple +from typing import cast + +from pendulum.constants import DAY_OF_WEEK_TABLE +from pendulum.constants import DAYS_PER_L_YEAR +from pendulum.constants import DAYS_PER_MONTHS +from pendulum.constants import DAYS_PER_N_YEAR +from pendulum.constants import EPOCH_YEAR +from pendulum.constants import MONTHS_OFFSETS +from pendulum.constants import SECS_PER_4_YEARS +from pendulum.constants import SECS_PER_100_YEARS +from pendulum.constants import SECS_PER_400_YEARS +from pendulum.constants import SECS_PER_DAY +from pendulum.constants import SECS_PER_HOUR +from pendulum.constants import SECS_PER_MIN +from pendulum.constants import SECS_PER_YEAR +from pendulum.constants import TM_DECEMBER +from pendulum.constants import TM_JANUARY +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", + ) +): + years: int + months: int + days: int + hours: int + minutes: int + seconds: int + microseconds: int + total_days: int + + def __repr__(self) -> str: + return ( + f"{self.years} years " + f"{self.months} months " + f"{self.days} days " + f"{self.hours} hours " + f"{self.minutes} minutes " + f"{self.seconds} seconds " + f"{self.microseconds} microseconds" + ) + + +def is_leap(year: int) -> bool: + return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) + + +def is_long_year(year: int) -> bool: + def p(y: int) -> int: + return y + y // 4 - y // 100 + y // 400 + + return p(year) % 7 == 4 or p(year - 1) % 7 == 3 + + +def week_day(year: int, month: int, day: int) -> int: + if month < 3: + year -= 1 + + w = ( + year + + year // 4 + - year // 100 + + year // 400 + + DAY_OF_WEEK_TABLE[month - 1] + + day + ) % 7 + + if not w: + w = 7 + + return w + + +def days_in_year(year: int) -> int: + if is_leap(year): + return DAYS_PER_L_YEAR + + 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]: + """ + Returns a UNIX time as a broken-down time + for a particular transition type. + """ + year = EPOCH_YEAR + seconds = int(math.floor(unix_time)) + + # Shift to a base year that is 400-year aligned. + if seconds >= 0: + seconds -= 10957 * SECS_PER_DAY + year += 30 # == 2000 + else: + seconds += (146097 - 10957) * 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, seconds and microseconds + hour = seconds // SECS_PER_HOUR + seconds %= SECS_PER_HOUR + minute = seconds // SECS_PER_MIN + second = seconds % SECS_PER_MIN + + return year, month, day, hour, minute, second, microseconds + + +def precise_diff( + d1: datetime.datetime | datetime.date, d2: datetime.datetime | datetime.date +) -> PreciseDiff: + """ + Calculate a precise difference between two datetimes. + + :param d1: The first datetime + :param d2: The second datetime + """ + sign = 1 + + if d1 == d2: + return PreciseDiff(0, 0, 0, 0, 0, 0, 0, 0) + + tzinfo1: datetime.tzinfo | None = ( + d1.tzinfo if isinstance(d1, datetime.datetime) else None + ) + tzinfo2: datetime.tzinfo | None = ( + d2.tzinfo if isinstance(d2, datetime.datetime) else None + ) + + if ( + tzinfo1 is None + and tzinfo2 is not None + or tzinfo2 is None + and tzinfo1 is not None + ): + raise ValueError( + "Comparison between naive and aware datetimes is not supported" + ) + + if d1 > d2: + d1, d2 = d2, d1 + sign = -1 + + d_diff = 0 + hour_diff = 0 + min_diff = 0 + sec_diff = 0 + mic_diff = 0 + total_days = _day_number(d2.year, d2.month, d2.day) - _day_number( + d1.year, d1.month, d1.day + ) + in_same_tz = False + tz1 = None + tz2 = None + + # Trying to figure out the timezone names + # If we can't find them, we assume different timezones + if tzinfo1 and tzinfo2: + tz1 = _get_tzinfo_name(tzinfo1) + tz2 = _get_tzinfo_name(tzinfo2) + + in_same_tz = tz1 == tz2 and tz1 is not None + + if isinstance(d2, datetime.datetime): + if isinstance(d1, datetime.datetime): + # If we are not in the same timezone + # we need to adjust + # + # We also need to adjust if we do not + # have variable-length units + if not in_same_tz or total_days == 0: + offset1 = d1.utcoffset() + offset2 = d2.utcoffset() + + if offset1: + d1 = d1 - offset1 + + if offset2: + d2 = d2 - offset2 + + hour_diff = d2.hour - d1.hour + min_diff = d2.minute - d1.minute + sec_diff = d2.second - d1.second + mic_diff = d2.microsecond - d1.microsecond + else: + hour_diff = d2.hour + min_diff = d2.minute + sec_diff = d2.second + mic_diff = d2.microsecond + + if mic_diff < 0: + mic_diff += 1000000 + sec_diff -= 1 + + if sec_diff < 0: + sec_diff += 60 + min_diff -= 1 + + if min_diff < 0: + min_diff += 60 + hour_diff -= 1 + + if hour_diff < 0: + hour_diff += 24 + d_diff -= 1 + + y_diff = d2.year - d1.year + m_diff = d2.month - d1.month + d_diff += d2.day - d1.day + + if d_diff < 0: + year = d2.year + month = d2.month + + if month == 1: + month = 12 + year -= 1 + else: + month -= 1 + + leap = int(is_leap(year)) + + days_in_last_month = DAYS_PER_MONTHS[leap][month] + days_in_month = DAYS_PER_MONTHS[int(is_leap(d2.year))][d2.month] + + if d_diff < days_in_month - days_in_last_month: + # We don't have a full month, we calculate days + if days_in_last_month < d1.day: + d_diff += d1.day + else: + d_diff += days_in_last_month + elif d_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 + d_diff = 0 + m_diff += 1 + else: + # We have a full month + d_diff += days_in_last_month + + m_diff -= 1 + + if m_diff < 0: + m_diff += 12 + y_diff -= 1 + + return PreciseDiff( + sign * y_diff, + sign * m_diff, + sign * d_diff, + sign * hour_diff, + sign * min_diff, + sign * sec_diff, + sign * mic_diff, + sign * total_days, + ) + + +def _day_number(year: int, month: int, day: int) -> int: + month = (month + 9) % 12 + year = year - month // 10 + + return ( + 365 * year + + year // 4 + - year // 100 + + year // 400 + + (month * 306 + 5) // 10 + + (day - 1) + ) + + +def _get_tzinfo_name(tzinfo: datetime.tzinfo | None) -> str | None: + if tzinfo is None: + return None + + if hasattr(tzinfo, "key"): + # zoneinfo timezone + return cast(str, 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 None diff --git a/pendulum/constants.py b/pendulum/constants.py index 3712df3..a3d2a18 100644 --- a/pendulum/constants.py +++ b/pendulum/constants.py @@ -1,107 +1,109 @@ -# The day constants -SUNDAY = 0 -MONDAY = 1 -TUESDAY = 2 -WEDNESDAY = 3 -THURSDAY = 4 -FRIDAY = 5 -SATURDAY = 6 - -# Number of X in Y. -YEARS_PER_CENTURY = 100 -YEARS_PER_DECADE = 10 -MONTHS_PER_YEAR = 12 -WEEKS_PER_YEAR = 52 -DAYS_PER_WEEK = 7 -HOURS_PER_DAY = 24 -MINUTES_PER_HOUR = 60 -SECONDS_PER_MINUTE = 60 -SECONDS_PER_HOUR = MINUTES_PER_HOUR * SECONDS_PER_MINUTE -SECONDS_PER_DAY = HOURS_PER_DAY * SECONDS_PER_HOUR -US_PER_SECOND = 1000000 - -# Formats -ATOM = "YYYY-MM-DDTHH:mm:ssZ" -COOKIE = "dddd, DD-MMM-YYYY HH:mm:ss zz" -ISO8601 = "YYYY-MM-DDTHH:mm:ssZ" -ISO8601_EXTENDED = "YYYY-MM-DDTHH:mm:ss.SSSSSSZ" -RFC822 = "ddd, DD MMM YY HH:mm:ss ZZ" -RFC850 = "dddd, DD-MMM-YY HH:mm:ss zz" -RFC1036 = "ddd, DD MMM YY HH:mm:ss ZZ" -RFC1123 = "ddd, DD MMM YYYY HH:mm:ss ZZ" -RFC2822 = "ddd, DD MMM YYYY HH:mm:ss ZZ" -RFC3339 = ISO8601 -RFC3339_EXTENDED = ISO8601_EXTENDED -RSS = "ddd, DD MMM YYYY HH:mm:ss ZZ" -W3C = ISO8601 - - -EPOCH_YEAR = 1970 - -DAYS_PER_N_YEAR = 365 -DAYS_PER_L_YEAR = 366 - -USECS_PER_SEC = 1000000 - -SECS_PER_MIN = 60 -SECS_PER_HOUR = 60 * SECS_PER_MIN -SECS_PER_DAY = SECS_PER_HOUR * 24 - -# 400-year chunks always have 146097 days (20871 weeks). -SECS_PER_400_YEARS = 146097 * 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. -SECS_PER_100_YEARS = ( - (76 * DAYS_PER_N_YEAR + 24 * DAYS_PER_L_YEAR) * SECS_PER_DAY, - (75 * DAYS_PER_N_YEAR + 25 * 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. -SECS_PER_4_YEARS = ( - (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. -SECS_PER_YEAR = (DAYS_PER_N_YEAR * SECS_PER_DAY, DAYS_PER_L_YEAR * SECS_PER_DAY) - -DAYS_PER_YEAR = (DAYS_PER_N_YEAR, DAYS_PER_L_YEAR) - -# The month lengths in non-leap and leap years respectively. -DAYS_PER_MONTHS = ( - (-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. -MONTHS_OFFSETS = ( - (-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), -) - -DAY_OF_WEEK_TABLE = (0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4) - -TM_SUNDAY = 0 -TM_MONDAY = 1 -TM_TUESDAY = 2 -TM_WEDNESDAY = 3 -TM_THURSDAY = 4 -TM_FRIDAY = 5 -TM_SATURDAY = 6 - -TM_JANUARY = 0 -TM_FEBRUARY = 1 -TM_MARCH = 2 -TM_APRIL = 3 -TM_MAY = 4 -TM_JUNE = 5 -TM_JULY = 6 -TM_AUGUST = 7 -TM_SEPTEMBER = 8 -TM_OCTOBER = 9 -TM_NOVEMBER = 10 -TM_DECEMBER = 11 +# 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 +YEARS_PER_DECADE = 10 +MONTHS_PER_YEAR = 12 +WEEKS_PER_YEAR = 52 +DAYS_PER_WEEK = 7 +HOURS_PER_DAY = 24 +MINUTES_PER_HOUR = 60 +SECONDS_PER_MINUTE = 60 +SECONDS_PER_HOUR = MINUTES_PER_HOUR * SECONDS_PER_MINUTE +SECONDS_PER_DAY = HOURS_PER_DAY * SECONDS_PER_HOUR +US_PER_SECOND = 1000000 + +# Formats +ATOM = "YYYY-MM-DDTHH:mm:ssZ" +COOKIE = "dddd, DD-MMM-YYYY HH:mm:ss zz" +ISO8601 = "YYYY-MM-DDTHH:mm:ssZ" +ISO8601_EXTENDED = "YYYY-MM-DDTHH:mm:ss.SSSSSSZ" +RFC822 = "ddd, DD MMM YY HH:mm:ss ZZ" +RFC850 = "dddd, DD-MMM-YY HH:mm:ss zz" +RFC1036 = "ddd, DD MMM YY HH:mm:ss ZZ" +RFC1123 = "ddd, DD MMM YYYY HH:mm:ss ZZ" +RFC2822 = "ddd, DD MMM YYYY HH:mm:ss ZZ" +RFC3339 = ISO8601 +RFC3339_EXTENDED = ISO8601_EXTENDED +RSS = "ddd, DD MMM YYYY HH:mm:ss ZZ" +W3C = ISO8601 + + +EPOCH_YEAR = 1970 + +DAYS_PER_N_YEAR = 365 +DAYS_PER_L_YEAR = 366 + +USECS_PER_SEC = 1000000 + +SECS_PER_MIN = 60 +SECS_PER_HOUR = 60 * SECS_PER_MIN +SECS_PER_DAY = SECS_PER_HOUR * 24 + +# 400-year chunks always have 146097 days (20871 weeks). +SECS_PER_400_YEARS = 146097 * 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. +SECS_PER_100_YEARS = ( + (76 * DAYS_PER_N_YEAR + 24 * DAYS_PER_L_YEAR) * SECS_PER_DAY, + (75 * DAYS_PER_N_YEAR + 25 * 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. +SECS_PER_4_YEARS = ( + (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. +SECS_PER_YEAR = (DAYS_PER_N_YEAR * SECS_PER_DAY, DAYS_PER_L_YEAR * SECS_PER_DAY) + +DAYS_PER_YEAR = (DAYS_PER_N_YEAR, DAYS_PER_L_YEAR) + +# The month lengths in non-leap and leap years respectively. +DAYS_PER_MONTHS = ( + (-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. +MONTHS_OFFSETS = ( + (-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), +) + +DAY_OF_WEEK_TABLE = (0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4) + +TM_SUNDAY = 0 +TM_MONDAY = 1 +TM_TUESDAY = 2 +TM_WEDNESDAY = 3 +TM_THURSDAY = 4 +TM_FRIDAY = 5 +TM_SATURDAY = 6 + +TM_JANUARY = 0 +TM_FEBRUARY = 1 +TM_MARCH = 2 +TM_APRIL = 3 +TM_MAY = 4 +TM_JUNE = 5 +TM_JULY = 6 +TM_AUGUST = 7 +TM_SEPTEMBER = 8 +TM_OCTOBER = 9 +TM_NOVEMBER = 10 +TM_DECEMBER = 11 diff --git a/pendulum/date.py b/pendulum/date.py index 41a9883..4e2655b 100644 --- a/pendulum/date.py +++ b/pendulum/date.py @@ -1,891 +1,760 @@ -from __future__ import absolute_import -from __future__ import division - -import calendar -import math - -from datetime import date -from datetime import timedelta - -import pendulum - -from .constants import FRIDAY -from .constants import MONDAY -from .constants import MONTHS_PER_YEAR -from .constants import SATURDAY -from .constants import SUNDAY -from .constants import THURSDAY -from .constants import TUESDAY -from .constants import WEDNESDAY -from .constants import YEARS_PER_CENTURY -from .constants import YEARS_PER_DECADE -from .exceptions import PendulumException -from .helpers import add_duration -from .mixins.default import FormattableMixin -from .period import Period - - -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"] - - # Getters/Setters - - def set(self, year=None, month=None, day=None): - return self.replace(year=year, month=month, day=day) - - @property - def day_of_week(self): - """ - Returns the day of the week (0-6). - - :rtype: int - """ - return self.isoweekday() % 7 - - @property - def day_of_year(self): - """ - Returns the day of the year (1-366). - - :rtype: int - """ - k = 1 if self.is_leap_year() else 2 - - return (275 * self.month) // 9 - k * ((self.month + 9) // 12) + self.day - 30 - - @property - def week_of_year(self): - return self.isocalendar()[1] - - @property - def days_in_month(self): - return calendar.monthrange(self.year, self.month)[1] - - @property - def week_of_month(self): - first_day_of_month = self.replace(day=1) - - return self.week_of_year - first_day_of_month.week_of_year + 1 - - @property - def age(self): - return self.diff(abs=False).in_years() - - @property - def quarter(self): - return int(math.ceil(self.month / 3)) - - # String Formatting - - def to_date_string(self): - """ - Format the instance as date. - - :rtype: str - """ - return self.strftime("%Y-%m-%d") - - def to_formatted_date_string(self): - """ - Format the instance as a readable date. - - :rtype: str - """ - return self.strftime("%b %d, %Y") - - def __repr__(self): - return ( - "{klass}(" - "{year}, {month}, {day}" - ")".format( - klass=self.__class__.__name__, - year=self.year, - month=self.month, - day=self.day, - ) - ) - - # COMPARISONS - - def closest(self, dt1, dt2): - """ - Get the closest date from the instance. - - :type dt1: Date or date - :type dt2: Date or date - - :rtype: Date - """ - dt1 = self.__class__(dt1.year, dt1.month, dt1.day) - dt2 = self.__class__(dt2.year, dt2.month, dt2.day) - - if self.diff(dt1).in_seconds() < self.diff(dt2).in_seconds(): - return dt1 - - return dt2 - - def farthest(self, dt1, dt2): - """ - Get the farthest date from the instance. - - :type dt1: Date or date - :type dt2: Date or date - - :rtype: Date - """ - dt1 = self.__class__(dt1.year, dt1.month, dt1.day) - dt2 = self.__class__(dt2.year, dt2.month, dt2.day) - - if self.diff(dt1).in_seconds() > self.diff(dt2).in_seconds(): - return dt1 - - return dt2 - - def is_future(self): - """ - Determines if the instance is in the future, ie. greater than now. - - :rtype: bool - """ - return self > self.today() - - def is_past(self): - """ - Determines if the instance is in the past, ie. less than now. - - :rtype: bool - """ - return self < self.today() - - def is_leap_year(self): - """ - Determines if the instance is a leap year. - - :rtype: bool - """ - return calendar.isleap(self.year) - - def is_long_year(self): - """ - Determines if the instance is a long year - - See link ``_ - - :rtype: bool - """ - return Date(self.year, 12, 28).isocalendar()[1] == 53 - - def is_same_day(self, dt): - """ - Checks if the passed in date is the same day as the instance current day. - - :type dt: Date or date - - :rtype: bool - """ - return self == dt - - def is_anniversary(self, dt=None): - """ - Check if its the anniversary. - - Compares the date/month values of the two dates. - - :rtype: bool - """ - if dt is None: - dt = Date.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 - is_birthday = is_anniversary - - # ADDITIONS AND SUBSTRACTIONS - - def add(self, years=0, months=0, weeks=0, days=0): - """ - Add duration to the instance. - - :param years: The number of years - :type years: int - - :param months: The number of months - :type months: int - - :param weeks: The number of weeks - :type weeks: int - - :param days: The number of days - :type days: int - - :rtype: Date - """ - dt = add_duration( - date(self.year, self.month, self.day), - years=years, - months=months, - weeks=weeks, - days=days, - ) - - return self.__class__(dt.year, dt.month, dt.day) - - def subtract(self, years=0, months=0, weeks=0, days=0): - """ - Remove duration from the instance. - - :param years: The number of years - :type years: int - - :param months: The number of months - :type months: int - - :param weeks: The number of weeks - :type weeks: int - - :param days: The number of days - :type days: int - - :rtype: Date - """ - return self.add(years=-years, months=-months, weeks=-weeks, days=-days) - - def _add_timedelta(self, delta): - """ - Add timedelta duration to the instance. - - :param delta: The timedelta instance - :type delta: pendulum.Duration or datetime.timedelta - - :rtype: Date - """ - if isinstance(delta, pendulum.Duration): - return self.add( - years=delta.years, - months=delta.months, - weeks=delta.weeks, - days=delta.remaining_days, - ) - - return self.add(days=delta.days) - - def _subtract_timedelta(self, delta): - """ - Remove timedelta duration from the instance. - - :param delta: The timedelta instance - :type delta: pendulum.Duration or datetime.timedelta - - :rtype: Date - """ - if isinstance(delta, pendulum.Duration): - return self.subtract( - years=delta.years, - months=delta.months, - weeks=delta.weeks, - days=delta.remaining_days, - ) - - return self.subtract(days=delta.days) - - def __add__(self, other): - if not isinstance(other, timedelta): - return NotImplemented - - return self._add_timedelta(other) - - def __sub__(self, other): - if isinstance(other, timedelta): - return self._subtract_timedelta(other) - - if not isinstance(other, date): - return NotImplemented - - dt = self.__class__(other.year, other.month, other.day) - - return dt.diff(self, False) - - # DIFFERENCES - - def diff(self, dt=None, abs=True): - """ - Returns the difference between two Date objects as a Period. - - :type dt: Date or None - - :param abs: Whether to return an absolute interval or not - :type abs: bool - - :rtype: Period - """ - if dt is None: - dt = self.today() - - return Period(self, Date(dt.year, dt.month, dt.day), absolute=abs) - - def diff_for_humans(self, other=None, absolute=False, locale=None): - """ - Get the difference in a human readable format in the current locale. - - When comparing a value in the past to default now: - 1 day ago - 5 months ago - - When comparing a value in the future to default now: - 1 day from now - 5 months from now - - When comparing a value in the past to another value: - 1 day before - 5 months before - - When comparing a value in the future to another value: - 1 day after - 5 months after - - :type other: Date - - :param absolute: removes time difference modifiers ago, after, etc - :type absolute: bool - - :param locale: The locale to use for localization - :type locale: str - - :rtype: str - """ - is_now = other is None - - if is_now: - other = self.today() - - diff = self.diff(other) - - return pendulum.format_diff(diff, is_now, absolute, locale) - - # MODIFIERS - - def start_of(self, unit): - """ - Returns a copy of the instance with the time reset - with the following rules: - - * day: time to 00:00:00 - * week: date to first day of the week and time to 00:00:00 - * month: date to first day of the month and time to 00:00:00 - * year: date to first day of the year and time to 00:00:00 - * decade: date to first day of the decade and time to 00:00:00 - * century: date to first day of century and time to 00:00:00 - - :param unit: The unit to reset to - :type unit: str - - :rtype: Date - """ - if unit not in self._MODIFIERS_VALID_UNITS: - raise ValueError('Invalid unit "{}" for start_of()'.format(unit)) - - return getattr(self, "_start_of_{}".format(unit))() - - def end_of(self, unit): - """ - Returns a copy of the instance with the time reset - with the following rules: - - * week: date to last day of the week - * month: date to last day of the month - * year: date to last day of the year - * decade: date to last day of the decade - * century: date to last day of century - - :param unit: The unit to reset to - :type unit: str - - :rtype: Date - """ - if unit not in self._MODIFIERS_VALID_UNITS: - raise ValueError('Invalid unit "%s" for end_of()' % unit) - - return getattr(self, "_end_of_%s" % unit)() - - def _start_of_day(self): - """ - Compatibility method. - - :rtype: Date - """ - return self - - def _end_of_day(self): - """ - Compatibility method - - :rtype: Date - """ - return self - - def _start_of_month(self): - """ - Reset the date to the first day of the month. - - :rtype: Date - """ - return self.set(self.year, self.month, 1) - - def _end_of_month(self): - """ - Reset the date to the last day of the month. - - :rtype: Date - """ - return self.set(self.year, self.month, self.days_in_month) - - def _start_of_year(self): - """ - Reset the date to the first day of the year. - - :rtype: Date - """ - return self.set(self.year, 1, 1) - - def _end_of_year(self): - """ - Reset the date to the last day of the year. - - :rtype: Date - """ - return self.set(self.year, 12, 31) - - def _start_of_decade(self): - """ - Reset the date to the first day of the decade. - - :rtype: Date - """ - year = self.year - self.year % YEARS_PER_DECADE - - return self.set(year, 1, 1) - - def _end_of_decade(self): - """ - Reset the date to the last day of the decade. - - :rtype: Date - """ - year = self.year - self.year % YEARS_PER_DECADE + YEARS_PER_DECADE - 1 - - return self.set(year, 12, 31) - - def _start_of_century(self): - """ - Reset the date to the first day of the century. - - :rtype: Date - """ - year = self.year - 1 - (self.year - 1) % YEARS_PER_CENTURY + 1 - - return self.set(year, 1, 1) - - def _end_of_century(self): - """ - Reset the date to the last day of the century. - - :rtype: Date - """ - year = self.year - 1 - (self.year - 1) % YEARS_PER_CENTURY + YEARS_PER_CENTURY - - return self.set(year, 12, 31) - - def _start_of_week(self): - """ - Reset the date to the first day of the week. - - :rtype: Date - """ - dt = self - - if self.day_of_week != pendulum._WEEK_STARTS_AT: - dt = self.previous(pendulum._WEEK_STARTS_AT) - - return dt.start_of("day") - - def _end_of_week(self): - """ - Reset the date to the last day of the week. - - :rtype: Date - """ - dt = self - - if self.day_of_week != pendulum._WEEK_ENDS_AT: - dt = self.next(pendulum._WEEK_ENDS_AT) - - return dt.end_of("day") - - def next(self, day_of_week=None): - """ - Modify to the next occurrence of a given day of the week. - If no day_of_week is provided, modify to the next occurrence - of the current day of the week. Use the supplied consts - to indicate the desired day_of_week, ex. pendulum.MONDAY. - - :param day_of_week: The next day of week to reset to. - :type day_of_week: int or None - - :rtype: Date - """ - if day_of_week is None: - day_of_week = self.day_of_week - - if day_of_week < SUNDAY or day_of_week > SATURDAY: - raise ValueError("Invalid day of week") - - dt = self.add(days=1) - while dt.day_of_week != day_of_week: - dt = dt.add(days=1) - - return dt - - def previous(self, day_of_week=None): - """ - Modify to the previous occurrence of a given day of the week. - If no day_of_week is provided, modify to the previous occurrence - of the current day of the week. Use the supplied consts - to indicate the desired day_of_week, ex. pendulum.MONDAY. - - :param day_of_week: The previous day of week to reset to. - :type day_of_week: int or None - - :rtype: Date - """ - if day_of_week is None: - day_of_week = self.day_of_week - - if day_of_week < SUNDAY or day_of_week > SATURDAY: - raise ValueError("Invalid day of week") - - dt = self.subtract(days=1) - while dt.day_of_week != day_of_week: - dt = dt.subtract(days=1) - - return dt - - def first_of(self, unit, day_of_week=None): - """ - 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. - - Supported units are month, quarter and year. - - :param unit: The unit to use - :type unit: str - - :type day_of_week: int or None - - :rtype: Date - """ - if unit not in ["month", "quarter", "year"]: - raise ValueError('Invalid unit "{}" for first_of()'.format(unit)) - - return getattr(self, "_first_of_{}".format(unit))(day_of_week) - - def last_of(self, unit, day_of_week=None): - """ - 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. - - Supported units are month, quarter and year. - - :param unit: The unit to use - :type unit: str - - :type day_of_week: int or None - - :rtype: Date - """ - if unit not in ["month", "quarter", "year"]: - raise ValueError('Invalid unit "{}" for first_of()'.format(unit)) - - return getattr(self, "_last_of_{}".format(unit))(day_of_week) - - def nth_of(self, unit, nth, day_of_week): - """ - Returns a new instance set to the given occurrence - of a given day of the week in the current unit. - If the calculated occurrence is outside the scope of the current unit, - then raise an error. Use the supplied consts - to indicate the desired day_of_week, ex. pendulum.MONDAY. - - Supported units are month, quarter and year. - - :param unit: The unit to use - :type unit: str - - :type nth: int - - :type day_of_week: int or None - - :rtype: Date - """ - if unit not in ["month", "quarter", "year"]: - raise ValueError('Invalid unit "{}" for first_of()'.format(unit)) - - dt = getattr(self, "_nth_of_{}".format(unit))(nth, day_of_week) - if dt is False: - raise PendulumException( - "Unable to find occurence {} of {} in {}".format( - nth, self._days[day_of_week], unit - ) - ) - - return dt - - def _first_of_month(self, day_of_week): - """ - Modify to the first occurrence of a given day of the week - in the current month. If no day_of_week is provided, - modify to the first day of the month. Use the supplied consts - to indicate the desired day_of_week, ex. pendulum.MONDAY. - - :type day_of_week: int - - :rtype: Date - """ - dt = self - - if day_of_week is None: - return dt.set(day=1) - - month = calendar.monthcalendar(dt.year, dt.month) - - calendar_day = (day_of_week - 1) % 7 - - if month[0][calendar_day] > 0: - day_of_month = month[0][calendar_day] - else: - day_of_month = month[1][calendar_day] - - return dt.set(day=day_of_month) - - def _last_of_month(self, day_of_week=None): - """ - Modify to the last occurrence of a given day of the week - in the current month. If no day_of_week is provided, - modify to the last day of the month. Use the supplied consts - to indicate the desired day_of_week, ex. pendulum.MONDAY. - - :type day_of_week: int or None - - :rtype: Date - """ - dt = self - - if day_of_week is None: - return dt.set(day=self.days_in_month) - - month = calendar.monthcalendar(dt.year, dt.month) - - calendar_day = (day_of_week - 1) % 7 - - if month[-1][calendar_day] > 0: - day_of_month = month[-1][calendar_day] - else: - day_of_month = month[-2][calendar_day] - - return dt.set(day=day_of_month) - - def _nth_of_month(self, nth, day_of_week): - """ - Modify to the given occurrence of a given day of the week - in the current month. If the calculated occurrence is outside, - the scope of the current month, then return False and no - modifications are made. Use the supplied consts - to indicate the desired day_of_week, ex. pendulum.MONDAY. - - :type nth: int - - :type day_of_week: int or None - - :rtype: Date - """ - if nth == 1: - return self.first_of("month", day_of_week) - - dt = self.first_of("month") - check = dt.format("YYYY-MM") - for i in range(nth - (1 if dt.day_of_week == day_of_week else 0)): - dt = dt.next(day_of_week) - - if dt.format("YYYY-MM") == check: - return self.set(day=dt.day) - - return False - - def _first_of_quarter(self, day_of_week=None): - """ - Modify to the first occurrence of a given day of the week - in the current quarter. If no day_of_week is provided, - modify to the first day of the quarter. Use the supplied consts - to indicate the desired day_of_week, ex. pendulum.MONDAY. - - :type day_of_week: int or None - - :rtype: Date - """ - return self.set(self.year, self.quarter * 3 - 2, 1).first_of( - "month", day_of_week - ) - - def _last_of_quarter(self, day_of_week=None): - """ - Modify to the last occurrence of a given day of the week - in the current quarter. If no day_of_week is provided, - modify to the last day of the quarter. Use the supplied consts - to indicate the desired day_of_week, ex. pendulum.MONDAY. - - :type day_of_week: int or None - - :rtype: Date - """ - return self.set(self.year, self.quarter * 3, 1).last_of("month", day_of_week) - - def _nth_of_quarter(self, nth, day_of_week): - """ - Modify to the given occurrence of a given day of the week - in the current quarter. If the calculated occurrence is outside, - the scope of the current quarter, then return False and no - modifications are made. Use the supplied consts - to indicate the desired day_of_week, ex. pendulum.MONDAY. - - :type nth: int - - :type day_of_week: int or None - - :rtype: Date - """ - if nth == 1: - return self.first_of("quarter", day_of_week) - - dt = self.replace(self.year, self.quarter * 3, 1) - last_month = dt.month - year = dt.year - dt = dt.first_of("quarter") - for i in range(nth - (1 if dt.day_of_week == day_of_week else 0)): - dt = dt.next(day_of_week) - - if last_month < dt.month or year != dt.year: - return False - - return self.set(self.year, dt.month, dt.day) - - def _first_of_year(self, day_of_week=None): - """ - Modify to the first occurrence of a given day of the week - in the current year. If no day_of_week is provided, - modify to the first day of the year. Use the supplied consts - to indicate the desired day_of_week, ex. pendulum.MONDAY. - - :type day_of_week: int or None - - :rtype: Date - """ - return self.set(month=1).first_of("month", day_of_week) - - def _last_of_year(self, day_of_week=None): - """ - Modify to the last occurrence of a given day of the week - in the current year. If no day_of_week is provided, - modify to the last day of the year. Use the supplied consts - to indicate the desired day_of_week, ex. pendulum.MONDAY. - - :type day_of_week: int or None - - :rtype: Date - """ - return self.set(month=MONTHS_PER_YEAR).last_of("month", day_of_week) - - def _nth_of_year(self, nth, day_of_week): - """ - Modify to the given occurrence of a given day of the week - in the current year. If the calculated occurrence is outside, - the scope of the current year, then return False and no - modifications are made. Use the supplied consts - to indicate the desired day_of_week, ex. pendulum.MONDAY. - - :type nth: int - - :type day_of_week: int or None - - :rtype: Date - """ - if nth == 1: - return self.first_of("year", day_of_week) - - dt = self.first_of("year") - year = dt.year - for i in range(nth - (1 if dt.day_of_week == day_of_week else 0)): - dt = dt.next(day_of_week) - - if year != dt.year: - return False - - return self.set(self.year, dt.month, dt.day) - - def average(self, dt=None): - """ - Modify the current instance to the average - of a given instance (default now) and the current instance. - - :type dt: Date or date - - :rtype: Date - """ - if dt is None: - dt = Date.today() - - return self.add(days=int(self.diff(dt, False).in_days() / 2)) - - # Native methods override - - @classmethod - def today(cls): - return pendulum.today().date() - - @classmethod - def fromtimestamp(cls, t): - dt = super(Date, cls).fromtimestamp(t) - - return cls(dt.year, dt.month, dt.day) - - @classmethod - def fromordinal(cls, n): - dt = super(Date, cls).fromordinal(n) - - return cls(dt.year, dt.month, dt.day) - - def replace(self, year=None, month=None, day=None): - 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 - - return self.__class__(year, month, day) +from __future__ import annotations + +import calendar +import math + +from datetime import date +from datetime import datetime +from datetime import timedelta +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.exceptions import PendulumException +from pendulum.helpers import add_duration +from pendulum.interval import Interval +from pendulum.mixins.default import FormattableMixin + + +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"] + + # Getters/Setters + + def set( + self, year: int | None = None, month: int | None = None, day: int | None = None + ) -> Date: + return self.replace(year=year, month=month, day=day) + + @property + def day_of_week(self) -> int: + """ + Returns the day of the week (0-6). + """ + return self.isoweekday() % 7 + + @property + def day_of_year(self) -> int: + """ + Returns the day of the year (1-366). + """ + k = 1 if self.is_leap_year() else 2 + + return (275 * self.month) // 9 - k * ((self.month + 9) // 12) + self.day - 30 + + @property + def week_of_year(self) -> int: + return self.isocalendar()[1] + + @property + def days_in_month(self) -> int: + return calendar.monthrange(self.year, self.month)[1] + + @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 + + @property + def age(self) -> int: + return self.diff(abs=False).in_years() + + @property + def quarter(self) -> int: + return int(math.ceil(self.month / 3)) + + # String Formatting + + def to_date_string(self) -> str: + """ + Format the instance as date. + + :rtype: str + """ + return self.strftime("%Y-%m-%d") + + def to_formatted_date_string(self) -> str: + """ + Format the instance as a readable date. + + :rtype: str + """ + return self.strftime("%b %d, %Y") + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.year}, {self.month}, {self.day})" + + # COMPARISONS + + def closest(self, dt1: date, dt2: date) -> Date: + """ + Get the closest date from the instance. + """ + dt1 = self.__class__(dt1.year, dt1.month, dt1.day) + dt2 = self.__class__(dt2.year, dt2.month, dt2.day) + + if self.diff(dt1).in_seconds() < self.diff(dt2).in_seconds(): + return dt1 + + return dt2 + + def farthest(self, dt1: date, dt2: date) -> Date: + """ + Get the farthest date from the instance. + """ + dt1 = self.__class__(dt1.year, dt1.month, dt1.day) + dt2 = self.__class__(dt2.year, dt2.month, dt2.day) + + if self.diff(dt1).in_seconds() > self.diff(dt2).in_seconds(): + return dt1 + + return dt2 + + def is_future(self) -> bool: + """ + Determines if the instance is in the future, ie. greater than now. + """ + return self > self.today() + + def is_past(self) -> bool: + """ + Determines if the instance is in the past, ie. less than now. + """ + return self < self.today() + + def is_leap_year(self) -> bool: + """ + Determines if the instance is a leap year. + """ + return calendar.isleap(self.year) + + def is_long_year(self) -> bool: + """ + Determines if the instance is a long year + + See link ``_ + """ + return Date(self.year, 12, 28).isocalendar()[1] == 53 + + def is_same_day(self, dt: date) -> bool: + """ + Checks if the passed in date is the same day as the instance current day. + """ + return self == dt + + def is_anniversary(self, dt: date | None = None) -> bool: + """ + Check if it's the anniversary. + + Compares the date/month values of the two dates. + """ + if dt is None: + dt = Date.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 + is_birthday = is_anniversary + + # ADDITIONS AND SUBTRACTIONS + + def add( + self, years: int = 0, months: int = 0, weeks: int = 0, days: int = 0 + ) -> Date: + """ + Add duration to the instance. + + :param years: The number of years + :param months: The number of months + :param weeks: The number of weeks + :param days: The number of days + """ + dt = add_duration( + date(self.year, self.month, self.day), + years=years, + months=months, + weeks=weeks, + days=days, + ) + + return self.__class__(dt.year, dt.month, dt.day) + + def subtract( + self, years: int = 0, months: int = 0, weeks: int = 0, days: int = 0 + ) -> Date: + """ + Remove duration from the instance. + + :param years: The number of years + :param months: The number of months + :param weeks: The number of weeks + :param days: The number of days + """ + return self.add(years=-years, months=-months, weeks=-weeks, days=-days) + + def _add_timedelta(self, delta: timedelta) -> Date: + """ + Add timedelta duration to the instance. + + :param delta: The timedelta instance + """ + if isinstance(delta, pendulum.Duration): + return self.add( + years=delta.years, + months=delta.months, + weeks=delta.weeks, + days=delta.remaining_days, + ) + + return self.add(days=delta.days) + + def _subtract_timedelta(self, delta: timedelta) -> Date: + """ + Remove timedelta duration from the instance. + + :param delta: The timedelta instance + """ + if isinstance(delta, pendulum.Duration): + return self.subtract( + years=delta.years, + months=delta.months, + weeks=delta.weeks, + days=delta.remaining_days, + ) + + return self.subtract(days=delta.days) + + def __add__(self, other: timedelta) -> Date: + if not isinstance(other, timedelta): + return NotImplemented + + return self._add_timedelta(other) + + @overload + def __sub__(self, delta: timedelta) -> Date: + ... + + @overload + def __sub__(self, dt: datetime) -> NoReturn: + ... + + @overload + def __sub__(self, dt: Date) -> Interval: + ... + + def __sub__(self, other: timedelta | date) -> Date | Interval: + if isinstance(other, timedelta): + return self._subtract_timedelta(other) + + if not isinstance(other, date): + return NotImplemented + + dt = self.__class__(other.year, other.month, other.day) + + return dt.diff(self, False) + + # DIFFERENCES + + def diff(self, dt: date | None = None, abs: bool = True) -> Interval: + """ + Returns the difference between two Date objects as an Interval. + + :param dt: The date to compare to (defaults to today) + :param abs: Whether to return an absolute interval or not + """ + if dt is None: + dt = self.today() + + return Interval(self, Date(dt.year, dt.month, dt.day), absolute=abs) + + def diff_for_humans( + self, + other: date | None = None, + absolute: bool = False, + locale: str | None = None, + ) -> str: + """ + Get the difference in a human readable format in the current locale. + + When comparing a value in the past to default now: + 1 day ago + 5 months ago + + When comparing a value in the future to default now: + 1 day from now + 5 months from now + + When comparing a value in the past to another value: + 1 day before + 5 months before + + When comparing a value in the future to another value: + 1 day after + 5 months after + + :param other: The date to compare to (defaults to today) + :param absolute: removes time difference modifiers ago, after, etc + :param locale: The locale to use for localization + """ + is_now = other is None + + if is_now: + other = self.today() + + diff = self.diff(other) + + return pendulum.format_diff(diff, is_now, absolute, locale) + + # MODIFIERS + + def start_of(self, unit: str) -> Date: + """ + Returns a copy of the instance with the time reset + with the following rules: + + * day: time to 00:00:00 + * week: date to first day of the week and time to 00:00:00 + * month: date to first day of the month and time to 00:00:00 + * year: date to first day of the year and time to 00:00:00 + * decade: date to first day of the decade and time to 00:00:00 + * century: date to first day of century and time to 00:00:00 + + :param unit: The unit to reset to + """ + 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}")()) + + def end_of(self, unit: str) -> Date: + """ + Returns a copy of the instance with the time reset + with the following rules: + + * week: date to last day of the week + * month: date to last day of the month + * year: date to last day of the year + * decade: date to last day of the decade + * century: date to last day of century + + :param unit: The unit to reset to + """ + 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}")()) + + def _start_of_day(self) -> Date: + """ + Compatibility method. + """ + return self + + def _end_of_day(self) -> Date: + """ + Compatibility method + """ + return self + + def _start_of_month(self) -> Date: + """ + Reset the date to the first day of the month. + """ + return self.set(self.year, self.month, 1) + + def _end_of_month(self) -> Date: + """ + 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: + """ + Reset the date to the first day of the year. + """ + return self.set(self.year, 1, 1) + + def _end_of_year(self) -> Date: + """ + Reset the date to the last day of the year. + """ + return self.set(self.year, 12, 31) + + def _start_of_decade(self) -> Date: + """ + Reset the date to the first day of the decade. + """ + year = self.year - self.year % YEARS_PER_DECADE + + return self.set(year, 1, 1) + + def _end_of_decade(self) -> Date: + """ + Reset the date to the last day of the decade. + """ + year = self.year - self.year % YEARS_PER_DECADE + YEARS_PER_DECADE - 1 + + return self.set(year, 12, 31) + + def _start_of_century(self) -> Date: + """ + Reset the date to the first day of the century. + """ + year = self.year - 1 - (self.year - 1) % YEARS_PER_CENTURY + 1 + + return self.set(year, 1, 1) + + def _end_of_century(self) -> Date: + """ + Reset the date to the last day of the century. + """ + year = self.year - 1 - (self.year - 1) % YEARS_PER_CENTURY + YEARS_PER_CENTURY + + return self.set(year, 12, 31) + + def _start_of_week(self) -> Date: + """ + Reset the date to the first day of the week. + """ + dt = self + + if self.day_of_week != pendulum._WEEK_STARTS_AT: + dt = self.previous(pendulum._WEEK_STARTS_AT) + + return dt.start_of("day") + + def _end_of_week(self) -> Date: + """ + Reset the date to the last day of the week. + """ + dt = self + + if self.day_of_week != pendulum._WEEK_ENDS_AT: + dt = self.next(pendulum._WEEK_ENDS_AT) + + return dt.end_of("day") + + def next(self, day_of_week: int | None = None) -> Date: + """ + Modify to the next occurrence of a given day of the week. + If no day_of_week is provided, modify to the next occurrence + of the current day of the week. Use the supplied consts + to indicate the desired day_of_week, ex. pendulum.MONDAY. + + :param day_of_week: The next day of week to reset to. + """ + if day_of_week is None: + day_of_week = self.day_of_week + + if day_of_week < SUNDAY or day_of_week > SATURDAY: + raise ValueError("Invalid day of week") + + dt = self.add(days=1) + while dt.day_of_week != day_of_week: + dt = dt.add(days=1) + + return dt + + def previous(self, day_of_week: int | None = None) -> Date: + """ + Modify to the previous occurrence of a given day of the week. + If no day_of_week is provided, modify to the previous occurrence + of the current day of the week. Use the supplied consts + to indicate the desired day_of_week, ex. pendulum.MONDAY. + + :param day_of_week: The previous day of week to reset to. + """ + if day_of_week is None: + day_of_week = self.day_of_week + + if day_of_week < SUNDAY or day_of_week > SATURDAY: + raise ValueError("Invalid day of week") + + dt = self.subtract(days=1) + while dt.day_of_week != day_of_week: + dt = dt.subtract(days=1) + + return dt + + def first_of(self, unit: str, day_of_week: int | None = None) -> Date: + """ + Returns an instance set to the first occurrence + of a given day of the week in the current unit. + If no day_of_week is provided, modify to the first day of the unit. + Use the supplied consts to indicate the desired day_of_week, ex. pendulum.MONDAY. + + Supported units are month, quarter and year. + + :param unit: The unit to use + :param day_of_week: The day of week to reset to. + """ + 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)) + + def last_of(self, unit: str, day_of_week: int | None = None) -> Date: + """ + Returns an instance set to the last occurrence + of a given day of the week in the current unit. + If no day_of_week is provided, modify to the last day of the unit. + Use the supplied consts to indicate the desired day_of_week, ex. pendulum.MONDAY. + + Supported units are month, quarter and year. + + :param unit: The unit to use + :param day_of_week: The day of week to reset to. + """ + 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)) + + def nth_of(self, unit: str, nth: int, day_of_week: int) -> Date: + """ + Returns a new instance set to the given occurrence + of a given day of the week in the current unit. + If the calculated occurrence is outside the scope of the current unit, + then raise an error. Use the supplied consts + to indicate the desired day_of_week, ex. pendulum.MONDAY. + + Supported units are month, quarter and year. + + :param unit: The unit to use + :param nth: The occurrence to use + :param day_of_week: The day of week to set to. + """ + 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)) + if not dt: + raise PendulumException( + f"Unable to find occurence {nth}" + f" of {self._days[day_of_week]} in {unit}" + ) + + return dt + + def _first_of_month(self, day_of_week: int) -> Date: + """ + Modify to the first occurrence of a given day of the week + in the current month. If no day_of_week is provided, + modify to the first day of the month. Use the supplied consts + to indicate the desired day_of_week, ex. pendulum.MONDAY. + + :param day_of_week: The day of week to set to. + """ + dt = self + + if day_of_week is None: + return dt.set(day=1) + + month = calendar.monthcalendar(dt.year, dt.month) + + calendar_day = (day_of_week - 1) % 7 + + if month[0][calendar_day] > 0: + day_of_month = month[0][calendar_day] + else: + day_of_month = month[1][calendar_day] + + return dt.set(day=day_of_month) + + def _last_of_month(self, day_of_week: int | None = None) -> Date: + """ + Modify to the last occurrence of a given day of the week + in the current month. If no day_of_week is provided, + modify to the last day of the month. Use the supplied consts + to indicate the desired day_of_week, ex. pendulum.MONDAY. + + :param day_of_week: The day of week to set to. + """ + dt = self + + if day_of_week is None: + return dt.set(day=self.days_in_month) + + month = calendar.monthcalendar(dt.year, dt.month) + + calendar_day = (day_of_week - 1) % 7 + + if month[-1][calendar_day] > 0: + day_of_month = month[-1][calendar_day] + else: + day_of_month = month[-2][calendar_day] + + return dt.set(day=day_of_month) + + def _nth_of_month(self, nth: int, day_of_week: int) -> Date | None: + """ + Modify to the given occurrence of a given day of the week + in the current month. If the calculated occurrence is outside, + the scope of the current month, then return False and no + modifications are made. Use the supplied consts + to indicate the desired day_of_week, ex. pendulum.MONDAY. + """ + if nth == 1: + return self.first_of("month", day_of_week) + + dt = self.first_of("month") + check = dt.format("YYYY-MM") + for _ in range(nth - (1 if dt.day_of_week == day_of_week else 0)): + dt = dt.next(day_of_week) + + if dt.format("YYYY-MM") == check: + return self.set(day=dt.day) + + return None + + def _first_of_quarter(self, day_of_week: int | None = None) -> Date: + """ + Modify to the first occurrence of a given day of the week + in the current quarter. If no day_of_week is provided, + modify to the first day of the quarter. Use the supplied consts + to indicate the desired day_of_week, ex. pendulum.MONDAY. + """ + return self.set(self.year, self.quarter * 3 - 2, 1).first_of( + "month", day_of_week + ) + + def _last_of_quarter(self, day_of_week: int | None = None) -> Date: + """ + Modify to the last occurrence of a given day of the week + in the current quarter. If no day_of_week is provided, + modify to the last day of the quarter. Use the supplied consts + to indicate the desired day_of_week, ex. pendulum.MONDAY. + """ + 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: + """ + Modify to the given occurrence of a given day of the week + in the current quarter. If the calculated occurrence is outside, + the scope of the current quarter, then return False and no + modifications are made. Use the supplied consts + to indicate the desired day_of_week, ex. pendulum.MONDAY. + """ + if nth == 1: + return self.first_of("quarter", day_of_week) + + dt = self.replace(self.year, self.quarter * 3, 1) + last_month = dt.month + year = dt.year + dt = dt.first_of("quarter") + for _ in range(nth - (1 if dt.day_of_week == day_of_week else 0)): + dt = dt.next(day_of_week) + + if last_month < dt.month or year != dt.year: + return None + + return self.set(self.year, dt.month, dt.day) + + def _first_of_year(self, day_of_week: int | None = None) -> Date: + """ + Modify to the first occurrence of a given day of the week + in the current year. If no day_of_week is provided, + modify to the first day of the year. Use the supplied consts + to indicate the desired day_of_week, ex. pendulum.MONDAY. + """ + return self.set(month=1).first_of("month", day_of_week) + + def _last_of_year(self, day_of_week: int | None = None) -> Date: + """ + Modify to the last occurrence of a given day of the week + in the current year. If no day_of_week is provided, + modify to the last day of the year. Use the supplied consts + to indicate the desired day_of_week, ex. pendulum.MONDAY. + """ + 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: + """ + Modify to the given occurrence of a given day of the week + in the current year. If the calculated occurrence is outside, + the scope of the current year, then return False and no + modifications are made. Use the supplied consts + to indicate the desired day_of_week, ex. pendulum.MONDAY. + """ + if nth == 1: + return self.first_of("year", day_of_week) + + dt = self.first_of("year") + year = dt.year + for _ in range(nth - (1 if dt.day_of_week == day_of_week else 0)): + dt = dt.next(day_of_week) + + if year != dt.year: + return None + + return self.set(self.year, dt.month, dt.day) + + def average(self, dt: date | None = None) -> Date: + """ + Modify the current instance to the average + of a given instance (default now) and the current instance. + """ + if dt is None: + dt = Date.today() + + return self.add(days=int(self.diff(dt, False).in_days() / 2)) + + # Native methods override + + @classmethod + def today(cls) -> Date: + dt = date.today() + + return cls(dt.year, dt.month, dt.day) + + @classmethod + def fromtimestamp(cls, t: float) -> Date: + dt = super().fromtimestamp(t) + + return cls(dt.year, dt.month, dt.day) + + @classmethod + def fromordinal(cls, n: int) -> Date: + 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 = 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 + + return self.__class__(year, month, day) diff --git a/pendulum/datetime.py b/pendulum/datetime.py index 6f1d5cf..52ad3cc 100644 --- a/pendulum/datetime.py +++ b/pendulum/datetime.py @@ -1,1563 +1,1381 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division - -import calendar -import datetime - -from typing import Optional -from typing import TypeVar -from typing import Union - -import pendulum - -from .constants import ATOM -from .constants import COOKIE -from .constants import MINUTES_PER_HOUR -from .constants import MONTHS_PER_YEAR -from .constants import RFC822 -from .constants import RFC850 -from .constants import RFC1036 -from .constants import RFC1123 -from .constants import RFC2822 -from .constants import RSS -from .constants import SATURDAY -from .constants import SECONDS_PER_DAY -from .constants import SECONDS_PER_MINUTE -from .constants import SUNDAY -from .constants import W3C -from .constants import YEARS_PER_CENTURY -from .constants import YEARS_PER_DECADE -from .date import Date -from .exceptions import PendulumException -from .helpers import add_duration -from .helpers import timestamp -from .period import Period -from .time import Time -from .tz import UTC -from .tz.timezone import Timezone -from .utils._compat import _HAS_FOLD - - -_D = TypeVar("_D", bound="DateTime") - - -class DateTime(datetime.datetime, Date): - - EPOCH = None # type: DateTime - - # Formats - - _FORMATS = { - "atom": ATOM, - "cookie": COOKIE, - "iso8601": lambda dt: dt.isoformat(), - "rfc822": RFC822, - "rfc850": RFC850, - "rfc1036": RFC1036, - "rfc1123": RFC1123, - "rfc2822": RFC2822, - "rfc3339": lambda dt: dt.isoformat(), - "rss": RSS, - "w3c": W3C, - } - - _EPOCH = datetime.datetime(1970, 1, 1, tzinfo=UTC) - - _MODIFIERS_VALID_UNITS = [ - "second", - "minute", - "hour", - "day", - "week", - "month", - "year", - "decade", - "century", - ] - - if not _HAS_FOLD: - - def __new__( - cls, - year, - month, - day, - hour=0, - minute=0, - second=0, - microsecond=0, - tzinfo=None, - fold=0, - ): - self = datetime.datetime.__new__( - cls, year, month, day, hour, minute, second, microsecond, tzinfo=tzinfo - ) - - self._fold = fold - - return self - - @classmethod - def now(cls, tz=None): # type: (Optional[Union[str, Timezone]]) -> DateTime - """ - Get a DateTime instance for the current date and time. - """ - return pendulum.now(tz) - - @classmethod - def utcnow(cls): # type: () -> DateTime - """ - Get a DateTime instance for the current date and time in UTC. - """ - return pendulum.now(UTC) - - @classmethod - def today(cls): # type: () -> DateTime - return pendulum.now() - - @classmethod - def strptime(cls, time, fmt): # type: (str, str) -> DateTime - return pendulum.instance(datetime.datetime.strptime(time, fmt)) - - # Getters/Setters - - def set( - self, - year=None, - month=None, - day=None, - hour=None, - minute=None, - second=None, - microsecond=None, - tz=None, - ): - if year is None: - year = self.year - if month is None: - month = self.month - if day is None: - day = self.day - if hour is None: - hour = self.hour - if minute is None: - minute = self.minute - if second is None: - second = self.second - if microsecond is None: - microsecond = self.microsecond - if tz is None: - tz = self.tz - - return pendulum.datetime( - year, month, day, hour, minute, second, microsecond, tz=tz - ) - - if not _HAS_FOLD: - - @property - def fold(self): - return self._fold - - def timestamp(self): - if self.tzinfo is None: - s = timestamp(self) - - return s + self.microsecond / 1e6 - else: - kwargs = {"tzinfo": self.tzinfo} - - if _HAS_FOLD: - kwargs["fold"] = self.fold - - dt = datetime.datetime( - self.year, - self.month, - self.day, - self.hour, - self.minute, - self.second, - self.microsecond, - **kwargs - ) - return (dt - self._EPOCH).total_seconds() - - @property - def float_timestamp(self): - return self.timestamp() - - @property - def int_timestamp(self): - # Workaround needed to avoid inaccuracy - # for far into the future datetimes - kwargs = {"tzinfo": self.tzinfo} - - if _HAS_FOLD: - kwargs["fold"] = self.fold - - dt = datetime.datetime( - self.year, - self.month, - self.day, - self.hour, - self.minute, - self.second, - self.microsecond, - **kwargs - ) - - delta = dt - self._EPOCH - - return delta.days * SECONDS_PER_DAY + delta.seconds - - @property - def offset(self): - return self.get_offset() - - @property - def offset_hours(self): - return self.get_offset() / SECONDS_PER_MINUTE / MINUTES_PER_HOUR - - @property - def timezone(self): # type: () -> Optional[Timezone] - if not isinstance(self.tzinfo, Timezone): - return - - return self.tzinfo - - @property - def tz(self): # type: () -> Optional[Timezone] - return self.timezone - - @property - def timezone_name(self): # type: () -> Optional[str] - tz = self.timezone - - if tz is None: - return None - - return tz.name - - @property - def age(self): - return self.date().diff(self.now(self.tz).date(), abs=False).in_years() - - def is_local(self): - return self.offset == self.in_timezone(pendulum.local_timezone()).offset - - def is_utc(self): - return self.offset == UTC.offset - - def is_dst(self): - return self.dst() != datetime.timedelta() - - def get_offset(self): - return int(self.utcoffset().total_seconds()) - - def date(self): - return Date(self.year, self.month, self.day) - - def time(self): - return Time(self.hour, self.minute, self.second, self.microsecond) - - def naive(self): # type: (_D) -> _D - """ - Return the DateTime without timezone information. - """ - return self.__class__( - self.year, - self.month, - self.day, - self.hour, - self.minute, - self.second, - self.microsecond, - ) - - def on(self, year, month, day): - """ - Returns a new instance with the current date set to a different date. - - :param year: The year - :type year: int - - :param month: The month - :type month: int - - :param day: The day - :type day: int - - :rtype: DateTime - """ - return self.set(year=int(year), month=int(month), day=int(day)) - - def at(self, hour, minute=0, second=0, microsecond=0): - """ - Returns a new instance with the current time to a different time. - - :param hour: The hour - :type hour: int - - :param minute: The minute - :type minute: int - - :param second: The second - :type second: int - - :param microsecond: The microsecond - :type microsecond: int - - :rtype: DateTime - """ - return self.set( - hour=hour, minute=minute, second=second, microsecond=microsecond - ) - - def in_timezone(self, tz): # type: (Union[str, Timezone]) -> DateTime - """ - Set the instance's timezone from a string or object. - """ - tz = pendulum._safe_timezone(tz) - - return tz.convert(self, dst_rule=pendulum.POST_TRANSITION) - - def in_tz(self, tz): # type: (Union[str, Timezone]) -> DateTime - """ - Set the instance's timezone from a string or object. - """ - return self.in_timezone(tz) - - # STRING FORMATTING - - def to_time_string(self): - """ - Format the instance as time. - - :rtype: str - """ - return self.format("HH:mm:ss") - - def to_datetime_string(self): - """ - Format the instance as date and time. - - :rtype: str - """ - return self.format("YYYY-MM-DD HH:mm:ss") - - def to_day_datetime_string(self): - """ - Format the instance as day, date and time (in english). - - :rtype: str - """ - return self.format("ddd, MMM D, YYYY h:mm A", locale="en") - - def to_atom_string(self): - """ - Format the instance as ATOM. - - :rtype: str - """ - return self._to_string("atom") - - def to_cookie_string(self): - """ - Format the instance as COOKIE. - - :rtype: str - """ - return self._to_string("cookie", locale="en") - - def to_iso8601_string(self): - """ - Format the instance as ISO 8601. - - :rtype: str - """ - string = self._to_string("iso8601") - - if self.tz and self.tz.name == "UTC": - string = string.replace("+00:00", "Z") - - return string - - def to_rfc822_string(self): - """ - Format the instance as RFC 822. - - :rtype: str - """ - return self._to_string("rfc822") - - def to_rfc850_string(self): - """ - Format the instance as RFC 850. - - :rtype: str - """ - return self._to_string("rfc850") - - def to_rfc1036_string(self): - """ - Format the instance as RFC 1036. - - :rtype: str - """ - return self._to_string("rfc1036") - - def to_rfc1123_string(self): - """ - Format the instance as RFC 1123. - - :rtype: str - """ - return self._to_string("rfc1123") - - def to_rfc2822_string(self): - """ - Format the instance as RFC 2822. - - :rtype: str - """ - return self._to_string("rfc2822") - - def to_rfc3339_string(self): - """ - Format the instance as RFC 3339. - - :rtype: str - """ - return self._to_string("rfc3339") - - def to_rss_string(self): - """ - Format the instance as RSS. - - :rtype: str - """ - return self._to_string("rss") - - def to_w3c_string(self): - """ - Format the instance as W3C. - - :rtype: str - """ - return self._to_string("w3c") - - def _to_string(self, fmt, locale=None): - """ - Format the instance to a common string format. - - :param fmt: The name of the string format - :type fmt: string - - :param locale: The locale to use - :type locale: str or None - - :rtype: str - """ - if fmt not in self._FORMATS: - raise ValueError("Format [{}] is not supported".format(fmt)) - - fmt = self._FORMATS[fmt] - if callable(fmt): - return fmt(self) - - return self.format(fmt, locale=locale) - - def __str__(self): - return self.isoformat("T") - - def __repr__(self): - us = "" - if self.microsecond: - us = ", {}".format(self.microsecond) - - repr_ = "{klass}(" "{year}, {month}, {day}, " "{hour}, {minute}, {second}{us}" - - if self.tzinfo is not None: - repr_ += ", tzinfo={tzinfo}" - - repr_ += ")" - - return repr_.format( - klass=self.__class__.__name__, - year=self.year, - month=self.month, - day=self.day, - hour=self.hour, - minute=self.minute, - second=self.second, - us=us, - tzinfo=self.tzinfo, - ) - - # Comparisons - def closest(self, dt1, dt2, *dts): - """ - Get the farthest date from the instance. - - :type dt1: datetime.datetime - :type dt2: datetime.datetime - :type dts: list[datetime.datetime,] - - :rtype: DateTime - """ - dt1 = pendulum.instance(dt1) - dt2 = pendulum.instance(dt2) - dts = [dt1, dt2] + [pendulum.instance(x) for x in dts] - dts = [(abs(self - dt), dt) for dt in dts] - - return min(dts)[1] - - def farthest(self, dt1, dt2, *dts): - """ - Get the farthest date from the instance. - - :type dt1: datetime.datetime - :type dt2: datetime.datetime - :type dts: list[datetime.datetime,] - - :rtype: DateTime - """ - dt1 = pendulum.instance(dt1) - dt2 = pendulum.instance(dt2) - - dts = [dt1, dt2] + [pendulum.instance(x) for x in dts] - dts = [(abs(self - dt), dt) for dt in dts] - - return max(dts)[1] - - def is_future(self): - """ - Determines if the instance is in the future, ie. greater than now. - - :rtype: bool - """ - return self > self.now(self.timezone) - - def is_past(self): - """ - Determines if the instance is in the past, ie. less than now. - - :rtype: bool - """ - return self < self.now(self.timezone) - - def is_long_year(self): - """ - Determines if the instance is a long year - - See link `https://en.wikipedia.org/wiki/ISO_8601#Week_dates`_ - - :rtype: bool - """ - return ( - pendulum.datetime(self.year, 12, 28, 0, 0, 0, tz=self.tz).isocalendar()[1] - == 53 - ) - - def is_same_day(self, dt): - """ - Checks if the passed in date is the same day - as the instance current day. - - :type dt: DateTime or datetime or str or int - - :rtype: bool - """ - dt = pendulum.instance(dt) - - return self.to_date_string() == dt.to_date_string() - - def is_anniversary(self, dt=None): - """ - Check if its the anniversary. - Compares the date/month values of the two dates. - - :rtype: bool - """ - if dt is None: - dt = self.now(self.tz) - - instance = pendulum.instance(dt) - - 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 - is_birthday = is_anniversary - - # ADDITIONS AND SUBSTRACTIONS - - def add( - self, - years=0, - months=0, - weeks=0, - days=0, - hours=0, - minutes=0, - seconds=0, - microseconds=0, - ): # type: (_D, int, int, int, int, int, int, int, int) -> _D - """ - Add a duration to the instance. - - If we're adding units of variable length (i.e., years, months), - move forward from curren time, - otherwise move forward from utc, for accuracy - when moving across DST boundaries. - """ - units_of_variable_length = any([years, months, weeks, days]) - - current_dt = datetime.datetime( - self.year, - self.month, - self.day, - self.hour, - self.minute, - self.second, - self.microsecond, - ) - if not units_of_variable_length: - offset = self.utcoffset() - if offset: - current_dt = current_dt - offset - - dt = add_duration( - current_dt, - years=years, - months=months, - weeks=weeks, - days=days, - hours=hours, - minutes=minutes, - seconds=seconds, - microseconds=microseconds, - ) - - if units_of_variable_length or self.tzinfo is None: - return pendulum.datetime( - dt.year, - dt.month, - dt.day, - dt.hour, - dt.minute, - dt.second, - dt.microsecond, - tz=self.tz, - ) - - dt = self.__class__( - dt.year, - dt.month, - dt.day, - dt.hour, - dt.minute, - dt.second, - dt.microsecond, - tzinfo=UTC, - ) - - dt = self.tz.convert(dt) - - return self.__class__( - dt.year, - dt.month, - dt.day, - dt.hour, - dt.minute, - dt.second, - dt.microsecond, - tzinfo=self.tz, - fold=dt.fold, - ) - - def subtract( - self, - years=0, - months=0, - weeks=0, - days=0, - hours=0, - minutes=0, - seconds=0, - microseconds=0, - ): - """ - Remove duration from the instance. - - :param years: The number of years - :type years: int - - :param months: The number of months - :type months: int - - :param weeks: The number of weeks - :type weeks: int - - :param days: The number of days - :type days: int - - :param hours: The number of hours - :type hours: int - - :param minutes: The number of minutes - :type minutes: int - - :param seconds: The number of seconds - :type seconds: int - - :param microseconds: The number of microseconds - :type microseconds: int - - :rtype: DateTime - """ - return self.add( - years=-years, - months=-months, - weeks=-weeks, - days=-days, - hours=-hours, - minutes=-minutes, - seconds=-seconds, - microseconds=-microseconds, - ) - - # 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): - """ - Add timedelta duration to the instance. - - :param delta: The timedelta instance - :type delta: pendulum.Duration or datetime.timedelta - - :rtype: DateTime - """ - if isinstance(delta, pendulum.Period): - return self.add( - years=delta.years, - months=delta.months, - weeks=delta.weeks, - days=delta.remaining_days, - hours=delta.hours, - minutes=delta.minutes, - seconds=delta.remaining_seconds, - microseconds=delta.microseconds, - ) - elif isinstance(delta, pendulum.Duration): - return self.add( - years=delta.years, months=delta.months, seconds=delta._total - ) - - return self.add(seconds=delta.total_seconds()) - - def _subtract_timedelta(self, delta): - """ - Remove timedelta duration from the instance. - - :param delta: The timedelta instance - :type delta: pendulum.Duration or datetime.timedelta - - :rtype: DateTime - """ - if isinstance(delta, pendulum.Duration): - return self.subtract( - years=delta.years, months=delta.months, seconds=delta._total - ) - - return self.subtract(seconds=delta.total_seconds()) - - # DIFFERENCES - - def diff(self, dt=None, abs=True): - """ - Returns the difference between two DateTime objects represented as a Duration. - - :type dt: DateTime or None - - :param abs: Whether to return an absolute interval or not - :type abs: bool - - :rtype: Period - """ - if dt is None: - dt = self.now(self.tz) - - return Period(self, dt, absolute=abs) - - def diff_for_humans( - self, - other=None, # type: Optional[DateTime] - absolute=False, # type: bool - locale=None, # type: Optional[str] - ): # type: (...) -> str - """ - Get the difference in a human readable format in the current locale. - - When comparing a value in the past to default now: - 1 day ago - 5 months ago - - When comparing a value in the future to default now: - 1 day from now - 5 months from now - - When comparing a value in the past to another value: - 1 day before - 5 months before - - When comparing a value in the future to another value: - 1 day after - 5 months after - """ - is_now = other is None - - if is_now: - other = self.now() - - diff = self.diff(other) - - return pendulum.format_diff(diff, is_now, absolute, locale) - - # Modifiers - def start_of(self, unit): - """ - Returns a copy of the instance with the time reset - with the following rules: - - * second: microsecond set to 0 - * minute: second and microsecond set to 0 - * hour: minute, second and microsecond set to 0 - * day: time to 00:00:00 - * week: date to first day of the week and time to 00:00:00 - * month: date to first day of the month and time to 00:00:00 - * year: date to first day of the year and time to 00:00:00 - * decade: date to first day of the decade and time to 00:00:00 - * century: date to first day of century and time to 00:00:00 - - :param unit: The unit to reset to - :type unit: str - - :rtype: DateTime - """ - if unit not in self._MODIFIERS_VALID_UNITS: - raise ValueError('Invalid unit "{}" for start_of()'.format(unit)) - - return getattr(self, "_start_of_{}".format(unit))() - - def end_of(self, unit): - """ - Returns a copy of the instance with the time reset - with the following rules: - - * second: microsecond set to 999999 - * minute: second set to 59 and microsecond set to 999999 - * hour: minute and second set to 59 and microsecond set to 999999 - * day: time to 23:59:59.999999 - * week: date to last day of the week and time to 23:59:59.999999 - * month: date to last day of the month and time to 23:59:59.999999 - * year: date to last day of the year and time to 23:59:59.999999 - * decade: date to last day of the decade and time to 23:59:59.999999 - * century: date to last day of century and time to 23:59:59.999999 - - :param unit: The unit to reset to - :type unit: str - - :rtype: DateTime - """ - if unit not in self._MODIFIERS_VALID_UNITS: - raise ValueError('Invalid unit "%s" for end_of()' % unit) - - return getattr(self, "_end_of_%s" % unit)() - - def _start_of_second(self): - """ - Reset microseconds to 0. - - :rtype: DateTime - """ - return self.set(microsecond=0) - - def _end_of_second(self): - """ - Set microseconds to 999999. - - :rtype: DateTime - """ - return self.set(microsecond=999999) - - def _start_of_minute(self): - """ - Reset seconds and microseconds to 0. - - :rtype: DateTime - """ - return self.set(second=0, microsecond=0) - - def _end_of_minute(self): - """ - Set seconds to 59 and microseconds to 999999. - - :rtype: DateTime - """ - return self.set(second=59, microsecond=999999) - - def _start_of_hour(self): - """ - Reset minutes, seconds and microseconds to 0. - - :rtype: DateTime - """ - return self.set(minute=0, second=0, microsecond=0) - - def _end_of_hour(self): - """ - Set minutes and seconds to 59 and microseconds to 999999. - - :rtype: DateTime - """ - return self.set(minute=59, second=59, microsecond=999999) - - def _start_of_day(self): - """ - Reset the time to 00:00:00 - - :rtype: DateTime - """ - return self.at(0, 0, 0, 0) - - def _end_of_day(self): - """ - Reset the time to 23:59:59.999999 - - :rtype: DateTime - """ - return self.at(23, 59, 59, 999999) - - def _start_of_month(self): - """ - Reset the date to the first day of the month and the time to 00:00:00. - - :rtype: DateTime - """ - return self.set(self.year, self.month, 1, 0, 0, 0, 0) - - def _end_of_month(self): - """ - Reset the date to the last day of the month - and the time to 23:59:59.999999. - - :rtype: DateTime - """ - return self.set(self.year, self.month, self.days_in_month, 23, 59, 59, 999999) - - def _start_of_year(self): - """ - Reset the date to the first day of the year and the time to 00:00:00. - - :rtype: DateTime - """ - return self.set(self.year, 1, 1, 0, 0, 0, 0) - - def _end_of_year(self): - """ - Reset the date to the last day of the year - and the time to 23:59:59.999999 - - :rtype: DateTime - """ - return self.set(self.year, 12, 31, 23, 59, 59, 999999) - - def _start_of_decade(self): - """ - Reset the date to the first day of the decade - and the time to 00:00:00. - - :rtype: DateTime - """ - year = self.year - self.year % YEARS_PER_DECADE - return self.set(year, 1, 1, 0, 0, 0, 0) - - def _end_of_decade(self): - """ - Reset the date to the last day of the decade - and the time to 23:59:59.999999. - - :rtype: DateTime - """ - year = self.year - self.year % YEARS_PER_DECADE + YEARS_PER_DECADE - 1 - - return self.set(year, 12, 31, 23, 59, 59, 999999) - - def _start_of_century(self): - """ - Reset the date to the first day of the century - and the time to 00:00:00. - - :rtype: DateTime - """ - year = self.year - 1 - (self.year - 1) % YEARS_PER_CENTURY + 1 - - return self.set(year, 1, 1, 0, 0, 0, 0) - - def _end_of_century(self): - """ - Reset the date to the last day of the century - and the time to 23:59:59.999999. - - :rtype: DateTime - """ - year = self.year - 1 - (self.year - 1) % YEARS_PER_CENTURY + YEARS_PER_CENTURY - - return self.set(year, 12, 31, 23, 59, 59, 999999) - - def _start_of_week(self): - """ - Reset the date to the first day of the week - and the time to 00:00:00. - - :rtype: DateTime - """ - dt = self - - if self.day_of_week != pendulum._WEEK_STARTS_AT: - dt = self.previous(pendulum._WEEK_STARTS_AT) - - return dt.start_of("day") - - def _end_of_week(self): - """ - Reset the date to the last day of the week - and the time to 23:59:59. - - :rtype: DateTime - """ - dt = self - - if self.day_of_week != pendulum._WEEK_ENDS_AT: - dt = self.next(pendulum._WEEK_ENDS_AT) - - return dt.end_of("day") - - def next(self, day_of_week=None, keep_time=False): - """ - Modify to the next occurrence of a given day of the week. - If no day_of_week is provided, modify to the next occurrence - of the current day of the week. Use the supplied consts - to indicate the desired day_of_week, ex. DateTime.MONDAY. - - :param day_of_week: The next day of week to reset to. - :type day_of_week: int or None - - :param keep_time: Whether to keep the time information or not. - :type keep_time: bool - - :rtype: DateTime - """ - if day_of_week is None: - day_of_week = self.day_of_week - - if day_of_week < SUNDAY or day_of_week > SATURDAY: - raise ValueError("Invalid day of week") - - if keep_time: - dt = self - else: - dt = self.start_of("day") - - dt = dt.add(days=1) - while dt.day_of_week != day_of_week: - dt = dt.add(days=1) - - return dt - - def previous(self, day_of_week=None, keep_time=False): - """ - Modify to the previous occurrence of a given day of the week. - If no day_of_week is provided, modify to the previous occurrence - of the current day of the week. Use the supplied consts - to indicate the desired day_of_week, ex. DateTime.MONDAY. - - :param day_of_week: The previous day of week to reset to. - :type day_of_week: int or None - - :param keep_time: Whether to keep the time information or not. - :type keep_time: bool - - :rtype: DateTime - """ - if day_of_week is None: - day_of_week = self.day_of_week - - if day_of_week < SUNDAY or day_of_week > SATURDAY: - raise ValueError("Invalid day of week") - - if keep_time: - dt = self - else: - dt = self.start_of("day") - - dt = dt.subtract(days=1) - while dt.day_of_week != day_of_week: - dt = dt.subtract(days=1) - - return dt - - def first_of(self, unit, day_of_week=None): - """ - 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. - - Supported units are month, quarter and year. - - :param unit: The unit to use - :type unit: str - - :type day_of_week: int or None - - :rtype: DateTime - """ - if unit not in ["month", "quarter", "year"]: - raise ValueError('Invalid unit "{}" for first_of()'.format(unit)) - - return getattr(self, "_first_of_{}".format(unit))(day_of_week) - - def last_of(self, unit, day_of_week=None): - """ - 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. - - Supported units are month, quarter and year. - - :param unit: The unit to use - :type unit: str - - :type day_of_week: int or None - - :rtype: DateTime - """ - if unit not in ["month", "quarter", "year"]: - raise ValueError('Invalid unit "{}" for first_of()'.format(unit)) - - return getattr(self, "_last_of_{}".format(unit))(day_of_week) - - def nth_of(self, unit, nth, day_of_week): - """ - Returns a new instance set to the given occurrence - of a given day of the week in the current unit. - If the calculated occurrence is outside the scope of the current unit, - then raise an error. Use the supplied consts - to indicate the desired day_of_week, ex. DateTime.MONDAY. - - Supported units are month, quarter and year. - - :param unit: The unit to use - :type unit: str - - :type nth: int - - :type day_of_week: int or None - - :rtype: DateTime - """ - if unit not in ["month", "quarter", "year"]: - raise ValueError('Invalid unit "{}" for first_of()'.format(unit)) - - dt = getattr(self, "_nth_of_{}".format(unit))(nth, day_of_week) - if dt is False: - raise PendulumException( - "Unable to find occurence {} of {} in {}".format( - nth, self._days[day_of_week], unit - ) - ) - - return dt - - def _first_of_month(self, day_of_week): - """ - Modify to the first occurrence of a given day of the week - in the current month. If no day_of_week is provided, - modify to the first day of the month. Use the supplied consts - to indicate the desired day_of_week, ex. DateTime.MONDAY. - - :type day_of_week: int - - :rtype: DateTime - """ - dt = self.start_of("day") - - if day_of_week is None: - return dt.set(day=1) - - month = calendar.monthcalendar(dt.year, dt.month) - - calendar_day = (day_of_week - 1) % 7 - - if month[0][calendar_day] > 0: - day_of_month = month[0][calendar_day] - else: - day_of_month = month[1][calendar_day] - - return dt.set(day=day_of_month) - - def _last_of_month(self, day_of_week=None): - """ - Modify to the last occurrence of a given day of the week - in the current month. If no day_of_week is provided, - modify to the last day of the month. Use the supplied consts - to indicate the desired day_of_week, ex. DateTime.MONDAY. - - :type day_of_week: int or None - - :rtype: DateTime - """ - dt = self.start_of("day") - - if day_of_week is None: - return dt.set(day=self.days_in_month) - - month = calendar.monthcalendar(dt.year, dt.month) - - calendar_day = (day_of_week - 1) % 7 - - if month[-1][calendar_day] > 0: - day_of_month = month[-1][calendar_day] - else: - day_of_month = month[-2][calendar_day] - - return dt.set(day=day_of_month) - - def _nth_of_month(self, nth, day_of_week): - """ - Modify to the given occurrence of a given day of the week - in the current month. If the calculated occurrence is outside, - the scope of the current month, then return False and no - modifications are made. Use the supplied consts - to indicate the desired day_of_week, ex. DateTime.MONDAY. - - :type nth: int - - :type day_of_week: int or None - - :rtype: DateTime - """ - if nth == 1: - return self.first_of("month", day_of_week) - - dt = self.first_of("month") - check = dt.format("%Y-%M") - for i in range(nth - (1 if dt.day_of_week == day_of_week else 0)): - dt = dt.next(day_of_week) - - if dt.format("%Y-%M") == check: - return self.set(day=dt.day).start_of("day") - - return False - - def _first_of_quarter(self, day_of_week=None): - """ - Modify to the first occurrence of a given day of the week - in the current quarter. If no day_of_week is provided, - modify to the first day of the quarter. Use the supplied consts - to indicate the desired day_of_week, ex. DateTime.MONDAY. - - :type day_of_week: int or None - - :rtype: DateTime - """ - return self.on(self.year, self.quarter * 3 - 2, 1).first_of( - "month", day_of_week - ) - - def _last_of_quarter(self, day_of_week=None): - """ - Modify to the last occurrence of a given day of the week - in the current quarter. If no day_of_week is provided, - modify to the last day of the quarter. Use the supplied consts - to indicate the desired day_of_week, ex. DateTime.MONDAY. - - :type day_of_week: int or None - - :rtype: DateTime - """ - return self.on(self.year, self.quarter * 3, 1).last_of("month", day_of_week) - - def _nth_of_quarter(self, nth, day_of_week): - """ - Modify to the given occurrence of a given day of the week - in the current quarter. If the calculated occurrence is outside, - the scope of the current quarter, then return False and no - modifications are made. Use the supplied consts - to indicate the desired day_of_week, ex. DateTime.MONDAY. - - :type nth: int - - :type day_of_week: int or None - - :rtype: DateTime - """ - if nth == 1: - return self.first_of("quarter", day_of_week) - - dt = self.set(day=1, month=self.quarter * 3) - last_month = dt.month - year = dt.year - dt = dt.first_of("quarter") - for i in range(nth - (1 if dt.day_of_week == day_of_week else 0)): - dt = dt.next(day_of_week) - - if last_month < dt.month or year != dt.year: - return False - - return self.on(self.year, dt.month, dt.day).start_of("day") - - def _first_of_year(self, day_of_week=None): - """ - Modify to the first occurrence of a given day of the week - in the current year. If no day_of_week is provided, - modify to the first day of the year. Use the supplied consts - to indicate the desired day_of_week, ex. DateTime.MONDAY. - - :type day_of_week: int or None - - :rtype: DateTime - """ - return self.set(month=1).first_of("month", day_of_week) - - def _last_of_year(self, day_of_week=None): - """ - Modify to the last occurrence of a given day of the week - in the current year. If no day_of_week is provided, - modify to the last day of the year. Use the supplied consts - to indicate the desired day_of_week, ex. DateTime.MONDAY. - - :type day_of_week: int or None - - :rtype: DateTime - """ - return self.set(month=MONTHS_PER_YEAR).last_of("month", day_of_week) - - def _nth_of_year(self, nth, day_of_week): - """ - Modify to the given occurrence of a given day of the week - in the current year. If the calculated occurrence is outside, - the scope of the current year, then return False and no - modifications are made. Use the supplied consts - to indicate the desired day_of_week, ex. DateTime.MONDAY. - - :type nth: int - - :type day_of_week: int or None - - :rtype: DateTime - """ - if nth == 1: - return self.first_of("year", day_of_week) - - dt = self.first_of("year") - year = dt.year - for i in range(nth - (1 if dt.day_of_week == day_of_week else 0)): - dt = dt.next(day_of_week) - - if year != dt.year: - return False - - return self.on(self.year, dt.month, dt.day).start_of("day") - - def average(self, dt=None): - """ - Modify the current instance to the average - of a given instance (default now) and the current instance. - - :type dt: DateTime or datetime - - :rtype: DateTime - """ - if dt is None: - dt = self.now(self.tz) - - diff = self.diff(dt, False) - return self.add( - microseconds=(diff.in_seconds() * 1000000 + diff.microseconds) // 2 - ) - - def __sub__(self, other): - if isinstance(other, datetime.timedelta): - return self._subtract_timedelta(other) - - if not isinstance(other, datetime.datetime): - return NotImplemented - - if not isinstance(other, self.__class__): - if other.tzinfo is None: - other = pendulum.naive( - other.year, - other.month, - other.day, - other.hour, - other.minute, - other.second, - other.microsecond, - ) - else: - other = pendulum.instance(other) - - return other.diff(self, False) - - def __rsub__(self, other): - if not isinstance(other, datetime.datetime): - return NotImplemented - - if not isinstance(other, self.__class__): - if other.tzinfo is None: - other = pendulum.naive( - other.year, - other.month, - other.day, - other.hour, - other.minute, - other.second, - other.microsecond, - ) - else: - other = pendulum.instance(other) - - return self.diff(other, False) - - def __add__(self, other): - if not isinstance(other, datetime.timedelta): - return NotImplemented - - return self._add_timedelta_(other) - - def __radd__(self, other): - return self.__add__(other) - - # Native methods override - - @classmethod - def fromtimestamp(cls, t, tz=None): - return pendulum.instance(datetime.datetime.fromtimestamp(t, tz=tz), tz=tz) - - @classmethod - def utcfromtimestamp(cls, t): - return pendulum.instance(datetime.datetime.utcfromtimestamp(t), tz=None) - - @classmethod - def fromordinal(cls, n): - return pendulum.instance(datetime.datetime.fromordinal(n), tz=None) - - @classmethod - def combine(cls, date, time): - return pendulum.instance(datetime.datetime.combine(date, time), tz=None) - - def astimezone(self, tz=None): - return pendulum.instance(super(DateTime, self).astimezone(tz)) - - def replace( - self, - year=None, - month=None, - day=None, - hour=None, - minute=None, - second=None, - microsecond=None, - tzinfo=True, - fold=None, - ): - if year is None: - year = self.year - if month is None: - month = self.month - if day is None: - day = self.day - if hour is None: - hour = self.hour - if minute is None: - minute = self.minute - if second is None: - second = self.second - if microsecond is None: - microsecond = self.microsecond - if tzinfo is True: - tzinfo = self.tzinfo - if fold is None: - fold = self.fold - - transition_rule = pendulum.POST_TRANSITION - if fold is not None: - transition_rule = pendulum.PRE_TRANSITION - if fold: - transition_rule = pendulum.POST_TRANSITION - - return pendulum.datetime( - year, - month, - day, - hour, - minute, - second, - microsecond, - tz=tzinfo, - dst_rule=transition_rule, - ) - - def __getnewargs__(self): - return (self,) - - def _getstate(self, protocol=3): - return ( - self.year, - self.month, - self.day, - self.hour, - self.minute, - self.second, - self.microsecond, - self.tzinfo, - ) - - def __reduce__(self): - return self.__reduce_ex__(2) - - def __reduce_ex__(self, protocol): - return self.__class__, self._getstate(protocol) - - def _cmp(self, other, **kwargs): - # Fix for pypy which compares using this method - # which would lead to infinite recursion if we didn't override - kwargs = {"tzinfo": self.tz} - - if _HAS_FOLD: - kwargs["fold"] = self.fold - - dt = datetime.datetime( - self.year, - self.month, - self.day, - self.hour, - self.minute, - self.second, - self.microsecond, - **kwargs - ) - - return 0 if dt == other else 1 if dt > other else -1 - - -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) +from __future__ import annotations + +import calendar +import datetime + +from typing import TYPE_CHECKING +from typing import Any +from typing import Callable +from typing import Optional +from typing import cast +from typing import overload + +import pendulum + +from pendulum.constants import ATOM +from pendulum.constants import COOKIE +from pendulum.constants import MINUTES_PER_HOUR +from pendulum.constants import MONTHS_PER_YEAR +from pendulum.constants import RFC822 +from pendulum.constants import RFC850 +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.exceptions import PendulumException +from pendulum.helpers import add_duration +from pendulum.interval import Interval +from pendulum.time import Time +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 + + +class DateTime(datetime.datetime, Date): + EPOCH: DateTime + + # Formats + + _FORMATS: dict[str, str | Callable[[datetime.datetime], str]] = { + "atom": ATOM, + "cookie": COOKIE, + "iso8601": lambda dt: dt.isoformat(), + "rfc822": RFC822, + "rfc850": RFC850, + "rfc1036": RFC1036, + "rfc1123": RFC1123, + "rfc2822": RFC2822, + "rfc3339": lambda dt: dt.isoformat(), + "rss": RSS, + "w3c": W3C, + } + + _MODIFIERS_VALID_UNITS: list[str] = [ + "second", + "minute", + "hour", + "day", + "week", + "month", + "year", + "decade", + "century", + ] + + _EPOCH: datetime.datetime = datetime.datetime(1970, 1, 1, tzinfo=UTC) + + @classmethod + def create( + cls, + year: int, + month: int, + day: int, + hour: int = 0, + minute: int = 0, + second: int = 0, + microsecond: int = 0, + tz: str | float | Timezone | FixedTimezone | None | datetime.tzinfo = UTC, + fold: int = 1, + raise_on_unknown_times: bool = False, + ) -> DateTime: + """ + Creates a new DateTime instance from a specific date and time. + """ + if tz is not None: + tz = pendulum._safe_timezone(tz) + + dt = datetime.datetime( + year, month, day, hour, minute, second, microsecond, fold=fold + ) + + if tz is not None: + dt = tz.convert(dt, raise_on_unknown_times=raise_on_unknown_times) + + return cls( + dt.year, + dt.month, + dt.day, + dt.hour, + dt.minute, + dt.second, + dt.microsecond, + tzinfo=dt.tzinfo, + fold=dt.fold, + ) + + @overload + @classmethod + def now(cls, tz: datetime.tzinfo | None = None) -> DateTime: + ... + + @overload + @classmethod + def now(cls, tz: str | Timezone | FixedTimezone | None = None) -> DateTime: + ... + + @classmethod + def now( + cls, tz: str | Timezone | FixedTimezone | datetime.tzinfo | None = None + ) -> DateTime: + """ + Get a DateTime instance for the current date and time. + """ + if tz is None or tz == "local": + dt = datetime.datetime.now(local_timezone()) + elif tz is UTC or tz == "UTC": + dt = datetime.datetime.now(UTC) + else: + dt = datetime.datetime.now(UTC) + tz = pendulum._safe_timezone(tz) + dt = dt.astimezone(tz) + + return cls( + dt.year, + dt.month, + dt.day, + dt.hour, + dt.minute, + dt.second, + dt.microsecond, + tzinfo=dt.tzinfo, + fold=dt.fold, + ) + + @classmethod + def utcnow(cls) -> DateTime: + """ + Get a DateTime instance for the current date and time in UTC. + """ + return cls.now(UTC) + + @classmethod + def today(cls) -> DateTime: + return cls.now() + + @classmethod + def strptime(cls, time: str, fmt: str) -> DateTime: + return pendulum.instance(datetime.datetime.strptime(time, fmt)) + + # Getters/Setters + + def set( + 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, + tz: str | float | Timezone | FixedTimezone | datetime.tzinfo | None = None, + ) -> DateTime: + if year is None: + year = self.year + if month is None: + month = self.month + if day is None: + day = self.day + if hour is None: + hour = self.hour + if minute is None: + minute = self.minute + if second is None: + second = self.second + if microsecond is None: + microsecond = self.microsecond + if tz is None: + tz = self.tz + + return DateTime.create( + year, month, day, hour, minute, second, microsecond, tz=tz + ) + + @property + def float_timestamp(self) -> float: + return self.timestamp() + + @property + def int_timestamp(self) -> int: + # Workaround needed to avoid inaccuracy + # for far into the future datetimes + dt = datetime.datetime( + self.year, + self.month, + self.day, + self.hour, + self.minute, + self.second, + self.microsecond, + tzinfo=self.tzinfo, + fold=self.fold, + ) + + delta = dt - self._EPOCH + + return delta.days * SECONDS_PER_DAY + delta.seconds + + @property + def offset(self) -> int | None: + return self.get_offset() + + @property + def offset_hours(self) -> float | None: + offset = self.get_offset() + + if offset is None: + return None + + return offset / SECONDS_PER_MINUTE / MINUTES_PER_HOUR + + @property + def timezone(self) -> Timezone | FixedTimezone | None: + if not isinstance(self.tzinfo, (Timezone, FixedTimezone)): + return None + + return self.tzinfo + + @property + def tz(self) -> Timezone | FixedTimezone | None: + return self.timezone + + @property + def timezone_name(self) -> str | None: + tz = self.timezone + + if tz is None: + return None + + return tz.name + + @property + def age(self) -> int: + return self.date().diff(self.now(self.tz).date(), abs=False).in_years() + + def is_local(self) -> bool: + return self.offset == self.in_timezone(pendulum.local_timezone()).offset + + def is_utc(self) -> bool: + return self.offset == 0 + + def is_dst(self) -> bool: + return self.dst() != datetime.timedelta() + + def get_offset(self) -> int | None: + utcoffset = self.utcoffset() + if utcoffset is None: + return None + + return int(utcoffset.total_seconds()) + + def date(self) -> Date: + return Date(self.year, self.month, self.day) + + def time(self) -> Time: + return Time(self.hour, self.minute, self.second, self.microsecond) + + def naive(self) -> DateTime: + """ + Return the DateTime without timezone information. + """ + return self.__class__( + self.year, + self.month, + self.day, + self.hour, + self.minute, + self.second, + self.microsecond, + ) + + def on(self, year: int, month: int, day: int) -> DateTime: + """ + Returns a new instance with the current date set to a different date. + """ + return self.set(year=int(year), month=int(month), day=int(day)) + + def at( + self, hour: int, minute: int = 0, second: int = 0, microsecond: int = 0 + ) -> DateTime: + """ + Returns a new instance with the current time to a different time. + """ + return self.set( + hour=hour, minute=minute, second=second, microsecond=microsecond + ) + + def in_timezone(self, tz: str | Timezone | FixedTimezone) -> DateTime: + """ + Set the instance's timezone from a string or object. + """ + tz = pendulum._safe_timezone(tz) + + dt = self + if not self.timezone: + dt = dt.replace(fold=1) + + return cast(DateTime, tz.convert(dt)) + + def in_tz(self, tz: str | Timezone | FixedTimezone) -> DateTime: + """ + Set the instance's timezone from a string or object. + """ + return self.in_timezone(tz) + + # STRING FORMATTING + + def to_time_string(self) -> str: + """ + Format the instance as time. + """ + return self.format("HH:mm:ss") + + def to_datetime_string(self) -> str: + """ + Format the instance as date and time. + """ + return self.format("YYYY-MM-DD HH:mm:ss") + + def to_day_datetime_string(self) -> str: + """ + Format the instance as day, date and time (in english). + """ + return self.format("ddd, MMM D, YYYY h:mm A", locale="en") + + def to_atom_string(self) -> str: + """ + Format the instance as ATOM. + """ + return self._to_string("atom") + + def to_cookie_string(self) -> str: + """ + Format the instance as COOKIE. + """ + return self._to_string("cookie", locale="en") + + def to_iso8601_string(self) -> str: + """ + Format the instance as ISO 8601. + """ + string = self._to_string("iso8601") + + if self.tz and self.tz.name == "UTC": + string = string.replace("+00:00", "Z") + + return string + + def to_rfc822_string(self) -> str: + """ + Format the instance as RFC 822. + """ + return self._to_string("rfc822") + + def to_rfc850_string(self) -> str: + """ + Format the instance as RFC 850. + """ + return self._to_string("rfc850") + + def to_rfc1036_string(self) -> str: + """ + Format the instance as RFC 1036. + """ + return self._to_string("rfc1036") + + def to_rfc1123_string(self) -> str: + """ + Format the instance as RFC 1123. + """ + return self._to_string("rfc1123") + + def to_rfc2822_string(self) -> str: + """ + Format the instance as RFC 2822. + """ + return self._to_string("rfc2822") + + def to_rfc3339_string(self) -> str: + """ + Format the instance as RFC 3339. + """ + return self._to_string("rfc3339") + + def to_rss_string(self) -> str: + """ + Format the instance as RSS. + """ + return self._to_string("rss") + + def to_w3c_string(self) -> str: + """ + Format the instance as W3C. + """ + return self._to_string("w3c") + + def _to_string(self, fmt: str, locale: str | None = None) -> str: + """ + Format the instance to a common string format. + """ + if fmt not in self._FORMATS: + raise ValueError(f"Format [{fmt}] is not supported") + + fmt_value = self._FORMATS[fmt] + if callable(fmt_value): + return fmt_value(self) + + return self.format(fmt_value, locale=locale) + + def __str__(self) -> str: + return self.isoformat("T") + + def __repr__(self) -> str: + us = "" + if self.microsecond: + us = f", {self.microsecond}" + + repr_ = "{klass}(" "{year}, {month}, {day}, " "{hour}, {minute}, {second}{us}" + + if self.tzinfo is not None: + repr_ += ", tzinfo={tzinfo}" + + repr_ += ")" + + return repr_.format( + klass=self.__class__.__name__, + year=self.year, + month=self.month, + day=self.day, + hour=self.hour, + minute=self.minute, + second=self.second, + us=us, + tzinfo=repr(self.tzinfo), + ) + + # Comparisons + def closest(self, *dts: datetime.datetime) -> DateTime: # type: ignore[override] + """ + Get the farthest date from the instance. + """ + pdts = [pendulum.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] + """ + Get the farthest date from the instance. + """ + pdts = [pendulum.instance(x) for x in dts] + + return max((abs(self - dt), dt) for dt in pdts)[1] + + def is_future(self) -> bool: + """ + Determines if the instance is in the future, ie. greater than now. + """ + return self > self.now(self.timezone) + + def is_past(self) -> bool: + """ + Determines if the instance is in the past, ie. less than now. + """ + return self < self.now(self.timezone) + + def is_long_year(self) -> bool: + """ + Determines if the instance is a long year + + See link `https://en.wikipedia.org/wiki/ISO_8601#Week_dates`_ + """ + return ( + DateTime.create(self.year, 12, 28, 0, 0, 0, tz=self.tz).isocalendar()[1] + == 53 + ) + + def is_same_day(self, dt: datetime.datetime) -> bool: # type: ignore[override] + """ + Checks if the passed in date is the same day + as the instance current day. + """ + dt = pendulum.instance(dt) + + return self.to_date_string() == dt.to_date_string() + + def is_anniversary( # type: ignore[override] + self, dt: datetime.datetime | None = None + ) -> bool: + """ + Check if its the anniversary. + Compares the date/month values of the two dates. + """ + if dt is None: + dt = self.now(self.tz) + + instance = pendulum.instance(dt) + + return (self.month, self.day) == (instance.month, instance.day) + + # ADDITIONS AND SUBSTRACTIONS + + def add( + self, + years: int = 0, + months: int = 0, + weeks: int = 0, + days: int = 0, + hours: int = 0, + minutes: int = 0, + seconds: float = 0, + microseconds: int = 0, + ) -> DateTime: + """ + Add a duration to the instance. + + If we're adding units of variable length (i.e., years, months), + move forward from current time, otherwise move forward from utc, for accuracy + when moving across DST boundaries. + """ + units_of_variable_length = any([years, months, weeks, days]) + + current_dt = datetime.datetime( + self.year, + self.month, + self.day, + self.hour, + self.minute, + self.second, + self.microsecond, + ) + if not units_of_variable_length: + offset = self.utcoffset() + if offset: + current_dt = current_dt - offset + + dt = add_duration( + current_dt, + years=years, + months=months, + weeks=weeks, + days=days, + hours=hours, + minutes=minutes, + seconds=seconds, + microseconds=microseconds, + ) + + if units_of_variable_length or self.tz is None: + return DateTime.create( + dt.year, + dt.month, + dt.day, + dt.hour, + dt.minute, + dt.second, + dt.microsecond, + tz=self.tz, + ) + + dt = datetime.datetime( + dt.year, + dt.month, + dt.day, + dt.hour, + dt.minute, + dt.second, + dt.microsecond, + tzinfo=UTC, + ) + + dt = self.tz.convert(dt) + + return self.__class__( + dt.year, + dt.month, + dt.day, + dt.hour, + dt.minute, + dt.second, + dt.microsecond, + tzinfo=self.tz, + fold=dt.fold, + ) + + def subtract( + self, + years: int = 0, + months: int = 0, + weeks: int = 0, + days: int = 0, + hours: int = 0, + minutes: int = 0, + seconds: float = 0, + microseconds: int = 0, + ) -> DateTime: + """ + Remove duration from the instance. + """ + return self.add( + years=-years, + months=-months, + weeks=-weeks, + days=-days, + hours=-hours, + minutes=-minutes, + seconds=-seconds, + microseconds=-microseconds, + ) + + # 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: + """ + Add timedelta duration to the instance. + """ + if isinstance(delta, pendulum.Interval): + return self.add( + years=delta.years, + months=delta.months, + weeks=delta.weeks, + days=delta.remaining_days, + hours=delta.hours, + minutes=delta.minutes, + seconds=delta.remaining_seconds, + microseconds=delta.microseconds, + ) + elif isinstance(delta, pendulum.Duration): + return self.add( + years=delta.years, months=delta.months, seconds=delta._total + ) + + return self.add(seconds=delta.total_seconds()) + + def _subtract_timedelta(self, delta: datetime.timedelta) -> DateTime: + """ + Remove timedelta duration from the instance. + """ + if isinstance(delta, pendulum.Duration): + return self.subtract( + years=delta.years, months=delta.months, seconds=delta._total + ) + + return self.subtract(seconds=delta.total_seconds()) + + # DIFFERENCES + + def diff( # type: ignore[override] + self, dt: datetime.datetime | None = None, abs: bool = True + ) -> Interval: + """ + Returns the difference between two DateTime objects represented as an Interval. + """ + if dt is None: + dt = self.now(self.tz) + + return Interval(self, dt, absolute=abs) + + def diff_for_humans( # type: ignore[override] + self, + other: DateTime | None = None, + absolute: bool = False, + locale: str | None = None, + ) -> str: + """ + Get the difference in a human readable format in the current locale. + + When comparing a value in the past to default now: + 1 day ago + 5 months ago + + When comparing a value in the future to default now: + 1 day from now + 5 months from now + + When comparing a value in the past to another value: + 1 day before + 5 months before + + When comparing a value in the future to another value: + 1 day after + 5 months after + """ + is_now = other is None + + if is_now: + other = self.now() + + diff = self.diff(other) + + return pendulum.format_diff(diff, is_now, absolute, locale) + + # Modifiers + def start_of(self, unit: str) -> DateTime: + """ + Returns a copy of the instance with the time reset + with the following rules: + + * second: microsecond set to 0 + * minute: second and microsecond set to 0 + * hour: minute, second and microsecond set to 0 + * day: time to 00:00:00 + * week: date to first day of the week and time to 00:00:00 + * month: date to first day of the month and time to 00:00:00 + * year: date to first day of the year and time to 00:00:00 + * decade: date to first day of the decade and time to 00:00:00 + * century: date to first day of century and time to 00:00:00 + """ + 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}")()) + + def end_of(self, unit: str) -> DateTime: + """ + Returns a copy of the instance with the time reset + with the following rules: + + * second: microsecond set to 999999 + * minute: second set to 59 and microsecond set to 999999 + * hour: minute and second set to 59 and microsecond set to 999999 + * day: time to 23:59:59.999999 + * week: date to last day of the week and time to 23:59:59.999999 + * month: date to last day of the month and time to 23:59:59.999999 + * year: date to last day of the year and time to 23:59:59.999999 + * decade: date to last day of the decade and time to 23:59:59.999999 + * century: date to last day of century and time to 23:59:59.999999 + """ + 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}")()) + + def _start_of_second(self) -> DateTime: + """ + Reset microseconds to 0. + """ + return self.set(microsecond=0) + + def _end_of_second(self) -> DateTime: + """ + Set microseconds to 999999. + """ + return self.set(microsecond=999999) + + def _start_of_minute(self) -> DateTime: + """ + Reset seconds and microseconds to 0. + """ + return self.set(second=0, microsecond=0) + + def _end_of_minute(self) -> DateTime: + """ + Set seconds to 59 and microseconds to 999999. + """ + return self.set(second=59, microsecond=999999) + + def _start_of_hour(self) -> DateTime: + """ + Reset minutes, seconds and microseconds to 0. + """ + return self.set(minute=0, second=0, microsecond=0) + + def _end_of_hour(self) -> DateTime: + """ + 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: + """ + Reset the time to 00:00:00. + """ + return self.at(0, 0, 0, 0) + + def _end_of_day(self) -> DateTime: + """ + Reset the time to 23:59:59.999999. + """ + return self.at(23, 59, 59, 999999) + + def _start_of_month(self) -> DateTime: + """ + 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: + """ + 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: + """ + 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: + """ + 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: + """ + Reset the date to the first day of the decade + and the time to 00:00:00. + """ + year = self.year - self.year % YEARS_PER_DECADE + return self.set(year, 1, 1, 0, 0, 0, 0) + + def _end_of_decade(self) -> DateTime: + """ + Reset the date to the last day of the decade + and the time to 23:59:59.999999. + """ + year = self.year - self.year % YEARS_PER_DECADE + YEARS_PER_DECADE - 1 + + return self.set(year, 12, 31, 23, 59, 59, 999999) + + def _start_of_century(self) -> DateTime: + """ + Reset the date to the first day of the century + and the time to 00:00:00. + """ + year = self.year - 1 - (self.year - 1) % YEARS_PER_CENTURY + 1 + + return self.set(year, 1, 1, 0, 0, 0, 0) + + def _end_of_century(self) -> DateTime: + """ + Reset the date to the last day of the century + and the time to 23:59:59.999999. + """ + year = self.year - 1 - (self.year - 1) % YEARS_PER_CENTURY + YEARS_PER_CENTURY + + return self.set(year, 12, 31, 23, 59, 59, 999999) + + def _start_of_week(self) -> DateTime: + """ + Reset the date to the first day of the week + and the time to 00:00:00. + """ + dt = self + + if self.day_of_week != pendulum._WEEK_STARTS_AT: + dt = self.previous(pendulum._WEEK_STARTS_AT) + + return dt.start_of("day") + + def _end_of_week(self) -> DateTime: + """ + Reset the date to the last day of the week + and the time to 23:59:59. + """ + dt = self + + if self.day_of_week != pendulum._WEEK_ENDS_AT: + dt = self.next(pendulum._WEEK_ENDS_AT) + + return dt.end_of("day") + + def next(self, day_of_week: int | None = None, keep_time: bool = False) -> DateTime: + """ + Modify to the next occurrence of a given day of the week. + If no day_of_week is provided, modify to the next occurrence + of the current day of the week. Use the supplied consts + to indicate the desired day_of_week, ex. DateTime.MONDAY. + """ + if day_of_week is None: + day_of_week = self.day_of_week + + if day_of_week < SUNDAY or day_of_week > SATURDAY: + raise ValueError("Invalid day of week") + + if keep_time: + dt = self + else: + dt = self.start_of("day") + + dt = dt.add(days=1) + while dt.day_of_week != day_of_week: + dt = dt.add(days=1) + + return dt + + def previous( + self, day_of_week: int | None = None, keep_time: bool = False + ) -> DateTime: + """ + Modify to the previous occurrence of a given day of the week. + If no day_of_week is provided, modify to the previous occurrence + of the current day of the week. Use the supplied consts + to indicate the desired day_of_week, ex. DateTime.MONDAY. + """ + if day_of_week is None: + day_of_week = self.day_of_week + + if day_of_week < SUNDAY or day_of_week > SATURDAY: + raise ValueError("Invalid day of week") + + if keep_time: + dt = self + else: + dt = self.start_of("day") + + dt = dt.subtract(days=1) + while dt.day_of_week != day_of_week: + dt = dt.subtract(days=1) + + return dt + + def first_of(self, unit: str, day_of_week: int | None = None) -> DateTime: + """ + 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. + + 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)) + + def last_of(self, unit: str, day_of_week: int | None = None) -> DateTime: + """ + 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. + + 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)) + + def nth_of(self, unit: str, nth: int, day_of_week: int) -> DateTime: + """ + Returns a new instance set to the given occurrence + of a given day of the week in the current unit. + If the calculated occurrence is outside the scope of the current unit, + then raise an error. 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()') + + dt = cast( + Optional[DateTime], 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}" + ) + + return dt + + def _first_of_month(self, day_of_week: int | None = None) -> DateTime: + """ + Modify to the first occurrence of a given day of the week + in the current month. If no day_of_week is provided, + modify to the first day of the month. Use the supplied consts + to indicate the desired day_of_week, ex. DateTime.MONDAY. + """ + dt = self.start_of("day") + + if day_of_week is None: + return dt.set(day=1) + + month = calendar.monthcalendar(dt.year, dt.month) + + calendar_day = (day_of_week - 1) % 7 + + if month[0][calendar_day] > 0: + day_of_month = month[0][calendar_day] + else: + day_of_month = month[1][calendar_day] + + return dt.set(day=day_of_month) + + def _last_of_month(self, day_of_week: int | None = None) -> DateTime: + """ + Modify to the last occurrence of a given day of the week + in the current month. If no day_of_week is provided, + modify to the last day of the month. Use the supplied consts + to indicate the desired day_of_week, ex. DateTime.MONDAY. + """ + dt = self.start_of("day") + + if day_of_week is None: + return dt.set(day=self.days_in_month) + + month = calendar.monthcalendar(dt.year, dt.month) + + calendar_day = (day_of_week - 1) % 7 + + if month[-1][calendar_day] > 0: + day_of_month = month[-1][calendar_day] + else: + day_of_month = month[-2][calendar_day] + + return dt.set(day=day_of_month) + + def _nth_of_month( + self, nth: int, day_of_week: int | None = None + ) -> DateTime | None: + """ + Modify to the given occurrence of a given day of the week + in the current month. If the calculated occurrence is outside, + the scope of the current month, then return False and no + modifications are made. Use the supplied consts + to indicate the desired day_of_week, ex. DateTime.MONDAY. + """ + if nth == 1: + return self.first_of("month", day_of_week) + + dt = self.first_of("month") + check = dt.format("%Y-%M") + for _ in range(nth - (1 if dt.day_of_week == day_of_week else 0)): + dt = dt.next(day_of_week) + + if dt.format("%Y-%M") == check: + return self.set(day=dt.day).start_of("day") + + return None + + def _first_of_quarter(self, day_of_week: int | None = None) -> DateTime: + """ + Modify to the first occurrence of a given day of the week + in the current quarter. If no day_of_week is provided, + modify to the first day of the quarter. Use the supplied consts + to indicate the desired day_of_week, ex. DateTime.MONDAY. + """ + return self.on(self.year, self.quarter * 3 - 2, 1).first_of( + "month", day_of_week + ) + + def _last_of_quarter(self, day_of_week: int | None = None) -> DateTime: + """ + Modify to the last occurrence of a given day of the week + in the current quarter. If no day_of_week is provided, + modify to the last day of the quarter. Use the supplied consts + to indicate the desired day_of_week, ex. DateTime.MONDAY. + """ + 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: + """ + Modify to the given occurrence of a given day of the week + in the current quarter. If the calculated occurrence is outside, + the scope of the current quarter, then return False and no + modifications are made. Use the supplied consts + to indicate the desired day_of_week, ex. DateTime.MONDAY. + """ + if nth == 1: + return self.first_of("quarter", day_of_week) + + dt = self.set(day=1, month=self.quarter * 3) + last_month = dt.month + year = dt.year + dt = dt.first_of("quarter") + for _ in range(nth - (1 if dt.day_of_week == day_of_week else 0)): + dt = dt.next(day_of_week) + + if last_month < dt.month or year != dt.year: + return None + + return self.on(self.year, dt.month, dt.day).start_of("day") + + def _first_of_year(self, day_of_week: int | None = None) -> DateTime: + """ + Modify to the first occurrence of a given day of the week + in the current year. If no day_of_week is provided, + modify to the first day of the year. Use the supplied consts + to indicate the desired day_of_week, ex. DateTime.MONDAY. + """ + return self.set(month=1).first_of("month", day_of_week) + + def _last_of_year(self, day_of_week: int | None = None) -> DateTime: + """ + Modify to the last occurrence of a given day of the week + in the current year. If no day_of_week is provided, + modify to the last day of the year. Use the supplied consts + to indicate the desired day_of_week, ex. DateTime.MONDAY. + """ + 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: + """ + Modify to the given occurrence of a given day of the week + in the current year. If the calculated occurrence is outside, + the scope of the current year, then return False and no + modifications are made. Use the supplied consts + to indicate the desired day_of_week, ex. DateTime.MONDAY. + """ + if nth == 1: + return self.first_of("year", day_of_week) + + dt = self.first_of("year") + year = dt.year + for _ in range(nth - (1 if dt.day_of_week == day_of_week else 0)): + dt = dt.next(day_of_week) + + if year != dt.year: + return None + + return self.on(self.year, dt.month, dt.day).start_of("day") + + def average( # type: ignore[override] + self, dt: datetime.datetime | None = None + ) -> DateTime: + """ + Modify the current instance to the average + of a given instance (default now) and the current instance. + """ + if dt is None: + dt = self.now(self.tz) + + diff = self.diff(dt, False) + return self.add( + microseconds=(diff.in_seconds() * 1000000 + diff.microseconds) // 2 + ) + + @overload # type: ignore[override] + def __sub__(self, other: datetime.timedelta) -> DateTime: + ... + + @overload + def __sub__(self, other: DateTime) -> Interval: + ... + + def __sub__( + self, other: datetime.datetime | datetime.timedelta + ) -> DateTime | Interval: + if isinstance(other, datetime.timedelta): + return self._subtract_timedelta(other) + + if not isinstance(other, datetime.datetime): + return NotImplemented + + if not isinstance(other, self.__class__): + if other.tzinfo is None: + other = pendulum.naive( + other.year, + other.month, + other.day, + other.hour, + other.minute, + other.second, + other.microsecond, + ) + else: + other = pendulum.instance(other) + + return other.diff(self, False) + + def __rsub__(self, other: datetime.datetime) -> Interval: + if not isinstance(other, datetime.datetime): + return NotImplemented + + if not isinstance(other, self.__class__): + if other.tzinfo is None: + other = pendulum.naive( + other.year, + other.month, + other.day, + other.hour, + other.minute, + other.second, + other.microsecond, + ) + else: + other = pendulum.instance(other) + + return self.diff(other, False) + + def __add__(self, other: datetime.timedelta) -> DateTime: + 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)) + + return self._add_timedelta_(other) + + def __radd__(self, other: datetime.timedelta) -> DateTime: + return self.__add__(other) + + # Native methods override + + @classmethod + def fromtimestamp(cls, t: float, tz: datetime.tzinfo | None = None) -> DateTime: + tzinfo = pendulum._safe_timezone(tz) + + return pendulum.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) + + @classmethod + def fromordinal(cls, n: int) -> DateTime: + return pendulum.instance(datetime.datetime.fromordinal(n), tz=None) + + @classmethod + def combine( + cls, + date: datetime.date, + time: datetime.time, + tzinfo: datetime.tzinfo | None = None, + ) -> DateTime: + return pendulum.instance(datetime.datetime.combine(date, time), tz=tzinfo) + + def astimezone(self, tz: datetime.tzinfo | None = None) -> DateTime: + dt = super().astimezone(tz) + + return self.__class__( + dt.year, + dt.month, + dt.day, + dt.hour, + dt.minute, + dt.second, + dt.microsecond, + fold=dt.fold, + tzinfo=dt.tzinfo, + ) + + 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, + tzinfo: bool | datetime.tzinfo | Literal[True] | None = True, + fold: int | None = None, + ) -> DateTime: + if year is None: + year = self.year + if month is None: + month = self.month + if day is None: + day = self.day + if hour is None: + hour = self.hour + if minute is None: + minute = self.minute + if second is None: + second = self.second + if microsecond is None: + microsecond = self.microsecond + if tzinfo is True: + tzinfo = self.tzinfo + if fold is None: + fold = self.fold + + if tzinfo is not None: + tzinfo = pendulum._safe_timezone(tzinfo) + + return DateTime.create( + year, + month, + day, + hour, + minute, + second, + microsecond, + tz=tzinfo, + fold=fold, + ) + + def __getnewargs__(self) -> tuple[DateTime]: + return (self,) + + def _getstate( + self, protocol: int = 3 + ) -> tuple[int, int, int, int, int, int, int, datetime.tzinfo | None]: + return ( + self.year, + self.month, + self.day, + self.hour, + self.minute, + self.second, + self.microsecond, + self.tzinfo, + ) + + def __reduce__( + self, + ) -> tuple[ + type[DateTime], 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 + ) -> tuple[ + type[DateTime], tuple[int, int, int, int, int, int, int, datetime.tzinfo | None] + ]: + return self.__class__, self._getstate(protocol) + + 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 + dt = datetime.datetime( + self.year, + self.month, + self.day, + self.hour, + self.minute, + self.second, + self.microsecond, + tzinfo=self.tz, + fold=self.fold, + ) + + 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] diff --git a/pendulum/duration.py b/pendulum/duration.py index 18d0c7f..a3a68b1 100644 --- a/pendulum/duration.py +++ b/pendulum/duration.py @@ -1,479 +1,502 @@ -from __future__ import absolute_import -from __future__ import division - -from datetime import timedelta - -import pendulum - -from pendulum.utils._compat import PYPY -from pendulum.utils._compat import decode - -from .constants import SECONDS_PER_DAY -from .constants import SECONDS_PER_HOUR -from .constants import SECONDS_PER_MINUTE -from .constants import US_PER_SECOND - - -def _divide_and_round(a, b): - """divide a by b and round result to the nearest integer - - When the ratio is exactly half-way between two integers, - the even integer is returned. - """ - # Based on the reference implementation for divmod_near - # in Objects/longobject.c. - q, r = divmod(a, b) - # round up if either r / b > 0.5, or r / b == 0.5 and q is odd. - # The expression r / b > 0.5 is equivalent to 2 * r > b if b is - # positive, 2 * r < b if b negative. - r *= 2 - greater_than_half = r > b if b > 0 else r < b - if greater_than_half or r == b and q % 2 == 1: - q += 1 - - return q - - -class Duration(timedelta): - """ - Replacement for the standard timedelta class. - - Provides several improvements over the base class. - """ - - _y = None - _m = None - _w = None - _d = None - _h = None - _i = None - _s = None - _invert = None - - def __new__( - cls, - days=0, - seconds=0, - microseconds=0, - milliseconds=0, - minutes=0, - hours=0, - weeks=0, - years=0, - months=0, - ): - if not isinstance(years, int) or not isinstance(months, int): - raise ValueError("Float year and months are not supported") - - self = timedelta.__new__( - cls, - days + years * 365 + months * 30, - seconds, - microseconds, - milliseconds, - minutes, - hours, - weeks, - ) - - # Intuitive normalization - total = self.total_seconds() - (years * 365 + months * 30) * SECONDS_PER_DAY - self._total = total - - m = 1 - if total < 0: - m = -1 - - self._microseconds = round(total % m * 1e6) - self._seconds = abs(int(total)) % SECONDS_PER_DAY * m - - _days = abs(int(total)) // SECONDS_PER_DAY * m - self._days = _days - self._remaining_days = abs(_days) % 7 * m - self._weeks = abs(_days) // 7 * m - self._months = months - self._years = years - - return self - - def total_minutes(self): - return self.total_seconds() / SECONDS_PER_MINUTE - - def total_hours(self): - return self.total_seconds() / SECONDS_PER_HOUR - - def total_days(self): - return self.total_seconds() / SECONDS_PER_DAY - - def total_weeks(self): - return self.total_days() / 7 - - if PYPY: - - def total_seconds(self): - days = 0 - - if hasattr(self, "_years"): - days += self._years * 365 - - if hasattr(self, "_months"): - days += self._months * 30 - - if hasattr(self, "_remaining_days"): - days += self._weeks * 7 + self._remaining_days - else: - days += self._days - - return ( - (days * SECONDS_PER_DAY + self._seconds) * US_PER_SECOND - + self._microseconds - ) / US_PER_SECOND - - @property - def years(self): - return self._years - - @property - def months(self): - return self._months - - @property - def weeks(self): - return self._weeks - - if PYPY: - - @property - def days(self): - return self._years * 365 + self._months * 30 + self._days - - @property - def remaining_days(self): - return self._remaining_days - - @property - def hours(self): - if self._h is None: - seconds = self._seconds - self._h = 0 - if abs(seconds) >= 3600: - self._h = (abs(seconds) // 3600 % 24) * self._sign(seconds) - - return self._h - - @property - def minutes(self): - if self._i is None: - seconds = self._seconds - self._i = 0 - if abs(seconds) >= 60: - self._i = (abs(seconds) // 60 % 60) * self._sign(seconds) - - return self._i - - @property - def seconds(self): - return self._seconds - - @property - def remaining_seconds(self): - if self._s is None: - self._s = self._seconds - self._s = abs(self._s) % 60 * self._sign(self._s) - - return self._s - - @property - def microseconds(self): - return self._microseconds - - @property - def invert(self): - if self._invert is None: - self._invert = self.total_seconds() < 0 - - return self._invert - - def in_weeks(self): - return int(self.total_weeks()) - - def in_days(self): - return int(self.total_days()) - - def in_hours(self): - return int(self.total_hours()) - - def in_minutes(self): - return int(self.total_minutes()) - - def in_seconds(self): - return int(self.total_seconds()) - - def in_words(self, locale=None, separator=" "): - """ - Get the current interval in words in the current locale. - - Ex: 6 jours 23 heures 58 minutes - - :param locale: The locale to use. Defaults to current locale. - :type locale: str - - :param separator: The separator to use between each unit - :type separator: str - - :rtype: str - """ - periods = [ - ("year", self.years), - ("month", self.months), - ("week", self.weeks), - ("day", self.remaining_days), - ("hour", self.hours), - ("minute", self.minutes), - ("second", self.remaining_seconds), - ] - - if locale is None: - locale = pendulum.get_locale() - - locale = pendulum.locale(locale) - parts = [] - for period in periods: - unit, count = period - if abs(count) > 0: - translation = locale.translation( - "units.{}.{}".format(unit, locale.plural(abs(count))) - ) - parts.append(translation.format(count)) - - if not parts: - if abs(self.microseconds) > 0: - unit = "units.second.{}".format(locale.plural(1)) - count = "{:.2f}".format(abs(self.microseconds) / 1e6) - else: - unit = "units.microsecond.{}".format(locale.plural(0)) - count = 0 - translation = locale.translation(unit) - parts.append(translation.format(count)) - - return decode(separator.join(parts)) - - def _sign(self, value): - if value < 0: - return -1 - - return 1 - - def as_timedelta(self): - """ - Return the interval as a native timedelta. - - :rtype: timedelta - """ - return timedelta(seconds=self.total_seconds()) - - def __str__(self): - return self.in_words() - - def __repr__(self): - rep = "{}(".format(self.__class__.__name__) - - if self._years: - rep += "years={}, ".format(self._years) - - if self._months: - rep += "months={}, ".format(self._months) - - if self._weeks: - rep += "weeks={}, ".format(self._weeks) - - if self._days: - rep += "days={}, ".format(self._remaining_days) - - if self.hours: - rep += "hours={}, ".format(self.hours) - - if self.minutes: - rep += "minutes={}, ".format(self.minutes) - - if self.remaining_seconds: - rep += "seconds={}, ".format(self.remaining_seconds) - - if self.microseconds: - rep += "microseconds={}, ".format(self.microseconds) - - rep += ")" - - return rep.replace(", )", ")") - - def __add__(self, other): - if isinstance(other, timedelta): - return self.__class__(seconds=self.total_seconds() + other.total_seconds()) - - return NotImplemented - - __radd__ = __add__ - - def __sub__(self, other): - if isinstance(other, timedelta): - return self.__class__(seconds=self.total_seconds() - other.total_seconds()) - - return NotImplemented - - def __neg__(self): - return self.__class__( - years=-self._years, - months=-self._months, - weeks=-self._weeks, - days=-self._remaining_days, - seconds=-self._seconds, - microseconds=-self._microseconds, - ) - - def _to_microseconds(self): - return (self._days * (24 * 3600) + self._seconds) * 1000000 + self._microseconds - - def __mul__(self, other): - if isinstance(other, int): - return self.__class__( - years=self._years * other, - months=self._months * other, - seconds=self._total * other, - ) - - if isinstance(other, float): - usec = self._to_microseconds() - a, b = other.as_integer_ratio() - - return self.__class__(0, 0, _divide_and_round(usec * a, b)) - - return NotImplemented - - __rmul__ = __mul__ - - def __floordiv__(self, other): - if not isinstance(other, (int, timedelta)): - return NotImplemented - - usec = self._to_microseconds() - if isinstance(other, timedelta): - return usec // other._to_microseconds() - - if isinstance(other, int): - return self.__class__( - 0, - 0, - usec // other, - years=self._years // other, - months=self._months // other, - ) - - def __truediv__(self, other): - if not isinstance(other, (int, float, timedelta)): - return NotImplemented - - usec = self._to_microseconds() - if isinstance(other, timedelta): - return usec / other._to_microseconds() - - if isinstance(other, int): - return self.__class__( - 0, - 0, - _divide_and_round(usec, other), - years=_divide_and_round(self._years, other), - months=_divide_and_round(self._months, other), - ) - - if isinstance(other, float): - a, b = other.as_integer_ratio() - - return self.__class__( - 0, - 0, - _divide_and_round(b * usec, a), - years=_divide_and_round(self._years * b, a), - months=_divide_and_round(self._months, other), - ) - - __div__ = __floordiv__ - - def __mod__(self, other): - if isinstance(other, timedelta): - r = self._to_microseconds() % other._to_microseconds() - - return self.__class__(0, 0, r) - - return NotImplemented - - def __divmod__(self, other): - if isinstance(other, timedelta): - q, r = divmod(self._to_microseconds(), other._to_microseconds()) - - return q, self.__class__(0, 0, r) - - return NotImplemented - - -Duration.min = Duration(days=-999999999) -Duration.max = Duration( - days=999999999, hours=23, minutes=59, seconds=59, microseconds=999999 -) -Duration.resolution = Duration(microseconds=1) - - -class AbsoluteDuration(Duration): - """ - Duration that expresses a time difference in absolute values. - """ - - def __new__( - cls, - days=0, - seconds=0, - microseconds=0, - milliseconds=0, - minutes=0, - hours=0, - weeks=0, - years=0, - months=0, - ): - if not isinstance(years, int) or not isinstance(months, int): - raise ValueError("Float year and months are not supported") - - self = timedelta.__new__( - cls, days, seconds, microseconds, milliseconds, minutes, hours, weeks - ) - - # We need to compute the total_seconds() value - # on a native timedelta object - delta = timedelta( - days, seconds, microseconds, milliseconds, minutes, hours, weeks - ) - - # Intuitive normalization - self._total = delta.total_seconds() - total = abs(self._total) - - self._microseconds = round(total % 1 * 1e6) - self._seconds = int(total) % SECONDS_PER_DAY - - days = int(total) // SECONDS_PER_DAY - self._days = abs(days + years * 365 + months * 30) - self._remaining_days = days % 7 - self._weeks = days // 7 - self._months = abs(months) - self._years = abs(years) - - return self - - def total_seconds(self): - return abs(self._total) - - @property - def invert(self): - if self._invert is None: - self._invert = self._total < 0 - - return self._invert +from __future__ import annotations + +from datetime import timedelta +from typing import cast +from typing import overload + +import pendulum + +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 US_PER_SECOND +from pendulum.utils._compat import PYPY + + +def _divide_and_round(a: float, b: float) -> int: + """divide a by b and round result to the nearest integer + + When the ratio is exactly half-way between two integers, + the even integer is returned. + """ + # Based on the reference implementation for divmod_near + # in Objects/longobject.c. + q, r = divmod(a, b) + + # The output of divmod() is either a float or an int, + # but we always want it to be an int. + q = int(q) + + # round up if either r / b > 0.5, or r / b == 0.5 and q is odd. + # The expression r / b > 0.5 is equivalent to 2 * r > b if b is + # positive, 2 * r < b if b negative. + r *= 2 + greater_than_half = r > b if b > 0 else r < b + if greater_than_half or r == b and q % 2 == 1: + q += 1 + + return q + + +class Duration(timedelta): + """ + Replacement for the standard timedelta class. + + Provides several improvements over the base class. + """ + + _total: float = 0 + _years: int = 0 + _months: int = 0 + _weeks: int = 0 + _days: int = 0 + _remaining_days: int = 0 + _seconds: int = 0 + _microseconds: int = 0 + + _y = None + _m = None + _w = None + _d = None + _h = None + _i = None + _s = None + _invert = None + + def __new__( + cls, + days: float = 0, + seconds: float = 0, + microseconds: float = 0, + milliseconds: float = 0, + minutes: float = 0, + hours: float = 0, + weeks: float = 0, + years: float = 0, + months: float = 0, + ) -> Duration: + if not isinstance(years, int) or not isinstance(months, int): + raise ValueError("Float year and months are not supported") + + self = timedelta.__new__( + cls, + days + years * 365 + months * 30, + seconds, + microseconds, + milliseconds, + minutes, + hours, + weeks, + ) + + # Intuitive normalization + total = self.total_seconds() - (years * 365 + months * 30) * SECONDS_PER_DAY + self._total = total + + m = 1 + if total < 0: + m = -1 + + self._microseconds = round(total % m * 1e6) + self._seconds = abs(int(total)) % SECONDS_PER_DAY * m + + _days = abs(int(total)) // SECONDS_PER_DAY * m + self._days = _days + self._remaining_days = abs(_days) % 7 * m + self._weeks = abs(_days) // 7 * m + self._months = months + self._years = years + + return self + + def total_minutes(self) -> float: + return self.total_seconds() / SECONDS_PER_MINUTE + + def total_hours(self) -> float: + return self.total_seconds() / SECONDS_PER_HOUR + + def total_days(self) -> float: + return self.total_seconds() / SECONDS_PER_DAY + + def total_weeks(self) -> float: + return self.total_days() / 7 + + if PYPY: + + def total_seconds(self) -> float: + days = 0 + + if hasattr(self, "_years"): + days += self._years * 365 + + if hasattr(self, "_months"): + days += self._months * 30 + + if hasattr(self, "_remaining_days"): + days += self._weeks * 7 + self._remaining_days + else: + days += self._days + + return ( + (days * SECONDS_PER_DAY + self._seconds) * US_PER_SECOND + + self._microseconds + ) / US_PER_SECOND + + @property + def years(self) -> int: + return self._years + + @property + def months(self) -> int: + return self._months + + @property + def weeks(self) -> int: + return self._weeks + + if PYPY: + + @property + def days(self) -> int: + return self._years * 365 + self._months * 30 + self._days + + @property + def remaining_days(self) -> int: + return self._remaining_days + + @property + def hours(self) -> int: + if self._h is None: + seconds = self._seconds + self._h = 0 + if abs(seconds) >= 3600: + self._h = (abs(seconds) // 3600 % 24) * self._sign(seconds) + + return self._h + + @property + def minutes(self) -> int: + if self._i is None: + seconds = self._seconds + self._i = 0 + if abs(seconds) >= 60: + self._i = (abs(seconds) // 60 % 60) * self._sign(seconds) + + return self._i + + @property + def seconds(self) -> int: + return self._seconds + + @property + def remaining_seconds(self) -> int: + if self._s is None: + self._s = self._seconds + self._s = abs(self._s) % 60 * self._sign(self._s) + + return self._s + + @property + def microseconds(self) -> int: + return self._microseconds + + @property + def invert(self) -> bool: + if self._invert is None: + self._invert = self.total_seconds() < 0 + + return self._invert + + def in_weeks(self) -> int: + return int(self.total_weeks()) + + def in_days(self) -> int: + return int(self.total_days()) + + def in_hours(self) -> int: + return int(self.total_hours()) + + def in_minutes(self) -> int: + return int(self.total_minutes()) + + def in_seconds(self) -> int: + return int(self.total_seconds()) + + def in_words(self, locale: str | None = None, separator: str = " ") -> str: + """ + Get the current interval in words in the current locale. + + Ex: 6 jours 23 heures 58 minutes + + :param locale: The locale to use. Defaults to current locale. + :param separator: The separator to use between each unit + """ + periods = [ + ("year", self.years), + ("month", self.months), + ("week", self.weeks), + ("day", self.remaining_days), + ("hour", self.hours), + ("minute", self.minutes), + ("second", self.remaining_seconds), + ] + + if locale is None: + locale = pendulum.get_locale() + + loaded_locale = pendulum.locale(locale) + + parts = [] + for period in periods: + unit, period_count = period + if abs(period_count) > 0: + translation = loaded_locale.translation( + f"units.{unit}.{loaded_locale.plural(abs(period_count))}" + ) + parts.append(translation.format(period_count)) + + if not parts: + count: int | str = 0 + if abs(self.microseconds) > 0: + unit = f"units.second.{loaded_locale.plural(1)}" + count = f"{abs(self.microseconds) / 1e6:.2f}" + else: + unit = f"units.microsecond.{loaded_locale.plural(0)}" + translation = loaded_locale.translation(unit) + parts.append(translation.format(count)) + + return separator.join(parts) + + def _sign(self, value: float) -> int: + if value < 0: + return -1 + + return 1 + + def as_timedelta(self) -> timedelta: + """ + Return the interval as a native timedelta. + """ + return timedelta(seconds=self.total_seconds()) + + def __str__(self) -> str: + return self.in_words() + + def __repr__(self) -> str: + rep = f"{self.__class__.__name__}(" + + if self._years: + rep += f"years={self._years}, " + + if self._months: + rep += f"months={self._months}, " + + if self._weeks: + rep += f"weeks={self._weeks}, " + + if self._days: + rep += f"days={self._remaining_days}, " + + if self.hours: + rep += f"hours={self.hours}, " + + if self.minutes: + rep += f"minutes={self.minutes}, " + + if self.remaining_seconds: + rep += f"seconds={self.remaining_seconds}, " + + if self.microseconds: + rep += f"microseconds={self.microseconds}, " + + rep += ")" + + return rep.replace(", )", ")") + + def __add__(self, other: timedelta) -> Duration: + if isinstance(other, timedelta): + return self.__class__(seconds=self.total_seconds() + other.total_seconds()) + + return NotImplemented + + __radd__ = __add__ + + def __sub__(self, other: timedelta) -> Duration: + if isinstance(other, timedelta): + return self.__class__(seconds=self.total_seconds() - other.total_seconds()) + + return NotImplemented + + def __neg__(self) -> Duration: + return self.__class__( + years=-self._years, + months=-self._months, + weeks=-self._weeks, + days=-self._remaining_days, + seconds=-self._seconds, + microseconds=-self._microseconds, + ) + + def _to_microseconds(self) -> int: + return (self._days * (24 * 3600) + self._seconds) * 1000000 + self._microseconds + + def __mul__(self, other: int | float) -> Duration: + if isinstance(other, int): + return self.__class__( + years=self._years * other, + months=self._months * other, + seconds=self._total * other, + ) + + if isinstance(other, float): + usec = self._to_microseconds() + a, b = other.as_integer_ratio() + + return self.__class__(0, 0, _divide_and_round(usec * a, b)) + + return NotImplemented + + __rmul__ = __mul__ + + @overload + def __floordiv__(self, other: timedelta) -> int: + ... + + @overload + def __floordiv__(self, other: int) -> Duration: + ... + + def __floordiv__(self, other: int | timedelta) -> int | Duration: + if not isinstance(other, (int, timedelta)): + return NotImplemented + + usec = self._to_microseconds() + if isinstance(other, timedelta): + return cast(int, usec // other._to_microseconds()) # type: ignore[attr-defined] + + if isinstance(other, int): + return self.__class__( + 0, + 0, + usec // other, + years=self._years // other, + months=self._months // other, + ) + + @overload + def __truediv__(self, other: timedelta) -> float: + ... + + @overload + def __truediv__(self, other: float) -> Duration: + ... + + def __truediv__(self, other: int | float | timedelta) -> Duration | 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] + + if isinstance(other, int): + return self.__class__( + 0, + 0, + _divide_and_round(usec, other), + years=_divide_and_round(self._years, other), + months=_divide_and_round(self._months, other), + ) + + if isinstance(other, float): + a, b = other.as_integer_ratio() + + return self.__class__( + 0, + 0, + _divide_and_round(b * usec, a), + years=_divide_and_round(self._years * b, a), + months=_divide_and_round(self._months, other), + ) + + __div__ = __floordiv__ + + def __mod__(self, other: timedelta) -> Duration: + if isinstance(other, timedelta): + r = self._to_microseconds() % other._to_microseconds() # type: ignore[attr-defined] + + return self.__class__(0, 0, r) + + return NotImplemented + + 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] + + return q, self.__class__(0, 0, r) + + return NotImplemented + + +Duration.min = Duration(days=-999999999) +Duration.max = Duration( + days=999999999, hours=23, minutes=59, seconds=59, microseconds=999999 +) +Duration.resolution = Duration(microseconds=1) + + +class AbsoluteDuration(Duration): + """ + Duration that expresses a time difference in absolute values. + """ + + def __new__( + cls, + days: float = 0, + seconds: float = 0, + microseconds: float = 0, + milliseconds: float = 0, + minutes: float = 0, + hours: float = 0, + weeks: float = 0, + years: float = 0, + months: float = 0, + ) -> AbsoluteDuration: + if not isinstance(years, int) or not isinstance(months, int): + raise ValueError("Float year and months are not supported") + + self = timedelta.__new__( + cls, days, seconds, microseconds, milliseconds, minutes, hours, weeks + ) + + # We need to compute the total_seconds() value + # on a native timedelta object + delta = timedelta( + days, seconds, microseconds, milliseconds, minutes, hours, weeks + ) + + # Intuitive normalization + self._total = delta.total_seconds() + total = abs(self._total) + + self._microseconds = round(total % 1 * 1e6) + self._seconds = int(total) % SECONDS_PER_DAY + + days = int(total) // SECONDS_PER_DAY + self._days = abs(days + years * 365 + months * 30) + self._remaining_days = days % 7 + self._weeks = days // 7 + self._months = abs(months) + self._years = abs(years) + + return self + + def total_seconds(self) -> float: + return abs(self._total) + + @property + def invert(self) -> bool: + if self._invert is None: + self._invert = self._total < 0 + + return self._invert diff --git a/pendulum/exceptions.py b/pendulum/exceptions.py index 6806783..3ab4db9 100644 --- a/pendulum/exceptions.py +++ b/pendulum/exceptions.py @@ -1,6 +1,8 @@ -from .parsing.exceptions import ParserError # noqa - - -class PendulumException(Exception): - - pass +from __future__ import annotations + +from .parsing.exceptions import ParserError # noqa + + +class PendulumException(Exception): + + pass diff --git a/pendulum/formatting/__init__.py b/pendulum/formatting/__init__.py index a2b47de..975c409 100644 --- a/pendulum/formatting/__init__.py +++ b/pendulum/formatting/__init__.py @@ -1,4 +1,5 @@ -from .formatter import Formatter - - -__all__ = ["Formatter"] +from __future__ import annotations + +from pendulum.formatting.formatter import Formatter + +__all__ = ["Formatter"] diff --git a/pendulum/formatting/difference_formatter.py b/pendulum/formatting/difference_formatter.py index 3243089..dad219d 100644 --- a/pendulum/formatting/difference_formatter.py +++ b/pendulum/formatting/difference_formatter.py @@ -1,153 +1,146 @@ -import typing - -import pendulum - -from pendulum.utils._compat import decode - -from ..locales.locale import Locale - - -class DifferenceFormatter(object): - """ - Handles formatting differences in text. - """ - - def __init__(self, locale="en"): - self._locale = Locale.load(locale) - - def format( - self, diff, is_now=True, absolute=False, locale=None - ): # type: (pendulum.Period, bool, bool, typing.Optional[str]) -> str - """ - Formats a difference. - - :param diff: The difference to format - :type diff: pendulum.period.Period - - :param is_now: Whether the difference includes now - :type is_now: bool - - :param absolute: Whether it's an absolute difference or not - :type absolute: bool - - :param locale: The locale to use - :type locale: str or None - - :rtype: str - """ - if locale is None: - locale = self._locale - else: - locale = Locale.load(locale) - - count = diff.remaining_seconds - - if diff.years > 0: - unit = "year" - count = diff.years - - if diff.months > 6: - count += 1 - elif diff.months == 11 and (diff.weeks * 7 + diff.remaining_days) > 15: - unit = "year" - count = 1 - elif diff.months > 0: - unit = "month" - count = diff.months - - if (diff.weeks * 7 + diff.remaining_days) >= 27: - count += 1 - elif diff.weeks > 0: - unit = "week" - count = diff.weeks - - if diff.remaining_days > 3: - count += 1 - elif diff.remaining_days > 0: - unit = "day" - count = diff.remaining_days - - if diff.hours >= 22: - count += 1 - elif diff.hours > 0: - unit = "hour" - count = diff.hours - elif diff.minutes > 0: - unit = "minute" - count = diff.minutes - elif 10 < diff.remaining_seconds <= 59: - unit = "second" - count = diff.remaining_seconds - else: - # We check if the "a few seconds" unit exists - time = locale.get("custom.units.few_second") - if time is not None: - if absolute: - return time - - key = "custom" - is_future = diff.invert - if is_now: - if is_future: - key += ".from_now" - else: - key += ".ago" - else: - if is_future: - key += ".after" - else: - key += ".before" - - return locale.get(key).format(time) - else: - unit = "second" - count = diff.remaining_seconds - - if count == 0: - count = 1 - - if absolute: - key = "translations.units.{}".format(unit) - else: - is_future = diff.invert - - if is_now: - # Relative to now, so we can use - # the CLDR data - key = "translations.relative.{}".format(unit) - - if is_future: - key += ".future" - else: - key += ".past" - else: - # Absolute comparison - # So we have to use the custom locale data - - # Checking for special pluralization rules - key = "custom.units_relative" - if is_future: - key += ".{}.future".format(unit) - else: - key += ".{}.past".format(unit) - - trans = locale.get(key) - if not trans: - # No special rule - time = locale.get( - "translations.units.{}.{}".format(unit, locale.plural(count)) - ).format(count) - else: - time = trans[locale.plural(count)].format(count) - - key = "custom" - if is_future: - key += ".after" - else: - key += ".before" - - return locale.get(key).format(decode(time)) - - key += ".{}".format(locale.plural(count)) - - return decode(locale.get(key).format(count)) +from __future__ import annotations + +import typing as t + +from pendulum.locales.locale import Locale + +if t.TYPE_CHECKING: + from pendulum import Duration + + +class DifferenceFormatter: + """ + Handles formatting differences in text. + """ + + def __init__(self, locale: str = "en") -> None: + self._locale = Locale.load(locale) + + def format( + self, + diff: Duration, + is_now: bool = True, + absolute: bool = False, + locale: str | Locale | None = None, + ) -> str: + """ + Formats a difference. + + :param diff: The difference to format + :param is_now: Whether the difference includes now + :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) + + if diff.years > 0: + unit = "year" + count = diff.years + + if diff.months > 6: + count += 1 + elif diff.months == 11 and (diff.weeks * 7 + diff.remaining_days) > 15: + unit = "year" + count = 1 + elif diff.months > 0: + unit = "month" + count = diff.months + + if (diff.weeks * 7 + diff.remaining_days) >= 27: + count += 1 + elif diff.weeks > 0: + unit = "week" + count = diff.weeks + + if diff.remaining_days > 3: + count += 1 + elif diff.remaining_days > 0: + unit = "day" + count = diff.remaining_days + + if diff.hours >= 22: + count += 1 + elif diff.hours > 0: + unit = "hour" + count = diff.hours + elif diff.minutes > 0: + unit = "minute" + count = diff.minutes + elif 10 < diff.remaining_seconds <= 59: + unit = "second" + count = diff.remaining_seconds + else: + # We check if the "a few seconds" unit exists + time = locale.get("custom.units.few_second") + if time is not None: + if absolute: + return t.cast(str, time) + + key = "custom" + is_future = diff.invert + if is_now: + if is_future: + key += ".from_now" + else: + key += ".ago" + else: + if is_future: + key += ".after" + else: + key += ".before" + + return t.cast(str, locale.get(key).format(time)) + else: + unit = "second" + count = diff.remaining_seconds + + if count == 0: + count = 1 + + if absolute: + key = f"translations.units.{unit}" + else: + is_future = diff.invert + + if is_now: + # Relative to now, so we can use + # the CLDR data + key = f"translations.relative.{unit}" + + if is_future: + key += ".future" + else: + key += ".past" + else: + # Absolute comparison + # So we have to use the custom locale data + + # Checking for special pluralization rules + key = "custom.units_relative" + if is_future: + key += f".{unit}.future" + else: + key += f".{unit}.past" + + trans = locale.get(key) + if not trans: + # No special rule + key = f"translations.units.{unit}.{locale.plural(count)}" + time = locale.get(key).format(count) + else: + time = trans[locale.plural(count)].format(count) + + key = "custom" + if is_future: + key += ".after" + else: + key += ".before" + + return t.cast(str, locale.get(key).format(time)) + + key += f".{locale.plural(count)}" + + return t.cast(str, locale.get(key).format(count)) diff --git a/pendulum/formatting/formatter.py b/pendulum/formatting/formatter.py index 4e493d0..f91d5d9 100644 --- a/pendulum/formatting/formatter.py +++ b/pendulum/formatting/formatter.py @@ -1,685 +1,683 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -import datetime -import re -import typing - -import pendulum - -from pendulum.locales.locale import Locale -from pendulum.utils._compat import decode - - -_MATCH_1 = r"\d" -_MATCH_2 = r"\d\d" -_MATCH_3 = r"\d{3}" -_MATCH_4 = r"\d{4}" -_MATCH_6 = r"[+-]?\d{6}" -_MATCH_1_TO_2 = r"\d\d?" -_MATCH_1_TO_2_LEFT_PAD = r"[0-9 ]\d?" -_MATCH_1_TO_3 = r"\d{1,3}" -_MATCH_1_TO_4 = r"\d{1,4}" -_MATCH_1_TO_6 = r"[+-]?\d{1,6}" -_MATCH_3_TO_4 = r"\d{3}\d?" -_MATCH_5_TO_6 = r"\d{5}\d?" -_MATCH_UNSIGNED = r"\d+" -_MATCH_SIGNED = r"[+-]?\d+" -_MATCH_OFFSET = r"[Zz]|[+-]\d\d:?\d\d" -_MATCH_SHORT_OFFSET = r"[Zz]|[+-]\d\d(?::?\d\d)?" -_MATCH_TIMESTAMP = r"[+-]?\d+(\.\d{1,6})?" -_MATCH_WORD = ( - "(?i)[0-9]*" - "['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+" - r"|[\u0600-\u06FF/]+(\s*?[\u0600-\u06FF]+){1,2}" -) -_MATCH_TIMEZONE = "[A-Za-z0-9-+]+(/[A-Za-z0-9-+_]+)?" - - -class Formatter: - - _TOKENS = ( - r"\[([^\[]*)\]|\\(.)|" - "(" - "Mo|MM?M?M?" - "|Do|DDDo|DD?D?D?|ddd?d?|do?" - "|E{1,4}" - "|w[o|w]?|W[o|W]?|Qo?" - "|YYYY|YY|Y" - "|gg(ggg?)?|GG(GGG?)?" - "|a|A" - "|hh?|HH?|kk?" - "|mm?|ss?|S{1,9}" - "|x|X" - "|zz?|ZZ?" - "|LTS|LT|LL?L?L?" - ")" - ) - - _FORMAT_RE = re.compile(_TOKENS) - - _FROM_FORMAT_RE = re.compile(r"(? str - """ - Formats a DateTime instance with a given format and locale. - - :param dt: The instance to format - :type dt: pendulum.DateTime - - :param fmt: The format to use - :type fmt: str - - :param locale: The locale to use - :type locale: str or Locale or None - - :rtype: str - """ - if not locale: - locale = pendulum.get_locale() - - locale = Locale.load(locale) - - result = self._FORMAT_RE.sub( - lambda m: m.group(1) - if m.group(1) - else m.group(2) - if m.group(2) - else self._format_token(dt, m.group(3), locale), - fmt, - ) - - return decode(result) - - def _format_token( - self, dt, token, locale - ): # type: (pendulum.DateTime, str, Locale) -> str - """ - Formats a DateTime instance with a given token and locale. - - :param dt: The instance to format - :type dt: pendulum.DateTime - - :param token: The token to use - :type token: str - - :param locale: The locale to use - :type locale: Locale - - :rtype: str - """ - if token in self._DATE_FORMATS: - fmt = locale.get("custom.date_formats.{}".format(token)) - if fmt is None: - fmt = self._DEFAULT_DATE_FORMATS[token] - - return self.format(dt, fmt, locale) - - if token in self._LOCALIZABLE_TOKENS: - return self._format_localizable_token(dt, token, locale) - - if token in self._TOKENS_RULES: - return self._TOKENS_RULES[token](dt) - - # Timezone - if token in ["ZZ", "Z"]: - if dt.tzinfo is None: - return "" - - separator = ":" if token == "Z" else "" - offset = dt.utcoffset() or datetime.timedelta() - minutes = offset.total_seconds() / 60 - - if minutes >= 0: - sign = "+" - else: - sign = "-" - - hour, minute = divmod(abs(int(minutes)), 60) - - return "{}{:02d}{}{:02d}".format(sign, hour, separator, minute) - - def _format_localizable_token( - self, dt, token, locale - ): # type: (pendulum.DateTime, str, Locale) -> str - """ - Formats a DateTime instance - with a given localizable token and locale. - - :param dt: The instance to format - :type dt: pendulum.DateTime - - :param token: The token to use - :type token: str - - :param locale: The locale to use - :type locale: Locale - - :rtype: str - """ - if token == "MMM": - return locale.get("translations.months.abbreviated")[dt.month] - elif token == "MMMM": - return locale.get("translations.months.wide")[dt.month] - elif token == "dd": - return locale.get("translations.days.short")[dt.day_of_week] - elif token == "ddd": - return locale.get("translations.days.abbreviated")[dt.day_of_week] - elif token == "dddd": - return locale.get("translations.days.wide")[dt.day_of_week] - elif token == "Do": - return locale.ordinalize(dt.day) - elif token == "do": - return locale.ordinalize(dt.day_of_week) - elif token == "Mo": - return locale.ordinalize(dt.month) - elif token == "Qo": - return locale.ordinalize(dt.quarter) - elif token == "wo": - return locale.ordinalize(dt.week_of_year) - elif token == "DDDo": - return locale.ordinalize(dt.day_of_year) - elif token == "A": - key = "translations.day_periods" - if dt.hour >= 12: - key += ".pm" - else: - key += ".am" - - return locale.get(key) - else: - return token - - def parse( - self, - time, # type: str - fmt, # type: str - now, # type: pendulum.DateTime - locale=None, # type: typing.Optional[str] - ): # type: (...) -> typing.Dict[str, typing.Any] - """ - Parses a time string matching a given format as a tuple. - - :param time: The timestring - :param fmt: The format - :param now: The datetime to use as "now" - :param locale: The locale to use - - :return: The parsed elements - """ - escaped_fmt = re.escape(fmt) - - tokens = self._FROM_FORMAT_RE.findall(escaped_fmt) - if not tokens: - return time - - if not locale: - locale = pendulum.get_locale() - - locale = Locale.load(locale) - - parsed = { - "year": None, - "month": None, - "day": None, - "hour": None, - "minute": None, - "second": None, - "microsecond": None, - "tz": None, - "quarter": None, - "day_of_week": None, - "day_of_year": None, - "meridiem": None, - "timestamp": None, - } - - pattern = self._FROM_FORMAT_RE.sub( - lambda m: self._replace_tokens(m.group(0), locale), escaped_fmt - ) - - if not re.search("^" + pattern + "$", time): - raise ValueError("String does not match format {}".format(fmt)) - - re.sub(pattern, lambda m: self._get_parsed_values(m, parsed, locale, now), time) - - return self._check_parsed(parsed, now) - - def _check_parsed( - self, parsed, now - ): # type: (typing.Dict[str, typing.Any], pendulum.DateTime) -> typing.Dict[str, typing.Any] - """ - Checks validity of parsed elements. - - :param parsed: The elements to parse. - - :return: The validated elements. - """ - validated = { - "year": parsed["year"], - "month": parsed["month"], - "day": parsed["day"], - "hour": parsed["hour"], - "minute": parsed["minute"], - "second": parsed["second"], - "microsecond": parsed["microsecond"], - "tz": None, - } - - # If timestamp has been specified - # we use it and don't go any further - if parsed["timestamp"] is not None: - str_us = str(parsed["timestamp"]) - if "." in str_us: - microseconds = int("{}".format(str_us.split(".")[1].ljust(6, "0"))) - else: - microseconds = 0 - - from pendulum.helpers import local_time - - time = local_time(parsed["timestamp"], 0, microseconds) - validated["year"] = time[0] - validated["month"] = time[1] - validated["day"] = time[2] - validated["hour"] = time[3] - validated["minute"] = time[4] - validated["second"] = time[5] - validated["microsecond"] = time[6] - - return validated - - if parsed["quarter"] is not None: - if validated["year"] is not None: - dt = pendulum.datetime(validated["year"], 1, 1) - else: - dt = now - - dt = dt.start_of("year") - - while dt.quarter != parsed["quarter"]: - dt = dt.add(months=3) - - validated["year"] = dt.year - validated["month"] = dt.month - validated["day"] = dt.day - - if validated["year"] is None: - validated["year"] = now.year - - if parsed["day_of_year"] is not None: - dt = pendulum.parse( - "{}-{:>03d}".format(validated["year"], parsed["day_of_year"]) - ) - - validated["month"] = dt.month - validated["day"] = dt.day - - if parsed["day_of_week"] is not None: - dt = pendulum.datetime( - validated["year"], - validated["month"] or now.month, - validated["day"] or now.day, - ) - dt = dt.start_of("week").subtract(days=1) - dt = dt.next(parsed["day_of_week"]) - validated["year"] = dt.year - validated["month"] = dt.month - validated["day"] = dt.day - - # Meridiem - if parsed["meridiem"] is not None: - # If the time is greater than 13:00:00 - # This is not valid - if validated["hour"] is None: - raise ValueError("Invalid Date") - - t = ( - validated["hour"], - validated["minute"], - validated["second"], - validated["microsecond"], - ) - if t >= (13, 0, 0, 0): - raise ValueError("Invalid date") - - pm = parsed["meridiem"] == "pm" - validated["hour"] %= 12 - if pm: - validated["hour"] += 12 - - if validated["month"] is None: - if parsed["year"] is not None: - validated["month"] = parsed["month"] or 1 - else: - validated["month"] = parsed["month"] or now.month - - if validated["day"] is None: - if parsed["year"] is not None or parsed["month"] is not None: - validated["day"] = parsed["day"] or 1 - else: - validated["day"] = parsed["day"] or now.day - - for part in ["hour", "minute", "second", "microsecond"]: - if validated[part] is None: - validated[part] = 0 - - validated["tz"] = parsed["tz"] - - return validated - - def _get_parsed_values( - self, m, parsed, locale, now - ): # type: (typing.Match[str], typing.Dict[str, typing.Any], Locale, pendulum.DateTime) -> None - for token, index in m.re.groupindex.items(): - if token in self._LOCALIZABLE_TOKENS: - self._get_parsed_locale_value(token, m.group(index), parsed, locale) - else: - self._get_parsed_value(token, m.group(index), parsed, now) - - def _get_parsed_value( - self, token, value, parsed, now - ): # type: (str, str, typing.Dict[str, typing.Any], pendulum.DateTime) -> None - parsed_token = self._PARSE_TOKENS[token](value) - - if "Y" in token: - if token == "YY": - parsed_token = now.year // 100 * 100 + parsed_token - - parsed["year"] = parsed_token - elif "Q" == token: - parsed["quarter"] = parsed_token - elif token in ["MM", "M"]: - parsed["month"] = parsed_token - elif token in ["DDDD", "DDD"]: - parsed["day_of_year"] = parsed_token - elif "D" in token: - parsed["day"] = parsed_token - elif "H" in token: - parsed["hour"] = parsed_token - elif token in ["hh", "h"]: - if parsed_token > 12: - raise ValueError("Invalid date") - - parsed["hour"] = parsed_token - elif "m" in token: - parsed["minute"] = parsed_token - elif "s" in token: - parsed["second"] = parsed_token - elif "S" in token: - parsed["microsecond"] = parsed_token - elif token in ["d", "E"]: - parsed["day_of_week"] = parsed_token - elif token in ["X", "x"]: - parsed["timestamp"] = parsed_token - elif token in ["ZZ", "Z"]: - negative = True if value.startswith("-") else False - tz = value[1:] - if ":" not in tz: - if len(tz) == 2: - tz = "{}00".format(tz) - - off_hour = tz[0:2] - off_minute = tz[2:4] - else: - off_hour, off_minute = tz.split(":") - - offset = ((int(off_hour) * 60) + int(off_minute)) * 60 - - if negative: - offset = -1 * offset - - parsed["tz"] = pendulum.timezone(offset) - elif token == "z": - # Full timezone - if value not in pendulum.timezones: - raise ValueError("Invalid date") - - parsed["tz"] = pendulum.timezone(value) - - def _get_parsed_locale_value( - self, token, value, parsed, locale - ): # type: (str, str, typing.Dict[str, typing.Any], Locale) -> None - if token == "MMMM": - unit = "month" - match = "months.wide" - elif token == "MMM": - unit = "month" - match = "months.abbreviated" - elif token == "Do": - parsed["day"] = int(re.match(r"(\d+)", value).group(1)) - - return - elif token == "dddd": - unit = "day_of_week" - match = "days.wide" - elif token == "ddd": - unit = "day_of_week" - match = "days.abbreviated" - elif token == "dd": - unit = "day_of_week" - match = "days.short" - elif token in ["a", "A"]: - valid_values = [ - locale.translation("day_periods.am"), - locale.translation("day_periods.pm"), - ] - - if token == "a": - value = value.lower() - valid_values = list(map(lambda x: x.lower(), valid_values)) - - if value not in valid_values: - raise ValueError("Invalid date") - - parsed["meridiem"] = ["am", "pm"][valid_values.index(value)] - - return - else: - raise ValueError('Invalid token "{}"'.format(token)) - - parsed[unit] = locale.match_translation(match, value) - if value is None: - raise ValueError("Invalid date") - - def _replace_tokens(self, token, locale): # type: (str, Locale) -> str - if token.startswith("[") and token.endswith("]"): - return token[1:-1] - elif token.startswith("\\"): - if len(token) == 2 and token[1] in {"[", "]"}: - return "" - - return token - elif token not in self._REGEX_TOKENS and token not in self._LOCALIZABLE_TOKENS: - raise ValueError("Unsupported token: {}".format(token)) - - if token in self._LOCALIZABLE_TOKENS: - values = self._LOCALIZABLE_TOKENS[token] - if callable(values): - candidates = values(locale) - else: - candidates = tuple( - locale.translation(self._LOCALIZABLE_TOKENS[token]).values() - ) - else: - candidates = self._REGEX_TOKENS[token] - - if not candidates: - raise ValueError("Unsupported token: {}".format(token)) - - if not isinstance(candidates, tuple): - candidates = (candidates,) - - pattern = "(?P<{}>{})".format(token, "|".join([decode(p) for p in candidates])) - - return pattern +from __future__ import annotations + +import datetime +import re + +from typing import TYPE_CHECKING +from typing import Any +from typing import Callable +from typing import Match +from typing import Sequence +from typing import cast + +import pendulum + +from pendulum.locales.locale import Locale + +if TYPE_CHECKING: + from pendulum import Timezone + +_MATCH_1 = r"\d" +_MATCH_2 = r"\d\d" +_MATCH_3 = r"\d{3}" +_MATCH_4 = r"\d{4}" +_MATCH_6 = r"[+-]?\d{6}" +_MATCH_1_TO_2 = r"\d\d?" +_MATCH_1_TO_2_LEFT_PAD = r"[0-9 ]\d?" +_MATCH_1_TO_3 = r"\d{1,3}" +_MATCH_1_TO_4 = r"\d{1,4}" +_MATCH_1_TO_6 = r"[+-]?\d{1,6}" +_MATCH_3_TO_4 = r"\d{3}\d?" +_MATCH_5_TO_6 = r"\d{5}\d?" +_MATCH_UNSIGNED = r"\d+" +_MATCH_SIGNED = r"[+-]?\d+" +_MATCH_OFFSET = r"[Zz]|[+-]\d\d:?\d\d" +_MATCH_SHORT_OFFSET = r"[Zz]|[+-]\d\d(?::?\d\d)?" +_MATCH_TIMESTAMP = r"[+-]?\d+(\.\d{1,6})?" +_MATCH_WORD = ( + "(?i)[0-9]*" + "['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+" + r"|[\u0600-\u06FF/]+(\s*?[\u0600-\u06FF]+){1,2}" +) +_MATCH_TIMEZONE = "[A-Za-z0-9-+]+(/[A-Za-z0-9-+_]+)?" + + +class Formatter: + _TOKENS: str = ( + r"\[([^\[]*)\]|\\(.)|" + "(" + "Mo|MM?M?M?" + "|Do|DDDo|DD?D?D?|ddd?d?|do?" + "|E{1,4}" + "|w[o|w]?|W[o|W]?|Qo?" + "|YYYY|YY|Y" + "|gg(ggg?)?|GG(GGG?)?" + "|a|A" + "|hh?|HH?|kk?" + "|mm?|ss?|S{1,9}" + "|x|X" + "|zz?|ZZ?" + "|LTS|LT|LL?L?L?" + ")" + ) + + _FORMAT_RE: re.Pattern[str] = re.compile(_TOKENS) + + _FROM_FORMAT_RE: re.Pattern[str] = re.compile(r"(? str: + """ + Formats a DateTime instance with a given format and locale. + + :param dt: The instance to format + :param fmt: The format to use + :param locale: The locale to use + """ + loaded_locale: Locale = Locale.load(locale or pendulum.get_locale()) + + result = self._FORMAT_RE.sub( + lambda m: m.group(1) + if m.group(1) + else m.group(2) + if m.group(2) + else self._format_token(dt, m.group(3), loaded_locale), + fmt, + ) + + return result + + def _format_token(self, dt: pendulum.DateTime, token: str, locale: Locale) -> str: + """ + Formats a DateTime instance with a given token and locale. + + :param dt: The instance to format + :param token: The token to use + :param locale: The locale to use + """ + if token in self._DATE_FORMATS: + fmt = locale.get(f"custom.date_formats.{token}") + if fmt is None: + fmt = self._DEFAULT_DATE_FORMATS[token] + + return self.format(dt, fmt, locale) + + if token in self._LOCALIZABLE_TOKENS: + return self._format_localizable_token(dt, token, locale) + + if token in self._TOKENS_RULES: + return self._TOKENS_RULES[token](dt) + + # Timezone + if token in ["ZZ", "Z"]: + if dt.tzinfo is None: + return "" + + separator = ":" if token == "Z" else "" + offset = dt.utcoffset() or datetime.timedelta() + minutes = offset.total_seconds() / 60 + + if minutes >= 0: + sign = "+" + else: + sign = "-" + + hour, minute = divmod(abs(int(minutes)), 60) + + return f"{sign}{hour:02d}{separator}{minute:02d}" + + return token + + def _format_localizable_token( + self, dt: pendulum.DateTime, token: str, locale: Locale + ) -> str: + """ + Formats a DateTime instance + with a given localizable token and locale. + + :param dt: The instance to format + :param token: The token to use + :param locale: The locale to use + """ + if token == "MMM": + return cast(str, locale.get("translations.months.abbreviated")[dt.month]) + elif token == "MMMM": + return cast(str, locale.get("translations.months.wide")[dt.month]) + elif token == "dd": + 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] + ) + elif token == "dddd": + return cast(str, locale.get("translations.days.wide")[dt.day_of_week]) + elif token == "Do": + return locale.ordinalize(dt.day) + elif token == "do": + return locale.ordinalize(dt.day_of_week) + elif token == "Mo": + return locale.ordinalize(dt.month) + elif token == "Qo": + return locale.ordinalize(dt.quarter) + elif token == "wo": + return locale.ordinalize(dt.week_of_year) + elif token == "DDDo": + return locale.ordinalize(dt.day_of_year) + elif token == "A": + key = "translations.day_periods" + if dt.hour >= 12: + key += ".pm" + else: + key += ".am" + + return cast(str, locale.get(key)) + else: + return token + + def parse( + self, + time: str, + fmt: str, + now: pendulum.DateTime, + locale: str | None = None, + ) -> dict[str, Any]: + """ + Parses a time string matching a given format as a tuple. + + :param time: The timestring + :param fmt: The format + :param now: The datetime to use as "now" + :param locale: The locale to use + + :return: The parsed elements + """ + escaped_fmt = re.escape(fmt) + + tokens = self._FROM_FORMAT_RE.findall(escaped_fmt) + if not tokens: + raise ValueError("The given time string does not match the given format") + + if not locale: + locale = pendulum.get_locale() + + loaded_locale: Locale = Locale.load(locale) + + parsed = { + "year": None, + "month": None, + "day": None, + "hour": None, + "minute": None, + "second": None, + "microsecond": None, + "tz": None, + "quarter": None, + "day_of_week": None, + "day_of_year": None, + "meridiem": None, + "timestamp": None, + } + + pattern = self._FROM_FORMAT_RE.sub( + lambda m: self._replace_tokens(m.group(0), loaded_locale), escaped_fmt + ) + + 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) + + re.sub(pattern, _get_parsed_values, time) + + return self._check_parsed(parsed, now) + + def _check_parsed( + self, parsed: dict[str, Any], now: pendulum.DateTime + ) -> dict[str, Any]: + """ + Checks validity of parsed elements. + + :param parsed: The elements to parse. + + :return: The validated elements. + """ + validated: dict[str, int | Timezone | None] = { + "year": parsed["year"], + "month": parsed["month"], + "day": parsed["day"], + "hour": parsed["hour"], + "minute": parsed["minute"], + "second": parsed["second"], + "microsecond": parsed["microsecond"], + "tz": None, + } + + # If timestamp has been specified + # we use it and don't go any further + if parsed["timestamp"] is not None: + str_us = str(parsed["timestamp"]) + if "." in str_us: + microseconds = int(f'{str_us.split(".")[1].ljust(6, "0")}') + else: + microseconds = 0 + + from pendulum.helpers import local_time + + time = local_time(parsed["timestamp"], 0, microseconds) + validated["year"] = time[0] + validated["month"] = time[1] + validated["day"] = time[2] + validated["hour"] = time[3] + validated["minute"] = time[4] + validated["second"] = time[5] + validated["microsecond"] = time[6] + + return validated + + if parsed["quarter"] is not None: + if validated["year"] is not None: + dt = pendulum.datetime(validated["year"], 1, 1) + else: + dt = now + + dt = dt.start_of("year") + + while dt.quarter != parsed["quarter"]: + dt = dt.add(months=3) + + validated["year"] = dt.year + validated["month"] = dt.month + validated["day"] = dt.day + + if validated["year"] is None: + validated["year"] = now.year + + if parsed["day_of_year"] is not None: + dt = cast( + pendulum.DateTime, + pendulum.parse(f'{validated["year"]}-{parsed["day_of_year"]:>03d}'), + ) + + validated["month"] = dt.month + validated["day"] = dt.day + + 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, + ) + dt = dt.start_of("week").subtract(days=1) + dt = dt.next(parsed["day_of_week"]) + validated["year"] = dt.year + validated["month"] = dt.month + validated["day"] = dt.day + + # Meridiem + if parsed["meridiem"] is not None: + # If the time is greater than 13:00:00 + # This is not valid + if validated["hour"] is None: + raise ValueError("Invalid Date") + + t = ( + validated["hour"], + validated["minute"], + validated["second"], + validated["microsecond"], + ) + if t >= (13, 0, 0, 0): + raise ValueError("Invalid date") + + pm = parsed["meridiem"] == "pm" + validated["hour"] %= 12 + if pm: + validated["hour"] += 12 + + if validated["month"] is None: + if parsed["year"] is not None: + validated["month"] = parsed["month"] or 1 + else: + validated["month"] = parsed["month"] or now.month + + if validated["day"] is None: + if parsed["year"] is not None or parsed["month"] is not None: + validated["day"] = parsed["day"] or 1 + else: + validated["day"] = parsed["day"] or now.day + + for part in ["hour", "minute", "second", "microsecond"]: + if validated[part] is None: + validated[part] = 0 + + validated["tz"] = parsed["tz"] + + return validated + + def _get_parsed_values( + self, + m: Match[str], + parsed: dict[str, Any], + locale: Locale, + now: pendulum.DateTime, + ) -> None: + for token, index in m.re.groupindex.items(): + if token in self._LOCALIZABLE_TOKENS: + self._get_parsed_locale_value(token, m.group(index), parsed, locale) + else: + self._get_parsed_value(token, m.group(index), parsed, now) + + def _get_parsed_value( + self, + token: str, + value: str, + parsed: dict[str, Any], + now: pendulum.DateTime, + ) -> None: + parsed_token = self._PARSE_TOKENS[token](value) + + if "Y" in token: + if token == "YY": + parsed_token = now.year // 100 * 100 + parsed_token + + parsed["year"] = parsed_token + elif token == "Q": + parsed["quarter"] = parsed_token + elif token in ["MM", "M"]: + parsed["month"] = parsed_token + elif token in ["DDDD", "DDD"]: + parsed["day_of_year"] = parsed_token + elif "D" in token: + parsed["day"] = parsed_token + elif "H" in token: + parsed["hour"] = parsed_token + elif token in ["hh", "h"]: + if parsed_token > 12: + raise ValueError("Invalid date") + + parsed["hour"] = parsed_token + elif "m" in token: + parsed["minute"] = parsed_token + elif "s" in token: + parsed["second"] = parsed_token + elif "S" in token: + parsed["microsecond"] = parsed_token + elif token in ["d", "E"]: + parsed["day_of_week"] = parsed_token + elif token in ["X", "x"]: + parsed["timestamp"] = parsed_token + elif token in ["ZZ", "Z"]: + negative = bool(value.startswith("-")) + tz = value[1:] + if ":" not in tz: + if len(tz) == 2: + tz = f"{tz}00" + + off_hour = tz[0:2] + off_minute = tz[2:4] + else: + off_hour, off_minute = tz.split(":") + + offset = ((int(off_hour) * 60) + int(off_minute)) * 60 + + if negative: + offset = -1 * offset + + parsed["tz"] = pendulum.timezone(offset) + elif token == "z": + # Full timezone + if value not in pendulum.timezones(): + raise ValueError("Invalid date") + + parsed["tz"] = pendulum.timezone(value) + + def _get_parsed_locale_value( + self, token: str, value: str, parsed: dict[str, Any], locale: Locale + ) -> None: + if token == "MMMM": + unit = "month" + match = "months.wide" + elif token == "MMM": + unit = "month" + match = "months.abbreviated" + elif token == "Do": + parsed["day"] = int(cast(Match[str], re.match(r"(\d+)", value)).group(1)) + + return + elif token == "dddd": + unit = "day_of_week" + match = "days.wide" + elif token == "ddd": + unit = "day_of_week" + match = "days.abbreviated" + elif token == "dd": + unit = "day_of_week" + match = "days.short" + elif token in ["a", "A"]: + valid_values = [ + locale.translation("day_periods.am"), + locale.translation("day_periods.pm"), + ] + + if token == "a": + value = value.lower() + valid_values = [x.lower() for x in valid_values] + + if value not in valid_values: + raise ValueError("Invalid date") + + parsed["meridiem"] = ["am", "pm"][valid_values.index(value)] + + return + else: + raise ValueError(f'Invalid token "{token}"') + + parsed[unit] = locale.match_translation(match, value) + if value is None: + raise ValueError("Invalid date") + + def _replace_tokens(self, token: str, locale: Locale) -> str: + if token.startswith("[") and token.endswith("]"): + return token[1:-1] + elif token.startswith("\\"): + if len(token) == 2 and token[1] in {"[", "]"}: + return "" + + return token + elif token not in self._REGEX_TOKENS and token not in self._LOCALIZABLE_TOKENS: + raise ValueError(f"Unsupported token: {token}") + + if token in self._LOCALIZABLE_TOKENS: + values = self._LOCALIZABLE_TOKENS[token] + if callable(values): + candidates = values(locale) + else: + candidates = tuple( + locale.translation( + cast(str, self._LOCALIZABLE_TOKENS[token]) + ).values() + ) + else: + candidates = cast(Sequence[str], self._REGEX_TOKENS[token]) + + if not candidates: + raise ValueError(f"Unsupported token: {token}") + + if not isinstance(candidates, tuple): + candidates = (cast(str, candidates),) + + pattern = f'(?P<{token}>{"|".join(candidates)})' + + return pattern diff --git a/pendulum/helpers.py b/pendulum/helpers.py index f149ca5..13b7f22 100644 --- a/pendulum/helpers.py +++ b/pendulum/helpers.py @@ -1,224 +1,223 @@ -from __future__ import absolute_import - -import os -import struct - -from contextlib import contextmanager -from datetime import date -from datetime import datetime -from datetime import timedelta -from math import copysign -from typing import TYPE_CHECKING -from typing import Iterator -from typing import Optional -from typing import TypeVar -from typing import overload - -import pendulum - -from .constants import DAYS_PER_MONTHS -from .formatting.difference_formatter import DifferenceFormatter -from .locales.locale import Locale - - -if TYPE_CHECKING: - # Prevent import cycles - from .period import Period - -with_extensions = os.getenv("PENDULUM_EXTENSIONS", "1") == "1" - -_DT = TypeVar("_DT", bound=datetime) -_D = TypeVar("_D", bound=date) - -try: - if not with_extensions or struct.calcsize("P") == 4: - raise ImportError() - - from ._extensions._helpers import local_time - from ._extensions._helpers import precise_diff - from ._extensions._helpers import is_leap - from ._extensions._helpers import is_long_year - from ._extensions._helpers import week_day - from ._extensions._helpers import days_in_year - from ._extensions._helpers import timestamp -except ImportError: - from ._extensions.helpers import local_time # noqa - from ._extensions.helpers import precise_diff # noqa - from ._extensions.helpers import is_leap # noqa - from ._extensions.helpers import is_long_year # noqa - from ._extensions.helpers import week_day # noqa - from ._extensions.helpers import days_in_year # noqa - from ._extensions.helpers import timestamp # noqa - - -difference_formatter = DifferenceFormatter() - - -@overload -def add_duration( - dt, # type: _DT - years=0, # type: int - months=0, # type: int - weeks=0, # type: int - days=0, # type: int - hours=0, # type: int - minutes=0, # type: int - seconds=0, # type: int - microseconds=0, # type: int -): # type: (...) -> _DT - pass - - -@overload -def add_duration( - dt, # type: _D - years=0, # type: int - months=0, # type: int - weeks=0, # type: int - days=0, # type: int -): # type: (...) -> _D - pass - - -def add_duration( - dt, - years=0, - months=0, - weeks=0, - days=0, - hours=0, - minutes=0, - seconds=0, - microseconds=0, -): - """ - Adds a duration to a date/datetime instance. - """ - days += weeks * 7 - - if ( - isinstance(dt, date) - and not isinstance(dt, datetime) - and any([hours, minutes, seconds, microseconds]) - ): - raise RuntimeError("Time elements cannot be added to a date instance.") - - # Normalizing - if abs(microseconds) > 999999: - s = _sign(microseconds) - div, mod = divmod(microseconds * s, 1000000) - microseconds = mod * s - seconds += div * s - - if abs(seconds) > 59: - s = _sign(seconds) - div, mod = divmod(seconds * s, 60) - seconds = mod * s - minutes += div * s - - if abs(minutes) > 59: - s = _sign(minutes) - div, mod = divmod(minutes * s, 60) - minutes = mod * s - hours += div * s - - if abs(hours) > 23: - s = _sign(hours) - div, mod = divmod(hours * s, 24) - hours = mod * s - days += div * s - - if abs(months) > 11: - s = _sign(months) - div, mod = divmod(months * s, 12) - months = mod * s - years += div * s - - year = dt.year + years - month = dt.month - - if months: - month += months - if month > 12: - year += 1 - month -= 12 - elif month < 1: - year -= 1 - month += 12 - - day = min(DAYS_PER_MONTHS[int(is_leap(year))][month], dt.day) - - dt = dt.replace(year=year, month=month, day=day) - - return dt + timedelta( - days=days, - hours=hours, - minutes=minutes, - seconds=seconds, - microseconds=microseconds, - ) - - -def format_diff( - diff, is_now=True, absolute=False, locale=None -): # type: (Period, bool, bool, Optional[str]) -> str - if locale is None: - locale = get_locale() - - return difference_formatter.format(diff, is_now, absolute, locale) - - -def _sign(x): - return int(copysign(1, x)) - - -# Global helpers - - -@contextmanager -def test(mock): # type: (pendulum.DateTime) -> Iterator[None] - set_test_now(mock) - try: - yield - finally: - set_test_now() - - -def set_test_now(test_now=None): # type: (Optional[pendulum.DateTime]) -> None - pendulum._TEST_NOW = test_now - - -def get_test_now(): # type: () -> Optional[pendulum.DateTime] - return pendulum._TEST_NOW - - -def has_test_now(): # type: () -> bool - return pendulum._TEST_NOW is not None - - -def locale(name): # type: (str) -> Locale - return Locale.load(name) - - -def set_locale(name): # type: (str) -> None - locale(name) - - pendulum._LOCALE = name - - -def get_locale(): # type: () -> str - return pendulum._LOCALE - - -def week_starts_at(wday): # type: (int) -> None - if wday < pendulum.SUNDAY or wday > pendulum.SATURDAY: - raise ValueError("Invalid week day as start of week.") - - pendulum._WEEK_STARTS_AT = wday - - -def week_ends_at(wday): # type: (int) -> None - if wday < pendulum.SUNDAY or wday > pendulum.SATURDAY: - raise ValueError("Invalid week day as start of week.") - - pendulum._WEEK_ENDS_AT = wday +from __future__ import annotations + +import os +import struct + +from datetime import date +from datetime import datetime +from datetime import timedelta +from math import copysign +from typing import TYPE_CHECKING +from typing import TypeVar +from typing import overload + +import pendulum + +from pendulum.constants import DAYS_PER_MONTHS +from pendulum.formatting.difference_formatter import DifferenceFormatter +from pendulum.locales.locale import Locale + +if TYPE_CHECKING: + # Prevent import cycles + from pendulum.duration import Duration + +with_extensions = os.getenv("PENDULUM_EXTENSIONS", "1") == "1" + +_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 +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 + +difference_formatter = DifferenceFormatter() + + +@overload +def add_duration( + dt: datetime, + years: int = 0, + months: int = 0, + weeks: int = 0, + days: int = 0, + hours: int = 0, + minutes: int = 0, + seconds: float = 0, + microseconds: int = 0, +) -> datetime: + ... + + +@overload +def add_duration( + dt: date, + years: int = 0, + months: int = 0, + weeks: int = 0, + days: int = 0, +) -> date: + pass + + +def add_duration( + dt: date | datetime, + years: int = 0, + months: int = 0, + weeks: int = 0, + days: int = 0, + hours: int = 0, + minutes: int = 0, + seconds: float = 0, + microseconds: int = 0, +) -> date | datetime: + """ + Adds a duration to a date/datetime instance. + """ + days += weeks * 7 + + if ( + isinstance(dt, date) + and not isinstance(dt, datetime) + and any([hours, minutes, seconds, microseconds]) + ): + raise RuntimeError("Time elements cannot be added to a date instance.") + + # Normalizing + if abs(microseconds) > 999999: + s = _sign(microseconds) + div, mod = divmod(microseconds * s, 1000000) + microseconds = mod * s + seconds += div * s + + if abs(seconds) > 59: + s = _sign(seconds) + div, mod = divmod(seconds * s, 60) # type: ignore[assignment] + seconds = mod * s + minutes += div * s + + if abs(minutes) > 59: + s = _sign(minutes) + div, mod = divmod(minutes * s, 60) + minutes = mod * s + hours += div * s + + if abs(hours) > 23: + s = _sign(hours) + div, mod = divmod(hours * s, 24) + hours = mod * s + days += div * s + + if abs(months) > 11: + s = _sign(months) + div, mod = divmod(months * s, 12) + months = mod * s + years += div * s + + year = dt.year + years + month = dt.month + + if months: + month += months + if month > 12: + year += 1 + month -= 12 + elif month < 1: + year -= 1 + month += 12 + + day = min(DAYS_PER_MONTHS[int(is_leap(year))][month], dt.day) + + dt = dt.replace(year=year, month=month, day=day) + + return dt + timedelta( + days=days, + hours=hours, + minutes=minutes, + seconds=seconds, + microseconds=microseconds, + ) + + +def format_diff( + diff: Duration, + is_now: bool = True, + absolute: bool = False, + locale: str | None = None, +) -> str: + if locale is None: + locale = get_locale() + + return difference_formatter.format(diff, is_now, absolute, locale) + + +def _sign(x: float) -> int: + return int(copysign(1, x)) + + +# Global helpers + + +def locale(name: str) -> Locale: + return Locale.load(name) + + +def set_locale(name: str) -> None: + locale(name) + + pendulum._LOCALE = name + + +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.") + + 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.") + + pendulum._WEEK_ENDS_AT = wday + + +__all__ = [ + "PreciseDiff", + "days_in_year", + "is_leap", + "is_long_year", + "local_time", + "precise_diff", + "timestamp", + "week_day", + "add_duration", + "format_diff", + "locale", + "set_locale", + "get_locale", + "week_starts_at", + "week_ends_at", +] diff --git a/pendulum/interval.py b/pendulum/interval.py new file mode 100644 index 0000000..f20042b --- /dev/null +++ b/pendulum/interval.py @@ -0,0 +1,448 @@ +from __future__ import annotations + +import operator + +from datetime import date +from datetime import datetime +from datetime import timedelta +from typing import TYPE_CHECKING +from typing import Iterator +from typing import Union +from typing import cast +from typing import overload + +import pendulum + +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 pendulum.helpers import PreciseDiff + from pendulum.locales.locale import Locale # noqa + + +class Interval(Duration): + """ + A period of time between two datetimes. + """ + + @overload + def __new__( + cls, + start: pendulum.DateTime | datetime, + end: pendulum.DateTime | datetime, + absolute: bool = False, + ) -> Interval: + ... + + @overload + def __new__( + cls, + start: pendulum.Date | date, + end: pendulum.Date | date, + absolute: bool = False, + ) -> Interval: + ... + + def __new__( + cls, + start: pendulum.DateTime | pendulum.Date | datetime | date, + end: pendulum.DateTime | pendulum.Date | datetime | date, + absolute: bool = False, + ) -> Interval: + 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") + + if ( + isinstance(start, datetime) + and isinstance(end, datetime) + and ( + start.tzinfo is None + and end.tzinfo is not None + or start.tzinfo is not None + and end.tzinfo is None + ) + ): + raise TypeError("can't compare offset-naive and offset-aware datetimes") + + if absolute and start > end: + end, start = start, end + + _start = start + _end = end + if isinstance(start, pendulum.DateTime): + _start = datetime( + start.year, + start.month, + start.day, + start.hour, + start.minute, + start.second, + start.microsecond, + tzinfo=start.tzinfo, + fold=start.fold, + ) + elif isinstance(start, pendulum.Date): + _start = date(start.year, start.month, start.day) + + if isinstance(end, pendulum.DateTime): + _end = datetime( + end.year, + end.month, + end.day, + end.hour, + end.minute, + end.second, + end.microsecond, + tzinfo=end.tzinfo, + fold=end.fold, + ) + elif isinstance(end, pendulum.Date): + _end = date(end.year, end.month, end.day) + + # Fixing issues with datetime.__sub__() + # not handling offsets if the tzinfo is the same + if ( + isinstance(_start, datetime) + and isinstance(_end, datetime) + and _start.tzinfo is _end.tzinfo + ): + if _start.tzinfo is not None: + offset = cast(timedelta, cast(datetime, start).utcoffset()) + _start = (_start - offset).replace(tzinfo=None) + + if isinstance(end, datetime) and _end.tzinfo is not None: + offset = cast(timedelta, end.utcoffset()) + _end = (_end - offset).replace(tzinfo=None) + + delta: timedelta = _end - _start # type: ignore[operator] + + return cast(Interval, super().__new__(cls, seconds=delta.total_seconds())) + + def __init__( + self, + start: pendulum.DateTime | pendulum.Date | datetime | date, + end: pendulum.DateTime | pendulum.Date | datetime | date, + absolute: bool = False, + ) -> None: + super().__init__() + + _start: pendulum.DateTime | pendulum.Date | datetime | date + if not isinstance(start, pendulum.Date): + if isinstance(start, datetime): + start = pendulum.instance(start) + else: + start = pendulum.date(start.year, start.month, start.day) + + _start = start + else: + if isinstance(start, pendulum.DateTime): + _start = datetime( + start.year, + start.month, + start.day, + start.hour, + start.minute, + start.second, + start.microsecond, + tzinfo=start.tzinfo, + ) + else: + _start = date(start.year, start.month, start.day) + + _end: pendulum.DateTime | pendulum.Date | datetime | date + if not isinstance(end, pendulum.Date): + if isinstance(end, datetime): + end = pendulum.instance(end) + else: + end = pendulum.date(end.year, end.month, end.day) + + _end = end + else: + if isinstance(end, pendulum.DateTime): + _end = datetime( + end.year, + end.month, + end.day, + end.hour, + end.minute, + end.second, + end.microsecond, + tzinfo=end.tzinfo, + ) + else: + _end = date(end.year, end.month, end.day) + + self._invert = False + if start > end: + self._invert = True + + if absolute: + end, start = start, end + _end, _start = _start, _end + + self._absolute = absolute + self._start: pendulum.DateTime | pendulum.Date = start + self._end: pendulum.DateTime | pendulum.Date = end + self._delta: PreciseDiff = precise_diff(_start, _end) + + @property + def years(self) -> int: + return self._delta.years + + @property + def months(self) -> int: + return self._delta.months + + @property + def weeks(self) -> int: + return abs(self._delta.days) // 7 * self._sign(self._delta.days) + + @property + def days(self) -> int: + return self._days + + @property + def remaining_days(self) -> int: + return abs(self._delta.days) % 7 * self._sign(self._days) + + @property + def hours(self) -> int: + return self._delta.hours + + @property + def minutes(self) -> int: + return self._delta.minutes + + @property + def start(self) -> pendulum.DateTime | pendulum.Date | datetime | date: + return self._start + + @property + def end(self) -> pendulum.DateTime | pendulum.Date | datetime | date: + return self._end + + def in_years(self) -> int: + """ + Gives the duration of the Period in full years. + """ + return self.years + + def in_months(self) -> int: + """ + Gives the duration of the Period in full months. + """ + return self.years * MONTHS_PER_YEAR + self.months + + def in_weeks(self) -> int: + days = self.in_days() + sign = 1 + + if days < 0: + sign = -1 + + return sign * (abs(days) // 7) + + def in_days(self) -> int: + return self._delta.total_days + + def in_words(self, locale: str | None = None, separator: str = " ") -> str: + """ + Get the current interval in words in the current locale. + + Ex: 6 jours 23 heures 58 minutes + + :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 + + periods = [ + ("year", self.years), + ("month", self.months), + ("week", self.weeks), + ("day", self.remaining_days), + ("hour", self.hours), + ("minute", self.minutes), + ("second", self.remaining_seconds), + ] + loaded_locale: Locale = Locale.load(locale or pendulum.get_locale()) + parts = [] + for period in periods: + unit, period_count = period + if abs(period_count) > 0: + translation = loaded_locale.translation( + f"units.{unit}.{loaded_locale.plural(abs(period_count))}" + ) + parts.append(translation.format(period_count)) + + if not parts: + count: str | int = 0 + if abs(self.microseconds) > 0: + unit = f"units.second.{loaded_locale.plural(1)}" + count = f"{abs(self.microseconds) / 1e6:.2f}" + else: + unit = f"units.microsecond.{loaded_locale.plural(0)}" + + translation = loaded_locale.translation(unit) + parts.append(translation.format(count)) + + return separator.join(parts) + + def range( + self, unit: str, amount: int = 1 + ) -> Iterator[pendulum.DateTime | pendulum.Date]: + method = "add" + op = operator.le + if not self._absolute and self.invert: + method = "subtract" + op = operator.ge + + start, end = self.start, self.end + + i = amount + while op(start, end): + yield cast(Union[pendulum.DateTime, pendulum.Date], start) + + start = getattr(self.start, method)(**{unit: i}) + + i += amount + + def as_interval(self) -> Duration: + """ + Return the Period as a Duration. + """ + return Duration(seconds=self.total_seconds()) + + def __iter__(self) -> Iterator[pendulum.DateTime | pendulum.Date]: + return self.range("days") + + def __contains__( + self, item: datetime | date | pendulum.DateTime | pendulum.Date + ) -> bool: + return self.start <= item <= self.end + + def __add__(self, other: timedelta) -> Duration: + return self.as_interval().__add__(other) + + __radd__ = __add__ + + def __sub__(self, other: timedelta) -> Duration: + return self.as_interval().__sub__(other) + + def __neg__(self) -> Interval: + return self.__class__(self.end, self.start, self._absolute) + + def __mul__(self, other: int | float) -> Duration: + return self.as_interval().__mul__(other) + + __rmul__ = __mul__ + + @overload + def __floordiv__(self, other: timedelta) -> int: + ... + + @overload + def __floordiv__(self, other: int) -> Duration: + ... + + def __floordiv__(self, other: int | timedelta) -> int | Duration: + return self.as_interval().__floordiv__(other) + + __div__ = __floordiv__ # type: ignore[assignment] + + @overload + def __truediv__(self, other: timedelta) -> float: + ... + + @overload + def __truediv__(self, other: float) -> Duration: + ... + + def __truediv__(self, other: float | timedelta) -> Duration | float: + return self.as_interval().__truediv__(other) + + def __mod__(self, other: timedelta) -> Duration: + return self.as_interval().__mod__(other) + + def __divmod__(self, other: timedelta) -> tuple[int, Duration]: + return self.as_interval().__divmod__(other) + + def __abs__(self) -> Interval: + return self.__class__(self.start, self.end, absolute=True) + + def __repr__(self) -> str: + return f" {self._end}]>" + + def __str__(self) -> str: + return self.__repr__() + + def _cmp(self, other: timedelta) -> int: + # Only needed for PyPy + assert isinstance(other, timedelta) + + if isinstance(other, Interval): + other = other.as_timedelta() + + td = self.as_timedelta() + + return 0 if td == other else 1 if td > other else -1 + + def _getstate( + self, protocol: SupportsIndex = 3 + ) -> tuple[ + pendulum.DateTime | pendulum.Date | datetime | date, + pendulum.DateTime | pendulum.Date | datetime | date, + bool, + ]: + start, end = self.start, self.end + + if self._invert and self._absolute: + end, start = start, end + + return start, end, self._absolute + + def __reduce__( + self, + ) -> tuple[ + type[Interval], + tuple[ + pendulum.DateTime | pendulum.Date | datetime | date, + pendulum.DateTime | pendulum.Date | datetime | date, + bool, + ], + ]: + return self.__reduce_ex__(2) + + def __reduce_ex__( + self, protocol: SupportsIndex + ) -> tuple[ + type[Interval], + tuple[ + pendulum.DateTime | pendulum.Date | datetime | date, + pendulum.DateTime | pendulum.Date | datetime | date, + bool, + ], + ]: + return self.__class__, self._getstate(protocol) + + def __hash__(self) -> int: + return hash((self.start, self.end, self._absolute)) + + def __eq__(self, other: object) -> bool: + if isinstance(other, Interval): + return (self.start, self.end, self._absolute) == ( + other.start, + other.end, + other._absolute, + ) + else: + return self.as_interval() == other diff --git a/pendulum/locales/cs/__init__.py b/pendulum/locales/cs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pendulum/locales/cs/custom.py b/pendulum/locales/cs/custom.py new file mode 100644 index 0000000..5f66b69 --- /dev/null +++ b/pendulum/locales/cs/custom.py @@ -0,0 +1,23 @@ +""" +cs custom locale file. +""" + +translations = { + "units": {"few_second": "pár vteřin"}, + # Relative time + "ago": "{} zpět", + "from_now": "za {}", + "after": "{0} po", + "before": "{0} zpět", + # Ordinals + "ordinal": {"one": ".", "two": ".", "few": ".", "other": "."}, + # Date formats + "date_formats": { + "LTS": "h:mm:ss", + "LT": "h:mm", + "L": "DD. M. YYYY", + "LL": "D. MMMM, YYYY", + "LLL": "D. MMMM, YYYY h:mm", + "LLLL": "dddd, D. MMMM, YYYY h:mm", + }, +} diff --git a/pendulum/locales/cs/locale.py b/pendulum/locales/cs/locale.py new file mode 100644 index 0000000..2c51c78 --- /dev/null +++ b/pendulum/locales/cs/locale.py @@ -0,0 +1,266 @@ +from .custom import translations as custom_translations + + +""" +cs locale file. + +It has been generated automatically and must not be modified directly. +""" + + +locale = { + "plural": lambda n: "few" + if ((n == n and (n >= 2 and n <= 4)) and (0 == 0 and (0 == 0))) + else "many" + if (not (0 == 0 and (0 == 0))) + else "one" + if ((n == n and (n == 1)) and (0 == 0 and (0 == 0))) + else "other", + "ordinal": lambda n: "other", + "translations": { + "days": { + "abbreviated": { + 0: "ne", + 1: "po", + 2: "út", + 3: "st", + 4: "čt", + 5: "pá", + 6: "so", + }, + "narrow": { + 0: "N", + 1: "P", + 2: "Ú", + 3: "S", + 4: "Č", + 5: "P", + 6: "S", + }, + "short": { + 0: "ne", + 1: "po", + 2: "út", + 3: "st", + 4: "čt", + 5: "pá", + 6: "so", + }, + "wide": { + 0: "neděle", + 1: "pondělí", + 2: "úterý", + 3: "středa", + 4: "čtvrtek", + 5: "pátek", + 6: "sobota", + }, + }, + "months": { + "abbreviated": { + 1: "led", + 2: "úno", + 3: "bře", + 4: "dub", + 5: "kvě", + 6: "čvn", + 7: "čvc", + 8: "srp", + 9: "zář", + 10: "říj", + 11: "lis", + 12: "pro", + }, + "narrow": { + 1: "1", + 2: "2", + 3: "3", + 4: "4", + 5: "5", + 6: "6", + 7: "7", + 8: "8", + 9: "9", + 10: "10", + 11: "11", + 12: "12", + }, + "wide": { + 1: "ledna", + 2: "února", + 3: "března", + 4: "dubna", + 5: "května", + 6: "června", + 7: "července", + 8: "srpna", + 9: "září", + 10: "října", + 11: "listopadu", + 12: "prosince", + }, + }, + "units": { + "year": { + "one": "{0} rok", + "few": "{0} roky", + "many": "{0} roku", + "other": "{0} let", + }, + "month": { + "one": "{0} měsíc", + "few": "{0} měsíce", + "many": "{0} měsíce", + "other": "{0} měsíců", + }, + "week": { + "one": "{0} týden", + "few": "{0} týdny", + "many": "{0} týdne", + "other": "{0} týdnů", + }, + "day": { + "one": "{0} den", + "few": "{0} dny", + "many": "{0} dne", + "other": "{0} dní", + }, + "hour": { + "one": "{0} hodina", + "few": "{0} hodiny", + "many": "{0} hodiny", + "other": "{0} hodin", + }, + "minute": { + "one": "{0} minuta", + "few": "{0} minuty", + "many": "{0} minuty", + "other": "{0} minut", + }, + "second": { + "one": "{0} sekunda", + "few": "{0} sekundy", + "many": "{0} sekundy", + "other": "{0} sekund", + }, + "microsecond": { + "one": "{0} mikrosekunda", + "few": "{0} mikrosekundy", + "many": "{0} mikrosekundy", + "other": "{0} mikrosekund", + }, + }, + "relative": { + "year": { + "future": { + "other": "za {0} let", + "one": "za {0} rok", + "few": "za {0} roky", + "many": "za {0} roku", + }, + "past": { + "other": "před {0} lety", + "one": "před {0} rokem", + "few": "před {0} lety", + "many": "před {0} roku", + }, + }, + "month": { + "future": { + "other": "za {0} měsíců", + "one": "za {0} měsíc", + "few": "za {0} měsíce", + "many": "za {0} měsíce", + }, + "past": { + "other": "před {0} měsíci", + "one": "před {0} měsícem", + "few": "před {0} měsíci", + "many": "před {0} měsíce", + }, + }, + "week": { + "future": { + "other": "za {0} týdnů", + "one": "za {0} týden", + "few": "za {0} týdny", + "many": "za {0} týdne", + }, + "past": { + "other": "před {0} týdny", + "one": "před {0} týdnem", + "few": "před {0} týdny", + "many": "před {0} týdne", + }, + }, + "day": { + "future": { + "other": "za {0} dní", + "one": "za {0} den", + "few": "za {0} dny", + "many": "za {0} dne", + }, + "past": { + "other": "před {0} dny", + "one": "před {0} dnem", + "few": "před {0} dny", + "many": "před {0} dne", + }, + }, + "hour": { + "future": { + "other": "za {0} hodin", + "one": "za {0} hodinu", + "few": "za {0} hodiny", + "many": "za {0} hodiny", + }, + "past": { + "other": "před {0} hodinami", + "one": "před {0} hodinou", + "few": "před {0} hodinami", + "many": "před {0} hodiny", + }, + }, + "minute": { + "future": { + "other": "za {0} minut", + "one": "za {0} minutu", + "few": "za {0} minuty", + "many": "za {0} minuty", + }, + "past": { + "other": "před {0} minutami", + "one": "před {0} minutou", + "few": "před {0} minutami", + "many": "před {0} minuty", + }, + }, + "second": { + "future": { + "other": "za {0} sekund", + "one": "za {0} sekundu", + "few": "za {0} sekundy", + "many": "za {0} sekundy", + }, + "past": { + "other": "před {0} sekundami", + "one": "před {0} sekundou", + "few": "před {0} sekundami", + "many": "před {0} sekundy", + }, + }, + }, + "day_periods": { + "midnight": "půlnoc", + "am": "dop.", + "noon": "poledne", + "pm": "odp.", + "morning1": "ráno", + "morning2": "dopoledne", + "afternoon1": "odpoledne", + "evening1": "večer", + "night1": "v noci", + }, + }, + "custom": custom_translations, +} diff --git a/pendulum/locales/da/custom.py b/pendulum/locales/da/custom.py index 258e47b..c62ab83 100644 --- a/pendulum/locales/da/custom.py +++ b/pendulum/locales/da/custom.py @@ -1,22 +1,18 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - - -""" -da custom locale file. -""" - -translations = { - # Relative time - "after": "{0} efter", - "before": "{0} før", - # Date formats - "date_formats": { - "LTS": "HH:mm:ss", - "LT": "HH:mm", - "LLLL": "dddd [d.] D. MMMM YYYY HH:mm", - "LLL": "D. MMMM YYYY HH:mm", - "LL": "D. MMMM YYYY", - "L": "DD/MM/YYYY", - }, -} +""" +da custom locale file. +""" + +translations = { + # Relative time + "after": "{0} efter", + "before": "{0} før", + # Date formats + "date_formats": { + "LTS": "HH:mm:ss", + "LT": "HH:mm", + "LLLL": "dddd [d.] D. MMMM YYYY HH:mm", + "LLL": "D. MMMM YYYY HH:mm", + "LL": "D. MMMM YYYY", + "L": "DD/MM/YYYY", + }, +} diff --git a/pendulum/locales/da/locale.py b/pendulum/locales/da/locale.py index b829e34..936af3a 100644 --- a/pendulum/locales/da/locale.py +++ b/pendulum/locales/da/locale.py @@ -1,150 +1,147 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from .custom import translations as custom_translations - - -""" -da locale file. - -It has been generated automatically and must not be modified directly. -""" - - -locale = { - "plural": lambda n: "one" - if ( - (n == n and ((n == 1))) - or ((not (0 == 0 and ((0 == 0)))) and (n == n and ((n == 0) or (n == 1)))) - ) - else "other", - "ordinal": lambda n: "other", - "translations": { - "days": { - "abbreviated": { - 0: "søn.", - 1: "man.", - 2: "tir.", - 3: "ons.", - 4: "tor.", - 5: "fre.", - 6: "lør.", - }, - "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ø"}, - "wide": { - 0: "søndag", - 1: "mandag", - 2: "tirsdag", - 3: "onsdag", - 4: "torsdag", - 5: "fredag", - 6: "lørdag", - }, - }, - "months": { - "abbreviated": { - 1: "jan.", - 2: "feb.", - 3: "mar.", - 4: "apr.", - 5: "maj", - 6: "jun.", - 7: "jul.", - 8: "aug.", - 9: "sep.", - 10: "okt.", - 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: "januar", - 2: "februar", - 3: "marts", - 4: "april", - 5: "maj", - 6: "juni", - 7: "juli", - 8: "august", - 9: "september", - 10: "oktober", - 11: "november", - 12: "december", - }, - }, - "units": { - "year": {"one": "{0} år", "other": "{0} år"}, - "month": {"one": "{0} måned", "other": "{0} måneder"}, - "week": {"one": "{0} uge", "other": "{0} uger"}, - "day": {"one": "{0} dag", "other": "{0} dage"}, - "hour": {"one": "{0} time", "other": "{0} timer"}, - "minute": {"one": "{0} minut", "other": "{0} minutter"}, - "second": {"one": "{0} sekund", "other": "{0} sekunder"}, - "microsecond": {"one": "{0} mikrosekund", "other": "{0} mikrosekunder"}, - }, - "relative": { - "year": { - "future": {"other": "om {0} år", "one": "om {0} år"}, - "past": {"other": "for {0} år siden", "one": "for {0} år siden"}, - }, - "month": { - "future": {"other": "om {0} måneder", "one": "om {0} måned"}, - "past": { - "other": "for {0} måneder siden", - "one": "for {0} måned siden", - }, - }, - "week": { - "future": {"other": "om {0} uger", "one": "om {0} uge"}, - "past": {"other": "for {0} uger siden", "one": "for {0} uge siden"}, - }, - "day": { - "future": {"other": "om {0} dage", "one": "om {0} dag"}, - "past": {"other": "for {0} dage siden", "one": "for {0} dag siden"}, - }, - "hour": { - "future": {"other": "om {0} timer", "one": "om {0} time"}, - "past": {"other": "for {0} timer siden", "one": "for {0} time siden"}, - }, - "minute": { - "future": {"other": "om {0} minutter", "one": "om {0} minut"}, - "past": { - "other": "for {0} minutter siden", - "one": "for {0} minut siden", - }, - }, - "second": { - "future": {"other": "om {0} sekunder", "one": "om {0} sekund"}, - "past": { - "other": "for {0} sekunder siden", - "one": "for {0} sekund siden", - }, - }, - }, - "day_periods": { - "midnight": "midnat", - "am": "AM", - "pm": "PM", - "morning1": "om morgenen", - "morning2": "om formiddagen", - "afternoon1": "om eftermiddagen", - "evening1": "om aftenen", - "night1": "om natten", - }, - }, - "custom": custom_translations, -} +from .custom import translations as custom_translations + + +""" +da locale file. + +It has been generated automatically and must not be modified directly. +""" + + +locale = { + "plural": lambda n: "one" + if ( + (n == n and (n == 1)) + or ((not (0 == 0 and (0 == 0))) and (n == n and ((n == 0) or (n == 1)))) + ) + else "other", + "ordinal": lambda n: "other", + "translations": { + "days": { + "abbreviated": { + 0: "søn.", + 1: "man.", + 2: "tir.", + 3: "ons.", + 4: "tor.", + 5: "fre.", + 6: "lør.", + }, + "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ø"}, + "wide": { + 0: "søndag", + 1: "mandag", + 2: "tirsdag", + 3: "onsdag", + 4: "torsdag", + 5: "fredag", + 6: "lørdag", + }, + }, + "months": { + "abbreviated": { + 1: "jan.", + 2: "feb.", + 3: "mar.", + 4: "apr.", + 5: "maj", + 6: "jun.", + 7: "jul.", + 8: "aug.", + 9: "sep.", + 10: "okt.", + 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: "januar", + 2: "februar", + 3: "marts", + 4: "april", + 5: "maj", + 6: "juni", + 7: "juli", + 8: "august", + 9: "september", + 10: "oktober", + 11: "november", + 12: "december", + }, + }, + "units": { + "year": {"one": "{0} år", "other": "{0} år"}, + "month": {"one": "{0} måned", "other": "{0} måneder"}, + "week": {"one": "{0} uge", "other": "{0} uger"}, + "day": {"one": "{0} dag", "other": "{0} dage"}, + "hour": {"one": "{0} time", "other": "{0} timer"}, + "minute": {"one": "{0} minut", "other": "{0} minutter"}, + "second": {"one": "{0} sekund", "other": "{0} sekunder"}, + "microsecond": {"one": "{0} mikrosekund", "other": "{0} mikrosekunder"}, + }, + "relative": { + "year": { + "future": {"other": "om {0} år", "one": "om {0} år"}, + "past": {"other": "for {0} år siden", "one": "for {0} år siden"}, + }, + "month": { + "future": {"other": "om {0} måneder", "one": "om {0} måned"}, + "past": { + "other": "for {0} måneder siden", + "one": "for {0} måned siden", + }, + }, + "week": { + "future": {"other": "om {0} uger", "one": "om {0} uge"}, + "past": {"other": "for {0} uger siden", "one": "for {0} uge siden"}, + }, + "day": { + "future": {"other": "om {0} dage", "one": "om {0} dag"}, + "past": {"other": "for {0} dage siden", "one": "for {0} dag siden"}, + }, + "hour": { + "future": {"other": "om {0} timer", "one": "om {0} time"}, + "past": {"other": "for {0} timer siden", "one": "for {0} time siden"}, + }, + "minute": { + "future": {"other": "om {0} minutter", "one": "om {0} minut"}, + "past": { + "other": "for {0} minutter siden", + "one": "for {0} minut siden", + }, + }, + "second": { + "future": {"other": "om {0} sekunder", "one": "om {0} sekund"}, + "past": { + "other": "for {0} sekunder siden", + "one": "for {0} sekund siden", + }, + }, + }, + "day_periods": { + "midnight": "midnat", + "am": "AM", + "pm": "PM", + "morning1": "om morgenen", + "morning2": "om formiddagen", + "afternoon1": "om eftermiddagen", + "evening1": "om aftenen", + "night1": "om natten", + }, + }, + "custom": custom_translations, +} diff --git a/pendulum/locales/de/custom.py b/pendulum/locales/de/custom.py index 3024f0b..a19a8e1 100644 --- a/pendulum/locales/de/custom.py +++ b/pendulum/locales/de/custom.py @@ -1,40 +1,36 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - - -""" -de custom locale file. -""" - -translations = { - # Relative time - "after": "{0} später", - "before": "{0} zuvor", - "units_relative": { - "year": { - "future": {"one": "{0} Jahr", "other": "{0} Jahren"}, - "past": {"one": "{0} Jahr", "other": "{0} Jahren"}, - }, - "month": { - "future": {"one": "{0} Monat", "other": "{0} Monaten"}, - "past": {"one": "{0} Monat", "other": "{0} Monaten"}, - }, - "week": { - "future": {"one": "{0} Woche", "other": "{0} Wochen"}, - "past": {"one": "{0} Woche", "other": "{0} Wochen"}, - }, - "day": { - "future": {"one": "{0} Tag", "other": "{0} Tagen"}, - "past": {"one": "{0} Tag", "other": "{0} Tagen"}, - }, - }, - # Date formats - "date_formats": { - "LTS": "HH:mm:ss", - "LT": "HH:mm", - "LLLL": "dddd, D. MMMM YYYY HH:mm", - "LLL": "D. MMMM YYYY HH:mm", - "LL": "D. MMMM YYYY", - "L": "DD.MM.YYYY", - }, -} +""" +de custom locale file. +""" + +translations = { + # Relative time + "after": "{0} später", + "before": "{0} zuvor", + "units_relative": { + "year": { + "future": {"one": "{0} Jahr", "other": "{0} Jahren"}, + "past": {"one": "{0} Jahr", "other": "{0} Jahren"}, + }, + "month": { + "future": {"one": "{0} Monat", "other": "{0} Monaten"}, + "past": {"one": "{0} Monat", "other": "{0} Monaten"}, + }, + "week": { + "future": {"one": "{0} Woche", "other": "{0} Wochen"}, + "past": {"one": "{0} Woche", "other": "{0} Wochen"}, + }, + "day": { + "future": {"one": "{0} Tag", "other": "{0} Tagen"}, + "past": {"one": "{0} Tag", "other": "{0} Tagen"}, + }, + }, + # Date formats + "date_formats": { + "LTS": "HH:mm:ss", + "LT": "HH:mm", + "LLLL": "dddd, D. MMMM YYYY HH:mm", + "LLL": "D. MMMM YYYY HH:mm", + "LL": "D. MMMM YYYY", + "L": "DD.MM.YYYY", + }, +} diff --git a/pendulum/locales/de/locale.py b/pendulum/locales/de/locale.py index b180fc5..94d2ff1 100644 --- a/pendulum/locales/de/locale.py +++ b/pendulum/locales/de/locale.py @@ -1,147 +1,144 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from .custom import translations as custom_translations - - -""" -de 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: "other", - "translations": { - "days": { - "abbreviated": { - 0: "So.", - 1: "Mo.", - 2: "Di.", - 3: "Mi.", - 4: "Do.", - 5: "Fr.", - 6: "Sa.", - }, - "narrow": {0: "S", 1: "M", 2: "D", 3: "M", 4: "D", 5: "F", 6: "S"}, - "short": { - 0: "So.", - 1: "Mo.", - 2: "Di.", - 3: "Mi.", - 4: "Do.", - 5: "Fr.", - 6: "Sa.", - }, - "wide": { - 0: "Sonntag", - 1: "Montag", - 2: "Dienstag", - 3: "Mittwoch", - 4: "Donnerstag", - 5: "Freitag", - 6: "Samstag", - }, - }, - "months": { - "abbreviated": { - 1: "Jan.", - 2: "Feb.", - 3: "März", - 4: "Apr.", - 5: "Mai", - 6: "Juni", - 7: "Juli", - 8: "Aug.", - 9: "Sep.", - 10: "Okt.", - 11: "Nov.", - 12: "Dez.", - }, - "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: "Januar", - 2: "Februar", - 3: "März", - 4: "April", - 5: "Mai", - 6: "Juni", - 7: "Juli", - 8: "August", - 9: "September", - 10: "Oktober", - 11: "November", - 12: "Dezember", - }, - }, - "units": { - "year": {"one": "{0} Jahr", "other": "{0} Jahre"}, - "month": {"one": "{0} Monat", "other": "{0} Monate"}, - "week": {"one": "{0} Woche", "other": "{0} Wochen"}, - "day": {"one": "{0} Tag", "other": "{0} Tage"}, - "hour": {"one": "{0} Stunde", "other": "{0} Stunden"}, - "minute": {"one": "{0} Minute", "other": "{0} Minuten"}, - "second": {"one": "{0} Sekunde", "other": "{0} Sekunden"}, - "microsecond": {"one": "{0} Mikrosekunde", "other": "{0} Mikrosekunden"}, - }, - "relative": { - "year": { - "future": {"other": "in {0} Jahren", "one": "in {0} Jahr"}, - "past": {"other": "vor {0} Jahren", "one": "vor {0} Jahr"}, - }, - "month": { - "future": {"other": "in {0} Monaten", "one": "in {0} Monat"}, - "past": {"other": "vor {0} Monaten", "one": "vor {0} Monat"}, - }, - "week": { - "future": {"other": "in {0} Wochen", "one": "in {0} Woche"}, - "past": {"other": "vor {0} Wochen", "one": "vor {0} Woche"}, - }, - "day": { - "future": {"other": "in {0} Tagen", "one": "in {0} Tag"}, - "past": {"other": "vor {0} Tagen", "one": "vor {0} Tag"}, - }, - "hour": { - "future": {"other": "in {0} Stunden", "one": "in {0} Stunde"}, - "past": {"other": "vor {0} Stunden", "one": "vor {0} Stunde"}, - }, - "minute": { - "future": {"other": "in {0} Minuten", "one": "in {0} Minute"}, - "past": {"other": "vor {0} Minuten", "one": "vor {0} Minute"}, - }, - "second": { - "future": {"other": "in {0} Sekunden", "one": "in {0} Sekunde"}, - "past": {"other": "vor {0} Sekunden", "one": "vor {0} Sekunde"}, - }, - }, - "day_periods": { - "midnight": "Mitternacht", - "am": "vorm.", - "pm": "nachm.", - "morning1": "morgens", - "morning2": "vormittags", - "afternoon1": "mittags", - "afternoon2": "nachmittags", - "evening1": "abends", - "night1": "nachts", - }, - }, - "custom": custom_translations, -} +from .custom import translations as custom_translations + + +""" +de 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: "other", + "translations": { + "days": { + "abbreviated": { + 0: "So.", + 1: "Mo.", + 2: "Di.", + 3: "Mi.", + 4: "Do.", + 5: "Fr.", + 6: "Sa.", + }, + "narrow": {0: "S", 1: "M", 2: "D", 3: "M", 4: "D", 5: "F", 6: "S"}, + "short": { + 0: "So.", + 1: "Mo.", + 2: "Di.", + 3: "Mi.", + 4: "Do.", + 5: "Fr.", + 6: "Sa.", + }, + "wide": { + 0: "Sonntag", + 1: "Montag", + 2: "Dienstag", + 3: "Mittwoch", + 4: "Donnerstag", + 5: "Freitag", + 6: "Samstag", + }, + }, + "months": { + "abbreviated": { + 1: "Jan.", + 2: "Feb.", + 3: "März", + 4: "Apr.", + 5: "Mai", + 6: "Juni", + 7: "Juli", + 8: "Aug.", + 9: "Sep.", + 10: "Okt.", + 11: "Nov.", + 12: "Dez.", + }, + "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: "Januar", + 2: "Februar", + 3: "März", + 4: "April", + 5: "Mai", + 6: "Juni", + 7: "Juli", + 8: "August", + 9: "September", + 10: "Oktober", + 11: "November", + 12: "Dezember", + }, + }, + "units": { + "year": {"one": "{0} Jahr", "other": "{0} Jahre"}, + "month": {"one": "{0} Monat", "other": "{0} Monate"}, + "week": {"one": "{0} Woche", "other": "{0} Wochen"}, + "day": {"one": "{0} Tag", "other": "{0} Tage"}, + "hour": {"one": "{0} Stunde", "other": "{0} Stunden"}, + "minute": {"one": "{0} Minute", "other": "{0} Minuten"}, + "second": {"one": "{0} Sekunde", "other": "{0} Sekunden"}, + "microsecond": {"one": "{0} Mikrosekunde", "other": "{0} Mikrosekunden"}, + }, + "relative": { + "year": { + "future": {"other": "in {0} Jahren", "one": "in {0} Jahr"}, + "past": {"other": "vor {0} Jahren", "one": "vor {0} Jahr"}, + }, + "month": { + "future": {"other": "in {0} Monaten", "one": "in {0} Monat"}, + "past": {"other": "vor {0} Monaten", "one": "vor {0} Monat"}, + }, + "week": { + "future": {"other": "in {0} Wochen", "one": "in {0} Woche"}, + "past": {"other": "vor {0} Wochen", "one": "vor {0} Woche"}, + }, + "day": { + "future": {"other": "in {0} Tagen", "one": "in {0} Tag"}, + "past": {"other": "vor {0} Tagen", "one": "vor {0} Tag"}, + }, + "hour": { + "future": {"other": "in {0} Stunden", "one": "in {0} Stunde"}, + "past": {"other": "vor {0} Stunden", "one": "vor {0} Stunde"}, + }, + "minute": { + "future": {"other": "in {0} Minuten", "one": "in {0} Minute"}, + "past": {"other": "vor {0} Minuten", "one": "vor {0} Minute"}, + }, + "second": { + "future": {"other": "in {0} Sekunden", "one": "in {0} Sekunde"}, + "past": {"other": "vor {0} Sekunden", "one": "vor {0} Sekunde"}, + }, + }, + "day_periods": { + "midnight": "Mitternacht", + "am": "vorm.", + "pm": "nachm.", + "morning1": "morgens", + "morning2": "vormittags", + "afternoon1": "mittags", + "afternoon2": "nachmittags", + "evening1": "abends", + "night1": "nachts", + }, + }, + "custom": custom_translations, +} diff --git a/pendulum/locales/en/custom.py b/pendulum/locales/en/custom.py index de224e0..a403ad8 100644 --- a/pendulum/locales/en/custom.py +++ b/pendulum/locales/en/custom.py @@ -1,27 +1,23 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - - -""" -en custom locale file. -""" - -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", - }, -} +""" +en custom locale file. +""" + +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/pendulum/locales/en/locale.py b/pendulum/locales/en/locale.py index acee4d2..00eafc2 100644 --- a/pendulum/locales/en/locale.py +++ b/pendulum/locales/en/locale.py @@ -1,153 +1,150 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from .custom import translations as custom_translations - - -""" -en 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: "Sun", - 1: "Mon", - 2: "Tue", - 3: "Wed", - 4: "Thu", - 5: "Fri", - 6: "Sat", - }, - "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"}, - "wide": { - 0: "Sunday", - 1: "Monday", - 2: "Tuesday", - 3: "Wednesday", - 4: "Thursday", - 5: "Friday", - 6: "Saturday", - }, - }, - "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", - }, - }, - "custom": custom_translations, -} +from .custom import translations as custom_translations + + +""" +en 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: "Sun", + 1: "Mon", + 2: "Tue", + 3: "Wed", + 4: "Thu", + 5: "Fri", + 6: "Sat", + }, + "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"}, + "wide": { + 0: "Sunday", + 1: "Monday", + 2: "Tuesday", + 3: "Wednesday", + 4: "Thursday", + 5: "Friday", + 6: "Saturday", + }, + }, + "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", + }, + }, + "custom": custom_translations, +} diff --git a/pendulum/locales/es/custom.py b/pendulum/locales/es/custom.py index 5862f7e..4b7e2b5 100644 --- a/pendulum/locales/es/custom.py +++ b/pendulum/locales/es/custom.py @@ -1,27 +1,23 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - - -""" -es custom locale file. -""" - -translations = { - "units": {"few_second": "unos segundos"}, - # Relative time - "ago": "hace {0}", - "from_now": "dentro de {0}", - "after": "{0} después", - "before": "{0} antes", - # Ordinals - "ordinal": {"other": "º"}, - # Date formats - "date_formats": { - "LTS": "H:mm:ss", - "LT": "H:mm", - "LLLL": "dddd, D [de] MMMM [de] YYYY H:mm", - "LLL": "D [de] MMMM [de] YYYY H:mm", - "LL": "D [de] MMMM [de] YYYY", - "L": "DD/MM/YYYY", - }, -} +""" +es custom locale file. +""" + +translations = { + "units": {"few_second": "unos segundos"}, + # Relative time + "ago": "hace {0}", + "from_now": "dentro de {0}", + "after": "{0} después", + "before": "{0} antes", + # Ordinals + "ordinal": {"other": "º"}, + # Date formats + "date_formats": { + "LTS": "H:mm:ss", + "LT": "H:mm", + "LLLL": "dddd, D [de] MMMM [de] YYYY H:mm", + "LLL": "D [de] MMMM [de] YYYY H:mm", + "LL": "D [de] MMMM [de] YYYY", + "L": "DD/MM/YYYY", + }, +} diff --git a/pendulum/locales/es/locale.py b/pendulum/locales/es/locale.py index f385e4c..edba4d3 100644 --- a/pendulum/locales/es/locale.py +++ b/pendulum/locales/es/locale.py @@ -1,144 +1,141 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from .custom import translations as custom_translations - - -""" -es 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: "dom.", - 1: "lun.", - 2: "mar.", - 3: "mié.", - 4: "jue.", - 5: "vie.", - 6: "sáb.", - }, - "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"}, - "wide": { - 0: "domingo", - 1: "lunes", - 2: "martes", - 3: "miércoles", - 4: "jueves", - 5: "viernes", - 6: "sábado", - }, - }, - "months": { - "abbreviated": { - 1: "ene.", - 2: "feb.", - 3: "mar.", - 4: "abr.", - 5: "may.", - 6: "jun.", - 7: "jul.", - 8: "ago.", - 9: "sept.", - 10: "oct.", - 11: "nov.", - 12: "dic.", - }, - "narrow": { - 1: "E", - 2: "F", - 3: "M", - 4: "A", - 5: "M", - 6: "J", - 7: "J", - 8: "A", - 9: "S", - 10: "O", - 11: "N", - 12: "D", - }, - "wide": { - 1: "enero", - 2: "febrero", - 3: "marzo", - 4: "abril", - 5: "mayo", - 6: "junio", - 7: "julio", - 8: "agosto", - 9: "septiembre", - 10: "octubre", - 11: "noviembre", - 12: "diciembre", - }, - }, - "units": { - "year": {"one": "{0} año", "other": "{0} años"}, - "month": {"one": "{0} mes", "other": "{0} meses"}, - "week": {"one": "{0} semana", "other": "{0} semanas"}, - "day": {"one": "{0} día", "other": "{0} días"}, - "hour": {"one": "{0} hora", "other": "{0} horas"}, - "minute": {"one": "{0} minuto", "other": "{0} minutos"}, - "second": {"one": "{0} segundo", "other": "{0} segundos"}, - "microsecond": {"one": "{0} microsegundo", "other": "{0} microsegundos"}, - }, - "relative": { - "year": { - "future": {"other": "dentro de {0} años", "one": "dentro de {0} año"}, - "past": {"other": "hace {0} años", "one": "hace {0} año"}, - }, - "month": { - "future": {"other": "dentro de {0} meses", "one": "dentro de {0} mes"}, - "past": {"other": "hace {0} meses", "one": "hace {0} mes"}, - }, - "week": { - "future": { - "other": "dentro de {0} semanas", - "one": "dentro de {0} semana", - }, - "past": {"other": "hace {0} semanas", "one": "hace {0} semana"}, - }, - "day": { - "future": {"other": "dentro de {0} días", "one": "dentro de {0} día"}, - "past": {"other": "hace {0} días", "one": "hace {0} día"}, - }, - "hour": { - "future": {"other": "dentro de {0} horas", "one": "dentro de {0} hora"}, - "past": {"other": "hace {0} horas", "one": "hace {0} hora"}, - }, - "minute": { - "future": { - "other": "dentro de {0} minutos", - "one": "dentro de {0} minuto", - }, - "past": {"other": "hace {0} minutos", "one": "hace {0} minuto"}, - }, - "second": { - "future": { - "other": "dentro de {0} segundos", - "one": "dentro de {0} segundo", - }, - "past": {"other": "hace {0} segundos", "one": "hace {0} segundo"}, - }, - }, - "day_periods": { - "am": "a. m.", - "noon": "del mediodía", - "pm": "p. m.", - "morning1": "de la madrugada", - "morning2": "de la mañana", - "evening1": "de la tarde", - "night1": "de la noche", - }, - }, - "custom": custom_translations, -} +from .custom import translations as custom_translations + + +""" +es 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: "dom.", + 1: "lun.", + 2: "mar.", + 3: "mié.", + 4: "jue.", + 5: "vie.", + 6: "sáb.", + }, + "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"}, + "wide": { + 0: "domingo", + 1: "lunes", + 2: "martes", + 3: "miércoles", + 4: "jueves", + 5: "viernes", + 6: "sábado", + }, + }, + "months": { + "abbreviated": { + 1: "ene.", + 2: "feb.", + 3: "mar.", + 4: "abr.", + 5: "may.", + 6: "jun.", + 7: "jul.", + 8: "ago.", + 9: "sept.", + 10: "oct.", + 11: "nov.", + 12: "dic.", + }, + "narrow": { + 1: "E", + 2: "F", + 3: "M", + 4: "A", + 5: "M", + 6: "J", + 7: "J", + 8: "A", + 9: "S", + 10: "O", + 11: "N", + 12: "D", + }, + "wide": { + 1: "enero", + 2: "febrero", + 3: "marzo", + 4: "abril", + 5: "mayo", + 6: "junio", + 7: "julio", + 8: "agosto", + 9: "septiembre", + 10: "octubre", + 11: "noviembre", + 12: "diciembre", + }, + }, + "units": { + "year": {"one": "{0} año", "other": "{0} años"}, + "month": {"one": "{0} mes", "other": "{0} meses"}, + "week": {"one": "{0} semana", "other": "{0} semanas"}, + "day": {"one": "{0} día", "other": "{0} días"}, + "hour": {"one": "{0} hora", "other": "{0} horas"}, + "minute": {"one": "{0} minuto", "other": "{0} minutos"}, + "second": {"one": "{0} segundo", "other": "{0} segundos"}, + "microsecond": {"one": "{0} microsegundo", "other": "{0} microsegundos"}, + }, + "relative": { + "year": { + "future": {"other": "dentro de {0} años", "one": "dentro de {0} año"}, + "past": {"other": "hace {0} años", "one": "hace {0} año"}, + }, + "month": { + "future": {"other": "dentro de {0} meses", "one": "dentro de {0} mes"}, + "past": {"other": "hace {0} meses", "one": "hace {0} mes"}, + }, + "week": { + "future": { + "other": "dentro de {0} semanas", + "one": "dentro de {0} semana", + }, + "past": {"other": "hace {0} semanas", "one": "hace {0} semana"}, + }, + "day": { + "future": {"other": "dentro de {0} días", "one": "dentro de {0} día"}, + "past": {"other": "hace {0} días", "one": "hace {0} día"}, + }, + "hour": { + "future": {"other": "dentro de {0} horas", "one": "dentro de {0} hora"}, + "past": {"other": "hace {0} horas", "one": "hace {0} hora"}, + }, + "minute": { + "future": { + "other": "dentro de {0} minutos", + "one": "dentro de {0} minuto", + }, + "past": {"other": "hace {0} minutos", "one": "hace {0} minuto"}, + }, + "second": { + "future": { + "other": "dentro de {0} segundos", + "one": "dentro de {0} segundo", + }, + "past": {"other": "hace {0} segundos", "one": "hace {0} segundo"}, + }, + }, + "day_periods": { + "am": "a. m.", + "noon": "del mediodía", + "pm": "p. m.", + "morning1": "de la madrugada", + "morning2": "de la mañana", + "evening1": "de la tarde", + "night1": "de la noche", + }, + }, + "custom": custom_translations, +} diff --git a/pendulum/locales/fa/custom.py b/pendulum/locales/fa/custom.py index fa5a7c1..082bfad 100644 --- a/pendulum/locales/fa/custom.py +++ b/pendulum/locales/fa/custom.py @@ -1,22 +1,18 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - - -""" -fa custom locale file. -""" - -translations = { - # Relative time - "after": "{0} پس از", - "before": "{0} پیش از", - # Date formats - "date_formats": { - "LTS": "HH:mm:ss", - "LT": "HH:mm", - "LLLL": "dddd, D MMMM YYYY HH:mm", - "LLL": "D MMMM YYYY HH:mm", - "LL": "D MMMM YYYY", - "L": "DD/MM/YYYY", - }, -} +""" +fa custom locale file. +""" + +translations = { + # Relative time + "after": "{0} پس از", + "before": "{0} پیش از", + # Date formats + "date_formats": { + "LTS": "HH:mm:ss", + "LT": "HH:mm", + "LLLL": "dddd, D MMMM YYYY HH:mm", + "LLL": "D MMMM YYYY HH:mm", + "LL": "D MMMM YYYY", + "L": "DD/MM/YYYY", + }, +} diff --git a/pendulum/locales/fa/locale.py b/pendulum/locales/fa/locale.py index f18b0f6..32f8e5f 100644 --- a/pendulum/locales/fa/locale.py +++ b/pendulum/locales/fa/locale.py @@ -1,138 +1,135 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from .custom import translations as custom_translations - - -""" -fa locale file. - -It has been generated automatically and must not be modified directly. -""" - - -locale = { - "plural": lambda n: "one" - if ((n == n and ((n == 0))) or (n == n and ((n == 1)))) - else "other", - "ordinal": lambda n: "other", - "translations": { - "days": { - "abbreviated": { - 0: "یکشنبه", - 1: "دوشنبه", - 2: "سه\u200cشنبه", - 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: "شنبه", - }, - }, - "months": { - "abbreviated": { - 1: "ژانویهٔ", - 2: "فوریهٔ", - 3: "مارس", - 4: "آوریل", - 5: "مهٔ", - 6: "ژوئن", - 7: "ژوئیهٔ", - 8: "اوت", - 9: "سپتامبر", - 10: "اکتبر", - 11: "نوامبر", - 12: "دسامبر", - }, - "narrow": { - 1: "ژ", - 2: "ف", - 3: "م", - 4: "آ", - 5: "م", - 6: "ژ", - 7: "ژ", - 8: "ا", - 9: "س", - 10: "ا", - 11: "ن", - 12: "د", - }, - "wide": { - 1: "ژانویهٔ", - 2: "فوریهٔ", - 3: "مارس", - 4: "آوریل", - 5: "مهٔ", - 6: "ژوئن", - 7: "ژوئیهٔ", - 8: "اوت", - 9: "سپتامبر", - 10: "اکتبر", - 11: "نوامبر", - 12: "دسامبر", - }, - }, - "units": { - "year": {"one": "{0} سال", "other": "{0} سال"}, - "month": {"one": "{0} ماه", "other": "{0} ماه"}, - "week": {"one": "{0} هفته", "other": "{0} هفته"}, - "day": {"one": "{0} روز", "other": "{0} روز"}, - "hour": {"one": "{0} ساعت", "other": "{0} ساعت"}, - "minute": {"one": "{0} دقیقه", "other": "{0} دقیقه"}, - "second": {"one": "{0} ثانیه", "other": "{0} ثانیه"}, - "microsecond": {"one": "{0} میکروثانیه", "other": "{0} میکروثانیه"}, - }, - "relative": { - "year": { - "future": {"other": "{0} سال بعد", "one": "{0} سال بعد"}, - "past": {"other": "{0} سال پیش", "one": "{0} سال پیش"}, - }, - "month": { - "future": {"other": "{0} ماه بعد", "one": "{0} ماه بعد"}, - "past": {"other": "{0} ماه پیش", "one": "{0} ماه پیش"}, - }, - "week": { - "future": {"other": "{0} هفته بعد", "one": "{0} هفته بعد"}, - "past": {"other": "{0} هفته پیش", "one": "{0} هفته پیش"}, - }, - "day": { - "future": {"other": "{0} روز بعد", "one": "{0} روز بعد"}, - "past": {"other": "{0} روز پیش", "one": "{0} روز پیش"}, - }, - "hour": { - "future": {"other": "{0} ساعت بعد", "one": "{0} ساعت بعد"}, - "past": {"other": "{0} ساعت پیش", "one": "{0} ساعت پیش"}, - }, - "minute": { - "future": {"other": "{0} دقیقه بعد", "one": "{0} دقیقه بعد"}, - "past": {"other": "{0} دقیقه پیش", "one": "{0} دقیقه پیش"}, - }, - "second": { - "future": {"other": "{0} ثانیه بعد", "one": "{0} ثانیه بعد"}, - "past": {"other": "{0} ثانیه پیش", "one": "{0} ثانیه پیش"}, - }, - }, - "day_periods": { - "midnight": "نیمه\u200cشب", - "am": "قبل\u200cازظهر", - "noon": "ظهر", - "pm": "بعدازظهر", - "morning1": "صبح", - "afternoon1": "عصر", - "evening1": "عصر", - "night1": "شب", - }, - }, - "custom": custom_translations, -} +from .custom import translations as custom_translations + + +""" +fa locale file. + +It has been generated automatically and must not be modified directly. +""" + + +locale = { + "plural": lambda n: "one" + if ((n == n and (n == 0)) or (n == n and (n == 1))) + else "other", + "ordinal": lambda n: "other", + "translations": { + "days": { + "abbreviated": { + 0: "یکشنبه", + 1: "دوشنبه", + 2: "سه\u200cشنبه", + 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: "شنبه", + }, + }, + "months": { + "abbreviated": { + 1: "ژانویهٔ", + 2: "فوریهٔ", + 3: "مارس", + 4: "آوریل", + 5: "مهٔ", + 6: "ژوئن", + 7: "ژوئیهٔ", + 8: "اوت", + 9: "سپتامبر", + 10: "اکتبر", + 11: "نوامبر", + 12: "دسامبر", + }, + "narrow": { + 1: "ژ", + 2: "ف", + 3: "م", + 4: "آ", + 5: "م", + 6: "ژ", + 7: "ژ", + 8: "ا", + 9: "س", + 10: "ا", + 11: "ن", + 12: "د", + }, + "wide": { + 1: "ژانویهٔ", + 2: "فوریهٔ", + 3: "مارس", + 4: "آوریل", + 5: "مهٔ", + 6: "ژوئن", + 7: "ژوئیهٔ", + 8: "اوت", + 9: "سپتامبر", + 10: "اکتبر", + 11: "نوامبر", + 12: "دسامبر", + }, + }, + "units": { + "year": {"one": "{0} سال", "other": "{0} سال"}, + "month": {"one": "{0} ماه", "other": "{0} ماه"}, + "week": {"one": "{0} هفته", "other": "{0} هفته"}, + "day": {"one": "{0} روز", "other": "{0} روز"}, + "hour": {"one": "{0} ساعت", "other": "{0} ساعت"}, + "minute": {"one": "{0} دقیقه", "other": "{0} دقیقه"}, + "second": {"one": "{0} ثانیه", "other": "{0} ثانیه"}, + "microsecond": {"one": "{0} میکروثانیه", "other": "{0} میکروثانیه"}, + }, + "relative": { + "year": { + "future": {"other": "{0} سال بعد", "one": "{0} سال بعد"}, + "past": {"other": "{0} سال پیش", "one": "{0} سال پیش"}, + }, + "month": { + "future": {"other": "{0} ماه بعد", "one": "{0} ماه بعد"}, + "past": {"other": "{0} ماه پیش", "one": "{0} ماه پیش"}, + }, + "week": { + "future": {"other": "{0} هفته بعد", "one": "{0} هفته بعد"}, + "past": {"other": "{0} هفته پیش", "one": "{0} هفته پیش"}, + }, + "day": { + "future": {"other": "{0} روز بعد", "one": "{0} روز بعد"}, + "past": {"other": "{0} روز پیش", "one": "{0} روز پیش"}, + }, + "hour": { + "future": {"other": "{0} ساعت بعد", "one": "{0} ساعت بعد"}, + "past": {"other": "{0} ساعت پیش", "one": "{0} ساعت پیش"}, + }, + "minute": { + "future": {"other": "{0} دقیقه بعد", "one": "{0} دقیقه بعد"}, + "past": {"other": "{0} دقیقه پیش", "one": "{0} دقیقه پیش"}, + }, + "second": { + "future": {"other": "{0} ثانیه بعد", "one": "{0} ثانیه بعد"}, + "past": {"other": "{0} ثانیه پیش", "one": "{0} ثانیه پیش"}, + }, + }, + "day_periods": { + "midnight": "نیمه\u200cشب", + "am": "قبل\u200cازظهر", + "noon": "ظهر", + "pm": "بعدازظهر", + "morning1": "صبح", + "afternoon1": "عصر", + "evening1": "عصر", + "night1": "شب", + }, + }, + "custom": custom_translations, +} diff --git a/pendulum/locales/fo/custom.py b/pendulum/locales/fo/custom.py index 946ab19..456dd59 100644 --- a/pendulum/locales/fo/custom.py +++ b/pendulum/locales/fo/custom.py @@ -1,24 +1,20 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - - -""" -fo custom locale file. -""" - -translations = { - # Relative time - "after": "{0} aftaná", - "before": "{0} áðrenn", - # Ordinals - "ordinal": {"other": "."}, - # Date formats - "date_formats": { - "LTS": "HH:mm:ss", - "LT": "HH:mm", - "LLLL": "dddd D. MMMM, YYYY HH:mm", - "LLL": "D MMMM YYYY HH:mm", - "LL": "D MMMM YYYY", - "L": "DD/MM/YYYY", - }, -} +""" +fo custom locale file. +""" + +translations = { + # Relative time + "after": "{0} aftaná", + "before": "{0} áðrenn", + # Ordinals + "ordinal": {"other": "."}, + # Date formats + "date_formats": { + "LTS": "HH:mm:ss", + "LT": "HH:mm", + "LLLL": "dddd D. MMMM, YYYY HH:mm", + "LLL": "D MMMM YYYY HH:mm", + "LL": "D MMMM YYYY", + "L": "DD/MM/YYYY", + }, +} diff --git a/pendulum/locales/fo/locale.py b/pendulum/locales/fo/locale.py index 345f524..10319ea 100644 --- a/pendulum/locales/fo/locale.py +++ b/pendulum/locales/fo/locale.py @@ -1,135 +1,132 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from .custom import translations as custom_translations - - -""" -fo 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: "sun.", - 1: "mán.", - 2: "týs.", - 3: "mik.", - 4: "hós.", - 5: "frí.", - 6: "ley.", - }, - "narrow": {0: "S", 1: "M", 2: "T", 3: "M", 4: "H", 5: "F", 6: "L"}, - "short": { - 0: "su.", - 1: "má.", - 2: "tý.", - 3: "mi.", - 4: "hó.", - 5: "fr.", - 6: "le.", - }, - "wide": { - 0: "sunnudagur", - 1: "mánadagur", - 2: "týsdagur", - 3: "mikudagur", - 4: "hósdagur", - 5: "fríggjadagur", - 6: "leygardagur", - }, - }, - "months": { - "abbreviated": { - 1: "jan.", - 2: "feb.", - 3: "mar.", - 4: "apr.", - 5: "mai", - 6: "jun.", - 7: "jul.", - 8: "aug.", - 9: "sep.", - 10: "okt.", - 11: "nov.", - 12: "des.", - }, - "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: "januar", - 2: "februar", - 3: "mars", - 4: "apríl", - 5: "mai", - 6: "juni", - 7: "juli", - 8: "august", - 9: "september", - 10: "oktober", - 11: "november", - 12: "desember", - }, - }, - "units": { - "year": {"one": "{0} ár", "other": "{0} ár"}, - "month": {"one": "{0} mánaður", "other": "{0} mánaðir"}, - "week": {"one": "{0} vika", "other": "{0} vikur"}, - "day": {"one": "{0} dagur", "other": "{0} dagar"}, - "hour": {"one": "{0} tími", "other": "{0} tímar"}, - "minute": {"one": "{0} minuttur", "other": "{0} minuttir"}, - "second": {"one": "{0} sekund", "other": "{0} sekundir"}, - "microsecond": {"one": "{0} mikrosekund", "other": "{0} mikrosekundir"}, - }, - "relative": { - "year": { - "future": {"other": "um {0} ár", "one": "um {0} ár"}, - "past": {"other": "{0} ár síðan", "one": "{0} ár síðan"}, - }, - "month": { - "future": {"other": "um {0} mánaðir", "one": "um {0} mánað"}, - "past": {"other": "{0} mánaðir síðan", "one": "{0} mánað síðan"}, - }, - "week": { - "future": {"other": "um {0} vikur", "one": "um {0} viku"}, - "past": {"other": "{0} vikur síðan", "one": "{0} vika síðan"}, - }, - "day": { - "future": {"other": "um {0} dagar", "one": "um {0} dag"}, - "past": {"other": "{0} dagar síðan", "one": "{0} dagur síðan"}, - }, - "hour": { - "future": {"other": "um {0} tímar", "one": "um {0} tíma"}, - "past": {"other": "{0} tímar síðan", "one": "{0} tími síðan"}, - }, - "minute": { - "future": {"other": "um {0} minuttir", "one": "um {0} minutt"}, - "past": {"other": "{0} minuttir síðan", "one": "{0} minutt síðan"}, - }, - "second": { - "future": {"other": "um {0} sekund", "one": "um {0} sekund"}, - "past": {"other": "{0} sekund síðan", "one": "{0} sekund síðan"}, - }, - }, - "day_periods": {"am": "AM", "pm": "PM"}, - }, - "custom": custom_translations, -} +from .custom import translations as custom_translations + + +""" +fo 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: "sun.", + 1: "mán.", + 2: "týs.", + 3: "mik.", + 4: "hós.", + 5: "frí.", + 6: "ley.", + }, + "narrow": {0: "S", 1: "M", 2: "T", 3: "M", 4: "H", 5: "F", 6: "L"}, + "short": { + 0: "su.", + 1: "má.", + 2: "tý.", + 3: "mi.", + 4: "hó.", + 5: "fr.", + 6: "le.", + }, + "wide": { + 0: "sunnudagur", + 1: "mánadagur", + 2: "týsdagur", + 3: "mikudagur", + 4: "hósdagur", + 5: "fríggjadagur", + 6: "leygardagur", + }, + }, + "months": { + "abbreviated": { + 1: "jan.", + 2: "feb.", + 3: "mar.", + 4: "apr.", + 5: "mai", + 6: "jun.", + 7: "jul.", + 8: "aug.", + 9: "sep.", + 10: "okt.", + 11: "nov.", + 12: "des.", + }, + "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: "januar", + 2: "februar", + 3: "mars", + 4: "apríl", + 5: "mai", + 6: "juni", + 7: "juli", + 8: "august", + 9: "september", + 10: "oktober", + 11: "november", + 12: "desember", + }, + }, + "units": { + "year": {"one": "{0} ár", "other": "{0} ár"}, + "month": {"one": "{0} mánaður", "other": "{0} mánaðir"}, + "week": {"one": "{0} vika", "other": "{0} vikur"}, + "day": {"one": "{0} dagur", "other": "{0} dagar"}, + "hour": {"one": "{0} tími", "other": "{0} tímar"}, + "minute": {"one": "{0} minuttur", "other": "{0} minuttir"}, + "second": {"one": "{0} sekund", "other": "{0} sekundir"}, + "microsecond": {"one": "{0} mikrosekund", "other": "{0} mikrosekundir"}, + }, + "relative": { + "year": { + "future": {"other": "um {0} ár", "one": "um {0} ár"}, + "past": {"other": "{0} ár síðan", "one": "{0} ár síðan"}, + }, + "month": { + "future": {"other": "um {0} mánaðir", "one": "um {0} mánað"}, + "past": {"other": "{0} mánaðir síðan", "one": "{0} mánað síðan"}, + }, + "week": { + "future": {"other": "um {0} vikur", "one": "um {0} viku"}, + "past": {"other": "{0} vikur síðan", "one": "{0} vika síðan"}, + }, + "day": { + "future": {"other": "um {0} dagar", "one": "um {0} dag"}, + "past": {"other": "{0} dagar síðan", "one": "{0} dagur síðan"}, + }, + "hour": { + "future": {"other": "um {0} tímar", "one": "um {0} tíma"}, + "past": {"other": "{0} tímar síðan", "one": "{0} tími síðan"}, + }, + "minute": { + "future": {"other": "um {0} minuttir", "one": "um {0} minutt"}, + "past": {"other": "{0} minuttir síðan", "one": "{0} minutt síðan"}, + }, + "second": { + "future": {"other": "um {0} sekund", "one": "um {0} sekund"}, + "past": {"other": "{0} sekund síðan", "one": "{0} sekund síðan"}, + }, + }, + "day_periods": {"am": "AM", "pm": "PM"}, + }, + "custom": custom_translations, +} diff --git a/pendulum/locales/fr/__init__.py b/pendulum/locales/fr/__init__.py index 4c48b5a..e69de29 100644 --- a/pendulum/locales/fr/__init__.py +++ b/pendulum/locales/fr/__init__.py @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/pendulum/locales/fr/custom.py b/pendulum/locales/fr/custom.py index 0edddbf..134f297 100644 --- a/pendulum/locales/fr/custom.py +++ b/pendulum/locales/fr/custom.py @@ -1,27 +1,23 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - - -""" -fr custom locale file. -""" - -translations = { - "units": {"few_second": "quelques secondes"}, - # Relative Time - "ago": "il y a {0}", - "from_now": "dans {0}", - "after": "{0} après", - "before": "{0} avant", - # Ordinals - "ordinal": {"one": "er", "other": "e"}, - # Date formats - "date_formats": { - "LTS": "HH:mm:ss", - "LT": "HH:mm", - "LLLL": "dddd D MMMM YYYY HH:mm", - "LLL": "D MMMM YYYY HH:mm", - "LL": "D MMMM YYYY", - "L": "DD/MM/YYYY", - }, -} +""" +fr custom locale file. +""" + +translations = { + "units": {"few_second": "quelques secondes"}, + # Relative Time + "ago": "il y a {0}", + "from_now": "dans {0}", + "after": "{0} après", + "before": "{0} avant", + # Ordinals + "ordinal": {"one": "er", "other": "e"}, + # Date formats + "date_formats": { + "LTS": "HH:mm:ss", + "LT": "HH:mm", + "LLLL": "dddd D MMMM YYYY HH:mm", + "LLL": "D MMMM YYYY HH:mm", + "LL": "D MMMM YYYY", + "L": "DD/MM/YYYY", + }, +} diff --git a/pendulum/locales/fr/locale.py b/pendulum/locales/fr/locale.py index 137c012..8855d53 100644 --- a/pendulum/locales/fr/locale.py +++ b/pendulum/locales/fr/locale.py @@ -1,136 +1,133 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from .custom import translations as custom_translations - - -""" -fr locale file. - -It has been generated automatically and must not be modified directly. -""" - - -locale = { - "plural": lambda n: "one" if (n == n and ((n == 0) or (n == 1))) else "other", - "ordinal": lambda n: "one" if (n == n and ((n == 1))) else "other", - "translations": { - "days": { - "abbreviated": { - 0: "dim.", - 1: "lun.", - 2: "mar.", - 3: "mer.", - 4: "jeu.", - 5: "ven.", - 6: "sam.", - }, - "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"}, - "wide": { - 0: "dimanche", - 1: "lundi", - 2: "mardi", - 3: "mercredi", - 4: "jeudi", - 5: "vendredi", - 6: "samedi", - }, - }, - "months": { - "abbreviated": { - 1: "janv.", - 2: "févr.", - 3: "mars", - 4: "avr.", - 5: "mai", - 6: "juin", - 7: "juil.", - 8: "août", - 9: "sept.", - 10: "oct.", - 11: "nov.", - 12: "déc.", - }, - "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: "janvier", - 2: "février", - 3: "mars", - 4: "avril", - 5: "mai", - 6: "juin", - 7: "juillet", - 8: "août", - 9: "septembre", - 10: "octobre", - 11: "novembre", - 12: "décembre", - }, - }, - "units": { - "year": {"one": "{0} an", "other": "{0} ans"}, - "month": {"one": "{0} mois", "other": "{0} mois"}, - "week": {"one": "{0} semaine", "other": "{0} semaines"}, - "day": {"one": "{0} jour", "other": "{0} jours"}, - "hour": {"one": "{0} heure", "other": "{0} heures"}, - "minute": {"one": "{0} minute", "other": "{0} minutes"}, - "second": {"one": "{0} seconde", "other": "{0} secondes"}, - "microsecond": {"one": "{0} microseconde", "other": "{0} microsecondes"}, - }, - "relative": { - "year": { - "future": {"other": "dans {0} ans", "one": "dans {0} an"}, - "past": {"other": "il y a {0} ans", "one": "il y a {0} an"}, - }, - "month": { - "future": {"other": "dans {0} mois", "one": "dans {0} mois"}, - "past": {"other": "il y a {0} mois", "one": "il y a {0} mois"}, - }, - "week": { - "future": {"other": "dans {0} semaines", "one": "dans {0} semaine"}, - "past": {"other": "il y a {0} semaines", "one": "il y a {0} semaine"}, - }, - "day": { - "future": {"other": "dans {0} jours", "one": "dans {0} jour"}, - "past": {"other": "il y a {0} jours", "one": "il y a {0} jour"}, - }, - "hour": { - "future": {"other": "dans {0} heures", "one": "dans {0} heure"}, - "past": {"other": "il y a {0} heures", "one": "il y a {0} heure"}, - }, - "minute": { - "future": {"other": "dans {0} minutes", "one": "dans {0} minute"}, - "past": {"other": "il y a {0} minutes", "one": "il y a {0} minute"}, - }, - "second": { - "future": {"other": "dans {0} secondes", "one": "dans {0} seconde"}, - "past": {"other": "il y a {0} secondes", "one": "il y a {0} seconde"}, - }, - }, - "day_periods": { - "midnight": "minuit", - "am": "AM", - "noon": "midi", - "pm": "PM", - "morning1": "du matin", - "afternoon1": "de l’après-midi", - "evening1": "du soir", - "night1": "de nuit", - }, - }, - "custom": custom_translations, -} +from .custom import translations as custom_translations + + +""" +fr locale file. + +It has been generated automatically and must not be modified directly. +""" + + +locale = { + "plural": lambda n: "one" if (n == n and ((n == 0) or (n == 1))) else "other", + "ordinal": lambda n: "one" if (n == n and (n == 1)) else "other", + "translations": { + "days": { + "abbreviated": { + 0: "dim.", + 1: "lun.", + 2: "mar.", + 3: "mer.", + 4: "jeu.", + 5: "ven.", + 6: "sam.", + }, + "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"}, + "wide": { + 0: "dimanche", + 1: "lundi", + 2: "mardi", + 3: "mercredi", + 4: "jeudi", + 5: "vendredi", + 6: "samedi", + }, + }, + "months": { + "abbreviated": { + 1: "janv.", + 2: "févr.", + 3: "mars", + 4: "avr.", + 5: "mai", + 6: "juin", + 7: "juil.", + 8: "août", + 9: "sept.", + 10: "oct.", + 11: "nov.", + 12: "déc.", + }, + "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: "janvier", + 2: "février", + 3: "mars", + 4: "avril", + 5: "mai", + 6: "juin", + 7: "juillet", + 8: "août", + 9: "septembre", + 10: "octobre", + 11: "novembre", + 12: "décembre", + }, + }, + "units": { + "year": {"one": "{0} an", "other": "{0} ans"}, + "month": {"one": "{0} mois", "other": "{0} mois"}, + "week": {"one": "{0} semaine", "other": "{0} semaines"}, + "day": {"one": "{0} jour", "other": "{0} jours"}, + "hour": {"one": "{0} heure", "other": "{0} heures"}, + "minute": {"one": "{0} minute", "other": "{0} minutes"}, + "second": {"one": "{0} seconde", "other": "{0} secondes"}, + "microsecond": {"one": "{0} microseconde", "other": "{0} microsecondes"}, + }, + "relative": { + "year": { + "future": {"other": "dans {0} ans", "one": "dans {0} an"}, + "past": {"other": "il y a {0} ans", "one": "il y a {0} an"}, + }, + "month": { + "future": {"other": "dans {0} mois", "one": "dans {0} mois"}, + "past": {"other": "il y a {0} mois", "one": "il y a {0} mois"}, + }, + "week": { + "future": {"other": "dans {0} semaines", "one": "dans {0} semaine"}, + "past": {"other": "il y a {0} semaines", "one": "il y a {0} semaine"}, + }, + "day": { + "future": {"other": "dans {0} jours", "one": "dans {0} jour"}, + "past": {"other": "il y a {0} jours", "one": "il y a {0} jour"}, + }, + "hour": { + "future": {"other": "dans {0} heures", "one": "dans {0} heure"}, + "past": {"other": "il y a {0} heures", "one": "il y a {0} heure"}, + }, + "minute": { + "future": {"other": "dans {0} minutes", "one": "dans {0} minute"}, + "past": {"other": "il y a {0} minutes", "one": "il y a {0} minute"}, + }, + "second": { + "future": {"other": "dans {0} secondes", "one": "dans {0} seconde"}, + "past": {"other": "il y a {0} secondes", "one": "il y a {0} seconde"}, + }, + }, + "day_periods": { + "midnight": "minuit", + "am": "AM", + "noon": "midi", + "pm": "PM", + "morning1": "du matin", + "afternoon1": "de l’après-midi", + "evening1": "du soir", + "night1": "de nuit", + }, + }, + "custom": custom_translations, +} diff --git a/pendulum/locales/he/__init__.py b/pendulum/locales/he/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pendulum/locales/he/custom.py b/pendulum/locales/he/custom.py new file mode 100644 index 0000000..51f8476 --- /dev/null +++ b/pendulum/locales/he/custom.py @@ -0,0 +1,23 @@ +""" +he custom locale file. +""" + +translations = { + "units": {"few_second": "כמה שניות"}, + # Relative time + "ago": "לפני {0}", + "from_now": "תוך {0}", + "after": "בעוד {0}", + "before": "{0} קודם", + # Ordinals + "ordinal": {"other": "º"}, + # Date formats + "date_formats": { + "LTS": "H:mm:ss", + "LT": "H:mm", + "LLLL": "dddd, D [ב] MMMM [ב] YYYY H:mm", + "LLL": "D [ב] MMMM [ב] YYYY H:mm", + "LL": "D [ב] MMMM [ב] YYYY", + "L": "DD/MM/YYYY", + }, +} diff --git a/pendulum/locales/he/locale.py b/pendulum/locales/he/locale.py new file mode 100644 index 0000000..457c101 --- /dev/null +++ b/pendulum/locales/he/locale.py @@ -0,0 +1,269 @@ +from .custom import translations as custom_translations + + +""" +he locale file. + +It has been generated automatically and must not be modified directly. +""" + + +locale = { + "plural": lambda n: "many" + if ( + ((0 == 0 and (0 == 0)) and (not (n == n and (n >= 0 and n <= 10)))) + and ((n % 10) == (n % 10) and ((n % 10) == 0)) + ) + else "one" + if ((n == n and (n == 1)) and (0 == 0 and (0 == 0))) + else "two" + if ((n == n and (n == 2)) and (0 == 0 and (0 == 0))) + else "other", + "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: "ש׳", + }, + "wide": { + 0: "יום ראשון", + 1: "יום שני", + 2: "יום שלישי", + 3: "יום רביעי", + 4: "יום חמישי", + 5: "יום שישי", + 6: "יום שבת", + }, + }, + "months": { + "abbreviated": { + 1: "ינו׳", + 2: "פבר׳", + 3: "מרץ", + 4: "אפר׳", + 5: "מאי", + 6: "יוני", + 7: "יולי", + 8: "אוג׳", + 9: "ספט׳", + 10: "אוק׳", + 11: "נוב׳", + 12: "דצמ׳", + }, + "narrow": { + 1: "1", + 2: "2", + 3: "3", + 4: "4", + 5: "5", + 6: "6", + 7: "7", + 8: "8", + 9: "9", + 10: "10", + 11: "11", + 12: "12", + }, + "wide": { + 1: "ינואר", + 2: "פברואר", + 3: "מרץ", + 4: "אפריל", + 5: "מאי", + 6: "יוני", + 7: "יולי", + 8: "אוגוסט", + 9: "ספטמבר", + 10: "אוקטובר", + 11: "נובמבר", + 12: "דצמבר", + }, + }, + "units": { + "year": { + "one": "שנה", + "two": "שנתיים", + "many": "{0} שנים", + "other": "{0} שנים", + }, + "month": { + "one": "חודש", + "two": "חודשיים", + "many": "{0} חודשים", + "other": "{0} חודשים", + }, + "week": { + "one": "שבוע", + "two": "שבועיים", + "many": "{0} שבועות", + "other": "{0} שבועות", + }, + "day": { + "one": "יום {0}", + "two": "יומיים", + "many": "{0} יום", + "other": "{0} ימים", + }, + "hour": { + "one": "שעה", + "two": "שעתיים", + "many": "{0} שעות", + "other": "{0} שעות", + }, + "minute": { + "one": "דקה", + "two": "שתי דקות", + "many": "{0} דקות", + "other": "{0} דקות", + }, + "second": { + "one": "שניה", + "two": "שתי שניות", + "many": "\u200f{0} שניות", + "other": "{0} שניות", + }, + "microsecond": { + "one": "{0} מיליונית שנייה", + "two": "{0} מיליוניות שנייה", + "many": "{0} מיליוניות שנייה", + "other": "{0} מיליוניות שנייה", + }, + }, + "relative": { + "year": { + "future": { + "other": "בעוד {0} שנים", + "one": "בעוד שנה", + "two": "בעוד שנתיים", + "many": "בעוד {0} שנה", + }, + "past": { + "other": "לפני {0} שנים", + "one": "לפני שנה", + "two": "לפני שנתיים", + "many": "לפני {0} שנה", + }, + }, + "month": { + "future": { + "other": "בעוד {0} חודשים", + "one": "בעוד חודש", + "two": "בעוד חודשיים", + "many": "בעוד {0} חודשים", + }, + "past": { + "other": "לפני {0} חודשים", + "one": "לפני חודש", + "two": "לפני חודשיים", + "many": "לפני {0} חודשים", + }, + }, + "week": { + "future": { + "other": "בעוד {0} שבועות", + "one": "בעוד שבוע", + "two": "בעוד שבועיים", + "many": "בעוד {0} שבועות", + }, + "past": { + "other": "לפני {0} שבועות", + "one": "לפני שבוע", + "two": "לפני שבועיים", + "many": "לפני {0} שבועות", + }, + }, + "day": { + "future": { + "other": "בעוד {0} ימים", + "one": "בעוד יום {0}", + "two": "בעוד יומיים", + "many": "בעוד {0} ימים", + }, + "past": { + "other": "לפני {0} ימים", + "one": "לפני יום {0}", + "two": "לפני יומיים", + "many": "לפני {0} ימים", + }, + }, + "hour": { + "future": { + "other": "בעוד {0} שעות", + "one": "בעוד שעה", + "two": "בעוד שעתיים", + "many": "בעוד {0} שעות", + }, + "past": { + "other": "לפני {0} שעות", + "one": "לפני שעה", + "two": "לפני שעתיים", + "many": "לפני {0} שעות", + }, + }, + "minute": { + "future": { + "other": "בעוד {0} דקות", + "one": "בעוד דקה", + "two": "בעוד שתי דקות", + "many": "בעוד {0} דקות", + }, + "past": { + "other": "לפני {0} דקות", + "one": "לפני דקה", + "two": "לפני שתי דקות", + "many": "לפני {0} דקות", + }, + }, + "second": { + "future": { + "other": "בעוד {0} שניות", + "one": "בעוד שנייה", + "two": "בעוד שתי שניות", + "many": "בעוד {0} שניות", + }, + "past": { + "other": "לפני {0} שניות", + "one": "לפני שנייה", + "two": "לפני שתי שניות", + "many": "לפני {0} שניות", + }, + }, + }, + "day_periods": { + "midnight": "חצות", + "am": "לפנה״צ", + "pm": "אחה״צ", + "morning1": "בבוקר", + "afternoon1": "בצהריים", + "afternoon2": "אחר הצהריים", + "evening1": "בערב", + "night1": "בלילה", + "night2": "לפנות בוקר", + }, + }, + "custom": custom_translations, +} diff --git a/pendulum/locales/id/custom.py b/pendulum/locales/id/custom.py index 2202481..3ba2035 100644 --- a/pendulum/locales/id/custom.py +++ b/pendulum/locales/id/custom.py @@ -1,23 +1,19 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - - -""" -id custom locale file. -""" - -translations = { - "units": {"few_second": "beberapa detik"}, - "ago": "{} yang lalu", - "from_now": "dalam {}", - "after": "{0} kemudian", - "before": "{0} yang lalu", - "date_formats": { - "LTS": "HH:mm:ss", - "LT": "HH:mm", - "LLLL": "dddd [d.] D. MMMM YYYY HH:mm", - "LLL": "D. MMMM YYYY HH:mm", - "LL": "D. MMMM YYYY", - "L": "DD/MM/YYYY", - }, -} +""" +id custom locale file. +""" + +translations = { + "units": {"few_second": "beberapa detik"}, + "ago": "{} yang lalu", + "from_now": "dalam {}", + "after": "{0} kemudian", + "before": "{0} yang lalu", + "date_formats": { + "LTS": "HH:mm:ss", + "LT": "HH:mm", + "LLLL": "dddd [d.] D. MMMM YYYY HH:mm", + "LLL": "D. MMMM YYYY HH:mm", + "LL": "D. MMMM YYYY", + "L": "DD/MM/YYYY", + }, +} diff --git a/pendulum/locales/id/locale.py b/pendulum/locales/id/locale.py index 5a3485e..bc994ce 100644 --- a/pendulum/locales/id/locale.py +++ b/pendulum/locales/id/locale.py @@ -1,144 +1,141 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from .custom import translations as custom_translations - - -""" -id locale file. - -It has been generated automatically and must not be modified directly. -""" - - -locale = { - "plural": lambda n: "other", - "ordinal": lambda n: "other", - "translations": { - "days": { - "abbreviated": { - 0: "Min", - 1: "Sen", - 2: "Sel", - 3: "Rab", - 4: "Kam", - 5: "Jum", - 6: "Sab", - }, - "narrow": {0: "M", 1: "S", 2: "S", 3: "R", 4: "K", 5: "J", 6: "S"}, - "short": { - 0: "Min", - 1: "Sen", - 2: "Sel", - 3: "Rab", - 4: "Kam", - 5: "Jum", - 6: "Sab", - }, - "wide": { - 0: "Minggu", - 1: "Senin", - 2: "Selasa", - 3: "Rabu", - 4: "Kamis", - 5: "Jumat", - 6: "Sabtu", - }, - }, - "months": { - "abbreviated": { - 1: "Jan", - 2: "Feb", - 3: "Mar", - 4: "Apr", - 5: "Mei", - 6: "Jun", - 7: "Jul", - 8: "Agt", - 9: "Sep", - 10: "Okt", - 11: "Nov", - 12: "Des", - }, - "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: "Januari", - 2: "Februari", - 3: "Maret", - 4: "April", - 5: "Mei", - 6: "Juni", - 7: "Juli", - 8: "Agustus", - 9: "September", - 10: "Oktober", - 11: "November", - 12: "Desember", - }, - }, - "units": { - "year": {"other": "{0} tahun"}, - "month": {"other": "{0} bulan"}, - "week": {"other": "{0} minggu"}, - "day": {"other": "{0} hari"}, - "hour": {"other": "{0} jam"}, - "minute": {"other": "{0} menit"}, - "second": {"other": "{0} detik"}, - "microsecond": {"other": "{0} mikrodetik"}, - }, - "relative": { - "year": { - "future": {"other": "dalam {0} tahun"}, - "past": {"other": "{0} tahun yang lalu"}, - }, - "month": { - "future": {"other": "dalam {0} bulan"}, - "past": {"other": "{0} bulan yang lalu"}, - }, - "week": { - "future": {"other": "dalam {0} minggu"}, - "past": {"other": "{0} minggu yang lalu"}, - }, - "day": { - "future": {"other": "dalam {0} hari"}, - "past": {"other": "{0} hari yang lalu"}, - }, - "hour": { - "future": {"other": "dalam {0} jam"}, - "past": {"other": "{0} jam yang lalu"}, - }, - "minute": { - "future": {"other": "dalam {0} menit"}, - "past": {"other": "{0} menit yang lalu"}, - }, - "second": { - "future": {"other": "dalam {0} detik"}, - "past": {"other": "{0} detik yang lalu"}, - }, - }, - "day_periods": { - "midnight": "tengah malam", - "am": "AM", - "noon": "tengah hari", - "pm": "PM", - "morning1": "pagi", - "afternoon1": "siang", - "evening1": "sore", - "night1": "malam", - }, - }, - "custom": custom_translations, -} +from .custom import translations as custom_translations + + +""" +id locale file. + +It has been generated automatically and must not be modified directly. +""" + + +locale = { + "plural": lambda n: "other", + "ordinal": lambda n: "other", + "translations": { + "days": { + "abbreviated": { + 0: "Min", + 1: "Sen", + 2: "Sel", + 3: "Rab", + 4: "Kam", + 5: "Jum", + 6: "Sab", + }, + "narrow": {0: "M", 1: "S", 2: "S", 3: "R", 4: "K", 5: "J", 6: "S"}, + "short": { + 0: "Min", + 1: "Sen", + 2: "Sel", + 3: "Rab", + 4: "Kam", + 5: "Jum", + 6: "Sab", + }, + "wide": { + 0: "Minggu", + 1: "Senin", + 2: "Selasa", + 3: "Rabu", + 4: "Kamis", + 5: "Jumat", + 6: "Sabtu", + }, + }, + "months": { + "abbreviated": { + 1: "Jan", + 2: "Feb", + 3: "Mar", + 4: "Apr", + 5: "Mei", + 6: "Jun", + 7: "Jul", + 8: "Agt", + 9: "Sep", + 10: "Okt", + 11: "Nov", + 12: "Des", + }, + "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: "Januari", + 2: "Februari", + 3: "Maret", + 4: "April", + 5: "Mei", + 6: "Juni", + 7: "Juli", + 8: "Agustus", + 9: "September", + 10: "Oktober", + 11: "November", + 12: "Desember", + }, + }, + "units": { + "year": {"other": "{0} tahun"}, + "month": {"other": "{0} bulan"}, + "week": {"other": "{0} minggu"}, + "day": {"other": "{0} hari"}, + "hour": {"other": "{0} jam"}, + "minute": {"other": "{0} menit"}, + "second": {"other": "{0} detik"}, + "microsecond": {"other": "{0} mikrodetik"}, + }, + "relative": { + "year": { + "future": {"other": "dalam {0} tahun"}, + "past": {"other": "{0} tahun yang lalu"}, + }, + "month": { + "future": {"other": "dalam {0} bulan"}, + "past": {"other": "{0} bulan yang lalu"}, + }, + "week": { + "future": {"other": "dalam {0} minggu"}, + "past": {"other": "{0} minggu yang lalu"}, + }, + "day": { + "future": {"other": "dalam {0} hari"}, + "past": {"other": "{0} hari yang lalu"}, + }, + "hour": { + "future": {"other": "dalam {0} jam"}, + "past": {"other": "{0} jam yang lalu"}, + }, + "minute": { + "future": {"other": "dalam {0} menit"}, + "past": {"other": "{0} menit yang lalu"}, + }, + "second": { + "future": {"other": "dalam {0} detik"}, + "past": {"other": "{0} detik yang lalu"}, + }, + }, + "day_periods": { + "midnight": "tengah malam", + "am": "AM", + "noon": "tengah hari", + "pm": "PM", + "morning1": "pagi", + "afternoon1": "siang", + "evening1": "sore", + "night1": "malam", + }, + }, + "custom": custom_translations, +} diff --git a/pendulum/locales/it/custom.py b/pendulum/locales/it/custom.py index 744f55c..e5cf1cc 100644 --- a/pendulum/locales/it/custom.py +++ b/pendulum/locales/it/custom.py @@ -1,27 +1,24 @@ -# -*- coding: utf-8 -*- -""" -it custom locale file. -""" - -from __future__ import unicode_literals - - -translations = { - "units": {"few_second": "alcuni secondi"}, - # Relative Time - "ago": "{0} fa", - "from_now": "in {0}", - "after": "{0} dopo", - "before": "{0} prima", - # Ordinals - "ordinal": {"other": "°"}, - # Date formats - "date_formats": { - "LTS": "H:mm:ss", - "LT": "H:mm", - "L": "DD/MM/YYYY", - "LL": "D MMMM YYYY", - "LLL": "D MMMM YYYY [alle] H:mm", - "LLLL": "dddd, D MMMM YYYY [alle] H:mm", - }, -} +""" +it custom locale file. +""" + + +translations = { + "units": {"few_second": "alcuni secondi"}, + # Relative Time + "ago": "{0} fa", + "from_now": "in {0}", + "after": "{0} dopo", + "before": "{0} prima", + # Ordinals + "ordinal": {"other": "°"}, + # Date formats + "date_formats": { + "LTS": "H:mm:ss", + "LT": "H:mm", + "L": "DD/MM/YYYY", + "LL": "D MMMM YYYY", + "LLL": "D MMMM YYYY [alle] H:mm", + "LLLL": "dddd, D MMMM YYYY [alle] H:mm", + }, +} diff --git a/pendulum/locales/it/locale.py b/pendulum/locales/it/locale.py index 4abf717..bb3fdcf 100644 --- a/pendulum/locales/it/locale.py +++ b/pendulum/locales/it/locale.py @@ -1,148 +1,145 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from .custom import translations as custom_translations - - -""" -it 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: "many" - if (n == n and ((n == 11) or (n == 8) or (n == 80) or (n == 800))) - else "other", - "translations": { - "days": { - "abbreviated": { - 0: "dom", - 1: "lun", - 2: "mar", - 3: "mer", - 4: "gio", - 5: "ven", - 6: "sab", - }, - "narrow": {0: "D", 1: "L", 2: "M", 3: "M", 4: "G", 5: "V", 6: "S"}, - "short": { - 0: "dom", - 1: "lun", - 2: "mar", - 3: "mer", - 4: "gio", - 5: "ven", - 6: "sab", - }, - "wide": { - 0: "domenica", - 1: "lunedì", - 2: "martedì", - 3: "mercoledì", - 4: "giovedì", - 5: "venerdì", - 6: "sabato", - }, - }, - "months": { - "abbreviated": { - 1: "gen", - 2: "feb", - 3: "mar", - 4: "apr", - 5: "mag", - 6: "giu", - 7: "lug", - 8: "ago", - 9: "set", - 10: "ott", - 11: "nov", - 12: "dic", - }, - "narrow": { - 1: "G", - 2: "F", - 3: "M", - 4: "A", - 5: "M", - 6: "G", - 7: "L", - 8: "A", - 9: "S", - 10: "O", - 11: "N", - 12: "D", - }, - "wide": { - 1: "gennaio", - 2: "febbraio", - 3: "marzo", - 4: "aprile", - 5: "maggio", - 6: "giugno", - 7: "luglio", - 8: "agosto", - 9: "settembre", - 10: "ottobre", - 11: "novembre", - 12: "dicembre", - }, - }, - "units": { - "year": {"one": "{0} anno", "other": "{0} anni"}, - "month": {"one": "{0} mese", "other": "{0} mesi"}, - "week": {"one": "{0} settimana", "other": "{0} settimane"}, - "day": {"one": "{0} giorno", "other": "{0} giorni"}, - "hour": {"one": "{0} ora", "other": "{0} ore"}, - "minute": {"one": "{0} minuto", "other": "{0} minuti"}, - "second": {"one": "{0} secondo", "other": "{0} secondi"}, - "microsecond": {"one": "{0} microsecondo", "other": "{0} microsecondi"}, - }, - "relative": { - "year": { - "future": {"other": "tra {0} anni", "one": "tra {0} anno"}, - "past": {"other": "{0} anni fa", "one": "{0} anno fa"}, - }, - "month": { - "future": {"other": "tra {0} mesi", "one": "tra {0} mese"}, - "past": {"other": "{0} mesi fa", "one": "{0} mese fa"}, - }, - "week": { - "future": {"other": "tra {0} settimane", "one": "tra {0} settimana"}, - "past": {"other": "{0} settimane fa", "one": "{0} settimana fa"}, - }, - "day": { - "future": {"other": "tra {0} giorni", "one": "tra {0} giorno"}, - "past": {"other": "{0} giorni fa", "one": "{0} giorno fa"}, - }, - "hour": { - "future": {"other": "tra {0} ore", "one": "tra {0} ora"}, - "past": {"other": "{0} ore fa", "one": "{0} ora fa"}, - }, - "minute": { - "future": {"other": "tra {0} minuti", "one": "tra {0} minuto"}, - "past": {"other": "{0} minuti fa", "one": "{0} minuto fa"}, - }, - "second": { - "future": {"other": "tra {0} secondi", "one": "tra {0} secondo"}, - "past": {"other": "{0} secondi fa", "one": "{0} secondo fa"}, - }, - }, - "day_periods": { - "midnight": "mezzanotte", - "am": "AM", - "noon": "mezzogiorno", - "pm": "PM", - "morning1": "di mattina", - "afternoon1": "del pomeriggio", - "evening1": "di sera", - "night1": "di notte", - }, - }, - "custom": custom_translations, -} +from .custom import translations as custom_translations + + +""" +it 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: "many" + if (n == n and ((n == 11) or (n == 8) or (n == 80) or (n == 800))) + else "other", + "translations": { + "days": { + "abbreviated": { + 0: "dom", + 1: "lun", + 2: "mar", + 3: "mer", + 4: "gio", + 5: "ven", + 6: "sab", + }, + "narrow": {0: "D", 1: "L", 2: "M", 3: "M", 4: "G", 5: "V", 6: "S"}, + "short": { + 0: "dom", + 1: "lun", + 2: "mar", + 3: "mer", + 4: "gio", + 5: "ven", + 6: "sab", + }, + "wide": { + 0: "domenica", + 1: "lunedì", + 2: "martedì", + 3: "mercoledì", + 4: "giovedì", + 5: "venerdì", + 6: "sabato", + }, + }, + "months": { + "abbreviated": { + 1: "gen", + 2: "feb", + 3: "mar", + 4: "apr", + 5: "mag", + 6: "giu", + 7: "lug", + 8: "ago", + 9: "set", + 10: "ott", + 11: "nov", + 12: "dic", + }, + "narrow": { + 1: "G", + 2: "F", + 3: "M", + 4: "A", + 5: "M", + 6: "G", + 7: "L", + 8: "A", + 9: "S", + 10: "O", + 11: "N", + 12: "D", + }, + "wide": { + 1: "gennaio", + 2: "febbraio", + 3: "marzo", + 4: "aprile", + 5: "maggio", + 6: "giugno", + 7: "luglio", + 8: "agosto", + 9: "settembre", + 10: "ottobre", + 11: "novembre", + 12: "dicembre", + }, + }, + "units": { + "year": {"one": "{0} anno", "other": "{0} anni"}, + "month": {"one": "{0} mese", "other": "{0} mesi"}, + "week": {"one": "{0} settimana", "other": "{0} settimane"}, + "day": {"one": "{0} giorno", "other": "{0} giorni"}, + "hour": {"one": "{0} ora", "other": "{0} ore"}, + "minute": {"one": "{0} minuto", "other": "{0} minuti"}, + "second": {"one": "{0} secondo", "other": "{0} secondi"}, + "microsecond": {"one": "{0} microsecondo", "other": "{0} microsecondi"}, + }, + "relative": { + "year": { + "future": {"other": "tra {0} anni", "one": "tra {0} anno"}, + "past": {"other": "{0} anni fa", "one": "{0} anno fa"}, + }, + "month": { + "future": {"other": "tra {0} mesi", "one": "tra {0} mese"}, + "past": {"other": "{0} mesi fa", "one": "{0} mese fa"}, + }, + "week": { + "future": {"other": "tra {0} settimane", "one": "tra {0} settimana"}, + "past": {"other": "{0} settimane fa", "one": "{0} settimana fa"}, + }, + "day": { + "future": {"other": "tra {0} giorni", "one": "tra {0} giorno"}, + "past": {"other": "{0} giorni fa", "one": "{0} giorno fa"}, + }, + "hour": { + "future": {"other": "tra {0} ore", "one": "tra {0} ora"}, + "past": {"other": "{0} ore fa", "one": "{0} ora fa"}, + }, + "minute": { + "future": {"other": "tra {0} minuti", "one": "tra {0} minuto"}, + "past": {"other": "{0} minuti fa", "one": "{0} minuto fa"}, + }, + "second": { + "future": {"other": "tra {0} secondi", "one": "tra {0} secondo"}, + "past": {"other": "{0} secondi fa", "one": "{0} secondo fa"}, + }, + }, + "day_periods": { + "midnight": "mezzanotte", + "am": "AM", + "noon": "mezzogiorno", + "pm": "PM", + "morning1": "di mattina", + "afternoon1": "del pomeriggio", + "evening1": "di sera", + "night1": "di notte", + }, + }, + "custom": custom_translations, +} diff --git a/pendulum/locales/ja/__init__.py b/pendulum/locales/ja/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pendulum/locales/ja/custom.py b/pendulum/locales/ja/custom.py new file mode 100644 index 0000000..c076250 --- /dev/null +++ b/pendulum/locales/ja/custom.py @@ -0,0 +1,21 @@ +""" +ja custom locale file. +""" + +translations = { + "units": {"few_second": "数秒"}, + # Relative time + "ago": "{} 前に", + "from_now": "今から {}", + "after": "{0} 後", + "before": "{0} 前", + # Date formats + "date_formats": { + "LTS": "h:mm:ss A", + "LT": "h:mm A", + "L": "MM/DD/YYYY", + "LL": "MMMM D, YYYY", + "LLL": "MMMM D, YYYY h:mm A", + "LLLL": "dddd, MMMM D, YYYY h:mm A", + }, +} diff --git a/pendulum/locales/ja/locale.py b/pendulum/locales/ja/locale.py new file mode 100644 index 0000000..574d2ec --- /dev/null +++ b/pendulum/locales/ja/locale.py @@ -0,0 +1,194 @@ +from .custom import translations as custom_translations + + +""" +ja locale file. + +It has been generated automatically and must not be modified directly. +""" + + +locale = { + "plural": lambda n: "other", + "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: "土", + }, + "wide": { + 0: "日曜日", + 1: "月曜日", + 2: "火曜日", + 3: "水曜日", + 4: "木曜日", + 5: "金曜日", + 6: "土曜日", + }, + }, + "months": { + "abbreviated": { + 1: "1月", + 2: "2月", + 3: "3月", + 4: "4月", + 5: "5月", + 6: "6月", + 7: "7月", + 8: "8月", + 9: "9月", + 10: "10月", + 11: "11月", + 12: "12月", + }, + "narrow": { + 1: "1", + 2: "2", + 3: "3", + 4: "4", + 5: "5", + 6: "6", + 7: "7", + 8: "8", + 9: "9", + 10: "10", + 11: "11", + 12: "12", + }, + "wide": { + 1: "1月", + 2: "2月", + 3: "3月", + 4: "4月", + 5: "5月", + 6: "6月", + 7: "7月", + 8: "8月", + 9: "9月", + 10: "10月", + 11: "11月", + 12: "12月", + }, + }, + "units": { + "year": { + "other": "{0} 年", + }, + "month": { + "other": "{0} か月", + }, + "week": { + "other": "{0} 週間", + }, + "day": { + "other": "{0} 日", + }, + "hour": { + "other": "{0} 時間", + }, + "minute": { + "other": "{0} 分", + }, + "second": { + "other": "{0} 秒", + }, + "microsecond": { + "other": "{0} マイクロ秒", + }, + }, + "relative": { + "year": { + "future": { + "other": "{0} 年後", + }, + "past": { + "other": "{0} 年前", + }, + }, + "month": { + "future": { + "other": "{0} か月後", + }, + "past": { + "other": "{0} か月前", + }, + }, + "week": { + "future": { + "other": "{0} 週間後", + }, + "past": { + "other": "{0} 週間前", + }, + }, + "day": { + "future": { + "other": "{0} 日後", + }, + "past": { + "other": "{0} 日前", + }, + }, + "hour": { + "future": { + "other": "{0} 時間後", + }, + "past": { + "other": "{0} 時間前", + }, + }, + "minute": { + "future": { + "other": "{0} 分後", + }, + "past": { + "other": "{0} 分前", + }, + }, + "second": { + "future": { + "other": "{0} 秒後", + }, + "past": { + "other": "{0} 秒前", + }, + }, + }, + "day_periods": { + "midnight": "真夜中", + "am": "午前", + "noon": "正午", + "pm": "午後", + "morning1": "朝", + "afternoon1": "昼", + "evening1": "夕方", + "night1": "夜", + "night2": "夜中", + }, + }, + "custom": custom_translations, +} diff --git a/pendulum/locales/ko/custom.py b/pendulum/locales/ko/custom.py index beac040..2c0e50c 100644 --- a/pendulum/locales/ko/custom.py +++ b/pendulum/locales/ko/custom.py @@ -1,22 +1,18 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - - -""" -ko custom locale file. -""" - -translations = { - # Relative time - "after": "{0} 뒤", - "before": "{0} 앞", - # Date formats - "date_formats": { - "LTS": "A h시 m분 s초", - "LT": "A h시 m분", - "LLLL": "YYYY년 MMMM D일 dddd A h시 m분", - "LLL": "YYYY년 MMMM D일 A h시 m분", - "LL": "YYYY년 MMMM D일", - "L": "YYYY.MM.DD", - }, -} +""" +ko custom locale file. +""" + +translations = { + # Relative time + "after": "{0} 뒤", + "before": "{0} 앞", + # Date formats + "date_formats": { + "LTS": "A h시 m분 s초", + "LT": "A h시 m분", + "LLLL": "YYYY년 MMMM D일 dddd A h시 m분", + "LLL": "YYYY년 MMMM D일 A h시 m분", + "LL": "YYYY년 MMMM D일", + "L": "YYYY.MM.DD", + }, +} diff --git a/pendulum/locales/ko/locale.py b/pendulum/locales/ko/locale.py index 3c81b0e..0f5a346 100644 --- a/pendulum/locales/ko/locale.py +++ b/pendulum/locales/ko/locale.py @@ -1,108 +1,105 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from .custom import translations as custom_translations - - -""" -ko locale file. - -It has been generated automatically and must not be modified directly. -""" - - -locale = { - "plural": lambda n: "other", - "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: "토"}, - "wide": { - 0: "일요일", - 1: "월요일", - 2: "화요일", - 3: "수요일", - 4: "목요일", - 5: "금요일", - 6: "토요일", - }, - }, - "months": { - "abbreviated": { - 1: "1월", - 2: "2월", - 3: "3월", - 4: "4월", - 5: "5월", - 6: "6월", - 7: "7월", - 8: "8월", - 9: "9월", - 10: "10월", - 11: "11월", - 12: "12월", - }, - "narrow": { - 1: "1월", - 2: "2월", - 3: "3월", - 4: "4월", - 5: "5월", - 6: "6월", - 7: "7월", - 8: "8월", - 9: "9월", - 10: "10월", - 11: "11월", - 12: "12월", - }, - "wide": { - 1: "1월", - 2: "2월", - 3: "3월", - 4: "4월", - 5: "5월", - 6: "6월", - 7: "7월", - 8: "8월", - 9: "9월", - 10: "10월", - 11: "11월", - 12: "12월", - }, - }, - "units": { - "year": {"other": "{0}년"}, - "month": {"other": "{0}개월"}, - "week": {"other": "{0}주"}, - "day": {"other": "{0}일"}, - "hour": {"other": "{0}시간"}, - "minute": {"other": "{0}분"}, - "second": {"other": "{0}초"}, - "microsecond": {"other": "{0}마이크로초"}, - }, - "relative": { - "year": {"future": {"other": "{0}년 후"}, "past": {"other": "{0}년 전"}}, - "month": {"future": {"other": "{0}개월 후"}, "past": {"other": "{0}개월 전"}}, - "week": {"future": {"other": "{0}주 후"}, "past": {"other": "{0}주 전"}}, - "day": {"future": {"other": "{0}일 후"}, "past": {"other": "{0}일 전"}}, - "hour": {"future": {"other": "{0}시간 후"}, "past": {"other": "{0}시간 전"}}, - "minute": {"future": {"other": "{0}분 후"}, "past": {"other": "{0}분 전"}}, - "second": {"future": {"other": "{0}초 후"}, "past": {"other": "{0}초 전"}}, - }, - "day_periods": { - "midnight": "자정", - "am": "오전", - "noon": "정오", - "pm": "오후", - "morning1": "새벽", - "morning2": "오전", - "afternoon1": "오후", - "evening1": "저녁", - "night1": "밤", - }, - }, - "custom": custom_translations, -} +from .custom import translations as custom_translations + + +""" +ko locale file. + +It has been generated automatically and must not be modified directly. +""" + + +locale = { + "plural": lambda n: "other", + "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: "토"}, + "wide": { + 0: "일요일", + 1: "월요일", + 2: "화요일", + 3: "수요일", + 4: "목요일", + 5: "금요일", + 6: "토요일", + }, + }, + "months": { + "abbreviated": { + 1: "1월", + 2: "2월", + 3: "3월", + 4: "4월", + 5: "5월", + 6: "6월", + 7: "7월", + 8: "8월", + 9: "9월", + 10: "10월", + 11: "11월", + 12: "12월", + }, + "narrow": { + 1: "1월", + 2: "2월", + 3: "3월", + 4: "4월", + 5: "5월", + 6: "6월", + 7: "7월", + 8: "8월", + 9: "9월", + 10: "10월", + 11: "11월", + 12: "12월", + }, + "wide": { + 1: "1월", + 2: "2월", + 3: "3월", + 4: "4월", + 5: "5월", + 6: "6월", + 7: "7월", + 8: "8월", + 9: "9월", + 10: "10월", + 11: "11월", + 12: "12월", + }, + }, + "units": { + "year": {"other": "{0}년"}, + "month": {"other": "{0}개월"}, + "week": {"other": "{0}주"}, + "day": {"other": "{0}일"}, + "hour": {"other": "{0}시간"}, + "minute": {"other": "{0}분"}, + "second": {"other": "{0}초"}, + "microsecond": {"other": "{0}마이크로초"}, + }, + "relative": { + "year": {"future": {"other": "{0}년 후"}, "past": {"other": "{0}년 전"}}, + "month": {"future": {"other": "{0}개월 후"}, "past": {"other": "{0}개월 전"}}, + "week": {"future": {"other": "{0}주 후"}, "past": {"other": "{0}주 전"}}, + "day": {"future": {"other": "{0}일 후"}, "past": {"other": "{0}일 전"}}, + "hour": {"future": {"other": "{0}시간 후"}, "past": {"other": "{0}시간 전"}}, + "minute": {"future": {"other": "{0}분 후"}, "past": {"other": "{0}분 전"}}, + "second": {"future": {"other": "{0}초 후"}, "past": {"other": "{0}초 전"}}, + }, + "day_periods": { + "midnight": "자정", + "am": "오전", + "noon": "정오", + "pm": "오후", + "morning1": "새벽", + "morning2": "오전", + "afternoon1": "오후", + "evening1": "저녁", + "night1": "밤", + }, + }, + "custom": custom_translations, +} diff --git a/pendulum/locales/locale.py b/pendulum/locales/locale.py index 154db42..637509a 100644 --- a/pendulum/locales/locale.py +++ b/pendulum/locales/locale.py @@ -1,104 +1,102 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -import os -import re - -from importlib import import_module -from typing import Any -from typing import Optional -from typing import Union - -from pendulum.utils._compat import basestring -from pendulum.utils._compat import decode - - -class Locale: - """ - Represent a specific locale. - """ - - _cache = {} - - def __init__(self, locale, data): # type: (str, Any) -> None - self._locale = locale - self._data = data - self._key_cache = {} - - @classmethod - def load(cls, locale): # type: (Union[str, Locale]) -> Locale - if isinstance(locale, Locale): - return locale - - locale = cls.normalize_locale(locale) - if locale in cls._cache: - return cls._cache[locale] - - # Checking locale existence - actual_locale = locale - locale_path = os.path.join(os.path.dirname(__file__), actual_locale) - while not os.path.exists(locale_path): - if actual_locale == locale: - raise ValueError("Locale [{}] does not exist.".format(locale)) - - actual_locale = actual_locale.split("_")[0] - - m = import_module("pendulum.locales.{}.locale".format(actual_locale)) - - cls._cache[locale] = cls(locale, m.locale) - - return cls._cache[locale] - - @classmethod - def normalize_locale(cls, locale): # type: (str) -> str - m = re.match("([a-z]{2})[-_]([a-z]{2})", locale, re.I) - if m: - return "{}_{}".format(m.group(1).lower(), m.group(2).lower()) - else: - return locale.lower() - - def get(self, key, default=None): # type: (str, Optional[Any]) -> Any - if key in self._key_cache: - return self._key_cache[key] - - parts = key.split(".") - try: - result = self._data[parts[0]] - for part in parts[1:]: - result = result[part] - except KeyError: - result = default - - if isinstance(result, basestring): - result = decode(result) - - self._key_cache[key] = result - - return self._key_cache[key] - - def translation(self, key): # type: (str) -> Any - return self.get("translations.{}".format(key)) - - def plural(self, number): # type: (int) -> str - return decode(self._data["plural"](number)) - - def ordinal(self, number): # type: (int) -> str - return decode(self._data["ordinal"](number)) - - def ordinalize(self, number): # type: (int) -> str - ordinal = self.get("custom.ordinal.{}".format(self.ordinal(number))) - - if not ordinal: - return decode("{}".format(number)) - - return decode("{}{}".format(number, ordinal)) - - def match_translation(self, key, value): - translations = self.translation(key) - if value not in translations.values(): - return None - - return {v: k for k, v in translations.items()}[value] - - def __repr__(self): - return "{}('{}')".format(self.__class__.__name__, self._locale) +from __future__ import annotations + +from importlib import import_module +from pathlib import Path + +import re +import sys +from typing import Any, cast +from typing import Dict + +if sys.version_info >= (3, 9): + from importlib import resources +else: + import importlib_resources as resources + + +class Locale: + """ + Represent a specific locale. + """ + + _cache: dict[str, Locale] = {} + + def __init__(self, locale: str, data: Any) -> None: + self._locale: str = locale + self._data: Any = data + self._key_cache: dict[str, str] = {} + + @classmethod + def load(cls, locale: str | Locale) -> Locale: + if isinstance(locale, Locale): + return locale + + locale = cls.normalize_locale(locale) + if locale in cls._cache: + return cls._cache[locale] + + # Checking locale existence + actual_locale = locale + locale_path = cast(Path, resources.files(__package__).joinpath(actual_locale)) + while not locale_path.exists(): + if actual_locale == locale: + raise ValueError(f"Locale [{locale}] does not exist.") + + actual_locale = actual_locale.split("_")[0] + + m = import_module(f"pendulum.locales.{actual_locale}.locale") + + cls._cache[locale] = cls(locale, m.locale) + + return cls._cache[locale] + + @classmethod + def normalize_locale(cls, locale: str) -> str: + m = re.match("([a-z]{2})[-_]([a-z]{2})", locale, re.I) + if m: + return f"{m.group(1).lower()}_{m.group(2).lower()}" + else: + return locale.lower() + + def get(self, key: str, default: Any | None = None) -> Any: + if key in self._key_cache: + return self._key_cache[key] + + parts = key.split(".") + try: + result = self._data[parts[0]] + for part in parts[1:]: + result = result[part] + except KeyError: + result = default + + self._key_cache[key] = result + + return self._key_cache[key] + + def translation(self, key: str) -> Any: + return self.get(f"translations.{key}") + + def plural(self, number: int) -> str: + return cast(str, self._data["plural"](number)) + + def ordinal(self, number: int) -> str: + return cast(str, self._data["ordinal"](number)) + + def ordinalize(self, number: int) -> str: + ordinal = self.get(f"custom.ordinal.{self.ordinal(number)}") + + if not ordinal: + return f"{number}" + + return f"{number}{ordinal}" + + def match_translation(self, key: str, value: Any) -> dict[str, str] | None: + translations = self.translation(key) + if value not in translations.values(): + return None + + return cast(Dict[str, str], {v: k for k, v in translations.items()}[value]) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}('{self._locale}')" diff --git a/pendulum/locales/lt/custom.py b/pendulum/locales/lt/custom.py index addaaf8..6480c31 100644 --- a/pendulum/locales/lt/custom.py +++ b/pendulum/locales/lt/custom.py @@ -1,122 +1,118 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - - -""" -lt custom locale file. -""" - -translations = { - # Relative time - "units_relative": { - "year": { - "future": { - "other": "{0} metų", - "one": "{0} metų", - "few": "{0} metų", - "many": "{0} metų", - }, - "past": { - "other": "{0} metų", - "one": "{0} metus", - "few": "{0} metus", - "many": "{0} metų", - }, - }, - "month": { - "future": { - "other": "{0} mėnesių", - "one": "{0} mėnesio", - "few": "{0} mėnesių", - "many": "{0} mėnesio", - }, - "past": { - "other": "{0} mėnesių", - "one": "{0} mėnesį", - "few": "{0} mėnesius", - "many": "{0} mėnesio", - }, - }, - "week": { - "future": { - "other": "{0} savaičių", - "one": "{0} savaitės", - "few": "{0} savaičių", - "many": "{0} savaitės", - }, - "past": { - "other": "{0} savaičių", - "one": "{0} savaitę", - "few": "{0} savaites", - "many": "{0} savaitės", - }, - }, - "day": { - "future": { - "other": "{0} dienų", - "one": "{0} dienos", - "few": "{0} dienų", - "many": "{0} dienos", - }, - "past": { - "other": "{0} dienų", - "one": "{0} dieną", - "few": "{0} dienas", - "many": "{0} dienos", - }, - }, - "hour": { - "future": { - "other": "{0} valandų", - "one": "{0} valandos", - "few": "{0} valandų", - "many": "{0} valandos", - }, - "past": { - "other": "{0} valandų", - "one": "{0} valandą", - "few": "{0} valandas", - "many": "{0} valandos", - }, - }, - "minute": { - "future": { - "other": "{0} minučių", - "one": "{0} minutės", - "few": "{0} minučių", - "many": "{0} minutės", - }, - "past": { - "other": "{0} minučių", - "one": "{0} minutę", - "few": "{0} minutes", - "many": "{0} minutės", - }, - }, - "second": { - "future": { - "other": "{0} sekundžių", - "one": "{0} sekundės", - "few": "{0} sekundžių", - "many": "{0} sekundės", - }, - "past": { - "other": "{0} sekundžių", - "one": "{0} sekundę", - "few": "{0} sekundes", - "many": "{0} sekundės", - }, - }, - }, - "after": "po {0}", - "before": "{0} nuo dabar", - # Date formats - "date_formats": { - "LTS": "HH:mm:ss", - "LT": "HH:mm", - "LLLL": "YYYY [m.] MMMM D [d.], dddd, HH:mm [val.]", - "LLL": "YYYY [m.] MMMM D [d.], HH:mm [val.]", - "LL": "YYYY [m.] MMMM D [d.]", - "L": "YYYY-MM-DD", - }, -} +""" +lt custom locale file. +""" + +translations = { + # Relative time + "units_relative": { + "year": { + "future": { + "other": "{0} metų", + "one": "{0} metų", + "few": "{0} metų", + "many": "{0} metų", + }, + "past": { + "other": "{0} metų", + "one": "{0} metus", + "few": "{0} metus", + "many": "{0} metų", + }, + }, + "month": { + "future": { + "other": "{0} mėnesių", + "one": "{0} mėnesio", + "few": "{0} mėnesių", + "many": "{0} mėnesio", + }, + "past": { + "other": "{0} mėnesių", + "one": "{0} mėnesį", + "few": "{0} mėnesius", + "many": "{0} mėnesio", + }, + }, + "week": { + "future": { + "other": "{0} savaičių", + "one": "{0} savaitės", + "few": "{0} savaičių", + "many": "{0} savaitės", + }, + "past": { + "other": "{0} savaičių", + "one": "{0} savaitę", + "few": "{0} savaites", + "many": "{0} savaitės", + }, + }, + "day": { + "future": { + "other": "{0} dienų", + "one": "{0} dienos", + "few": "{0} dienų", + "many": "{0} dienos", + }, + "past": { + "other": "{0} dienų", + "one": "{0} dieną", + "few": "{0} dienas", + "many": "{0} dienos", + }, + }, + "hour": { + "future": { + "other": "{0} valandų", + "one": "{0} valandos", + "few": "{0} valandų", + "many": "{0} valandos", + }, + "past": { + "other": "{0} valandų", + "one": "{0} valandą", + "few": "{0} valandas", + "many": "{0} valandos", + }, + }, + "minute": { + "future": { + "other": "{0} minučių", + "one": "{0} minutės", + "few": "{0} minučių", + "many": "{0} minutės", + }, + "past": { + "other": "{0} minučių", + "one": "{0} minutę", + "few": "{0} minutes", + "many": "{0} minutės", + }, + }, + "second": { + "future": { + "other": "{0} sekundžių", + "one": "{0} sekundės", + "few": "{0} sekundžių", + "many": "{0} sekundės", + }, + "past": { + "other": "{0} sekundžių", + "one": "{0} sekundę", + "few": "{0} sekundes", + "many": "{0} sekundės", + }, + }, + }, + "after": "po {0}", + "before": "{0} nuo dabar", + # Date formats + "date_formats": { + "LTS": "HH:mm:ss", + "LT": "HH:mm", + "LLLL": "YYYY [m.] MMMM D [d.], dddd, HH:mm [val.]", + "LLL": "YYYY [m.] MMMM D [d.], HH:mm [val.]", + "LL": "YYYY [m.] MMMM D [d.]", + "L": "YYYY-MM-DD", + }, +} diff --git a/pendulum/locales/lt/locale.py b/pendulum/locales/lt/locale.py index 12451b6..fb017ef 100644 --- a/pendulum/locales/lt/locale.py +++ b/pendulum/locales/lt/locale.py @@ -1,258 +1,255 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from .custom import translations as custom_translations - - -""" -lt locale file. - -It has been generated automatically and must not be modified directly. -""" - - -locale = { - "plural": lambda n: "few" - if ( - ((n % 10) == (n % 10) and (((n % 10) >= 2 and (n % 10) <= 9))) - and (not ((n % 100) == (n % 100) and (((n % 100) >= 11 and (n % 100) <= 19)))) - ) - else "many" - if (not (0 == 0 and ((0 == 0)))) - else "one" - if ( - ((n % 10) == (n % 10) and (((n % 10) == 1))) - and (not ((n % 100) == (n % 100) and (((n % 100) >= 11 and (n % 100) <= 19)))) - ) - else "other", - "ordinal": lambda n: "other", - "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"}, - "wide": { - 0: "sekmadienis", - 1: "pirmadienis", - 2: "antradienis", - 3: "trečiadienis", - 4: "ketvirtadienis", - 5: "penktadienis", - 6: "šeštadienis", - }, - }, - "months": { - "abbreviated": { - 1: "saus.", - 2: "vas.", - 3: "kov.", - 4: "bal.", - 5: "geg.", - 6: "birž.", - 7: "liep.", - 8: "rugp.", - 9: "rugs.", - 10: "spal.", - 11: "lapkr.", - 12: "gruod.", - }, - "narrow": { - 1: "S", - 2: "V", - 3: "K", - 4: "B", - 5: "G", - 6: "B", - 7: "L", - 8: "R", - 9: "R", - 10: "S", - 11: "L", - 12: "G", - }, - "wide": { - 1: "sausio", - 2: "vasario", - 3: "kovo", - 4: "balandžio", - 5: "gegužės", - 6: "birželio", - 7: "liepos", - 8: "rugpjūčio", - 9: "rugsėjo", - 10: "spalio", - 11: "lapkričio", - 12: "gruodžio", - }, - }, - "units": { - "year": { - "one": "{0} metai", - "few": "{0} metai", - "many": "{0} metų", - "other": "{0} metų", - }, - "month": { - "one": "{0} mėnuo", - "few": "{0} mėnesiai", - "many": "{0} mėnesio", - "other": "{0} mėnesių", - }, - "week": { - "one": "{0} savaitė", - "few": "{0} savaitės", - "many": "{0} savaitės", - "other": "{0} savaičių", - }, - "day": { - "one": "{0} diena", - "few": "{0} dienos", - "many": "{0} dienos", - "other": "{0} dienų", - }, - "hour": { - "one": "{0} valanda", - "few": "{0} valandos", - "many": "{0} valandos", - "other": "{0} valandų", - }, - "minute": { - "one": "{0} minutė", - "few": "{0} minutės", - "many": "{0} minutės", - "other": "{0} minučių", - }, - "second": { - "one": "{0} sekundė", - "few": "{0} sekundės", - "many": "{0} sekundės", - "other": "{0} sekundžių", - }, - "microsecond": { - "one": "{0} mikrosekundė", - "few": "{0} mikrosekundės", - "many": "{0} mikrosekundės", - "other": "{0} mikrosekundžių", - }, - }, - "relative": { - "year": { - "future": { - "other": "po {0} metų", - "one": "po {0} metų", - "few": "po {0} metų", - "many": "po {0} metų", - }, - "past": { - "other": "prieš {0} metų", - "one": "prieš {0} metus", - "few": "prieš {0} metus", - "many": "prieš {0} metų", - }, - }, - "month": { - "future": { - "other": "po {0} mėnesių", - "one": "po {0} mėnesio", - "few": "po {0} mėnesių", - "many": "po {0} mėnesio", - }, - "past": { - "other": "prieš {0} mėnesių", - "one": "prieš {0} mėnesį", - "few": "prieš {0} mėnesius", - "many": "prieš {0} mėnesio", - }, - }, - "week": { - "future": { - "other": "po {0} savaičių", - "one": "po {0} savaitės", - "few": "po {0} savaičių", - "many": "po {0} savaitės", - }, - "past": { - "other": "prieš {0} savaičių", - "one": "prieš {0} savaitę", - "few": "prieš {0} savaites", - "many": "prieš {0} savaitės", - }, - }, - "day": { - "future": { - "other": "po {0} dienų", - "one": "po {0} dienos", - "few": "po {0} dienų", - "many": "po {0} dienos", - }, - "past": { - "other": "prieš {0} dienų", - "one": "prieš {0} dieną", - "few": "prieš {0} dienas", - "many": "prieš {0} dienos", - }, - }, - "hour": { - "future": { - "other": "po {0} valandų", - "one": "po {0} valandos", - "few": "po {0} valandų", - "many": "po {0} valandos", - }, - "past": { - "other": "prieš {0} valandų", - "one": "prieš {0} valandą", - "few": "prieš {0} valandas", - "many": "prieš {0} valandos", - }, - }, - "minute": { - "future": { - "other": "po {0} minučių", - "one": "po {0} minutės", - "few": "po {0} minučių", - "many": "po {0} minutės", - }, - "past": { - "other": "prieš {0} minučių", - "one": "prieš {0} minutę", - "few": "prieš {0} minutes", - "many": "prieš {0} minutės", - }, - }, - "second": { - "future": { - "other": "po {0} sekundžių", - "one": "po {0} sekundės", - "few": "po {0} sekundžių", - "many": "po {0} sekundės", - }, - "past": { - "other": "prieš {0} sekundžių", - "one": "prieš {0} sekundę", - "few": "prieš {0} sekundes", - "many": "prieš {0} sekundės", - }, - }, - }, - "day_periods": { - "midnight": "vidurnaktis", - "am": "priešpiet", - "noon": "perpiet", - "pm": "popiet", - "morning1": "rytas", - "afternoon1": "popietė", - "evening1": "vakaras", - "night1": "naktis", - }, - }, - "custom": custom_translations, -} +from .custom import translations as custom_translations + + +""" +lt locale file. + +It has been generated automatically and must not be modified directly. +""" + + +locale = { + "plural": lambda n: "few" + if ( + ((n % 10) == (n % 10) and ((n % 10) >= 2 and (n % 10) <= 9)) + and (not ((n % 100) == (n % 100) and ((n % 100) >= 11 and (n % 100) <= 19))) + ) + else "many" + if (not (0 == 0 and (0 == 0))) + else "one" + if ( + ((n % 10) == (n % 10) and ((n % 10) == 1)) + and (not ((n % 100) == (n % 100) and ((n % 100) >= 11 and (n % 100) <= 19))) + ) + else "other", + "ordinal": lambda n: "other", + "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"}, + "wide": { + 0: "sekmadienis", + 1: "pirmadienis", + 2: "antradienis", + 3: "trečiadienis", + 4: "ketvirtadienis", + 5: "penktadienis", + 6: "šeštadienis", + }, + }, + "months": { + "abbreviated": { + 1: "saus.", + 2: "vas.", + 3: "kov.", + 4: "bal.", + 5: "geg.", + 6: "birž.", + 7: "liep.", + 8: "rugp.", + 9: "rugs.", + 10: "spal.", + 11: "lapkr.", + 12: "gruod.", + }, + "narrow": { + 1: "S", + 2: "V", + 3: "K", + 4: "B", + 5: "G", + 6: "B", + 7: "L", + 8: "R", + 9: "R", + 10: "S", + 11: "L", + 12: "G", + }, + "wide": { + 1: "sausio", + 2: "vasario", + 3: "kovo", + 4: "balandžio", + 5: "gegužės", + 6: "birželio", + 7: "liepos", + 8: "rugpjūčio", + 9: "rugsėjo", + 10: "spalio", + 11: "lapkričio", + 12: "gruodžio", + }, + }, + "units": { + "year": { + "one": "{0} metai", + "few": "{0} metai", + "many": "{0} metų", + "other": "{0} metų", + }, + "month": { + "one": "{0} mėnuo", + "few": "{0} mėnesiai", + "many": "{0} mėnesio", + "other": "{0} mėnesių", + }, + "week": { + "one": "{0} savaitė", + "few": "{0} savaitės", + "many": "{0} savaitės", + "other": "{0} savaičių", + }, + "day": { + "one": "{0} diena", + "few": "{0} dienos", + "many": "{0} dienos", + "other": "{0} dienų", + }, + "hour": { + "one": "{0} valanda", + "few": "{0} valandos", + "many": "{0} valandos", + "other": "{0} valandų", + }, + "minute": { + "one": "{0} minutė", + "few": "{0} minutės", + "many": "{0} minutės", + "other": "{0} minučių", + }, + "second": { + "one": "{0} sekundė", + "few": "{0} sekundės", + "many": "{0} sekundės", + "other": "{0} sekundžių", + }, + "microsecond": { + "one": "{0} mikrosekundė", + "few": "{0} mikrosekundės", + "many": "{0} mikrosekundės", + "other": "{0} mikrosekundžių", + }, + }, + "relative": { + "year": { + "future": { + "other": "po {0} metų", + "one": "po {0} metų", + "few": "po {0} metų", + "many": "po {0} metų", + }, + "past": { + "other": "prieš {0} metų", + "one": "prieš {0} metus", + "few": "prieš {0} metus", + "many": "prieš {0} metų", + }, + }, + "month": { + "future": { + "other": "po {0} mėnesių", + "one": "po {0} mėnesio", + "few": "po {0} mėnesių", + "many": "po {0} mėnesio", + }, + "past": { + "other": "prieš {0} mėnesių", + "one": "prieš {0} mėnesį", + "few": "prieš {0} mėnesius", + "many": "prieš {0} mėnesio", + }, + }, + "week": { + "future": { + "other": "po {0} savaičių", + "one": "po {0} savaitės", + "few": "po {0} savaičių", + "many": "po {0} savaitės", + }, + "past": { + "other": "prieš {0} savaičių", + "one": "prieš {0} savaitę", + "few": "prieš {0} savaites", + "many": "prieš {0} savaitės", + }, + }, + "day": { + "future": { + "other": "po {0} dienų", + "one": "po {0} dienos", + "few": "po {0} dienų", + "many": "po {0} dienos", + }, + "past": { + "other": "prieš {0} dienų", + "one": "prieš {0} dieną", + "few": "prieš {0} dienas", + "many": "prieš {0} dienos", + }, + }, + "hour": { + "future": { + "other": "po {0} valandų", + "one": "po {0} valandos", + "few": "po {0} valandų", + "many": "po {0} valandos", + }, + "past": { + "other": "prieš {0} valandų", + "one": "prieš {0} valandą", + "few": "prieš {0} valandas", + "many": "prieš {0} valandos", + }, + }, + "minute": { + "future": { + "other": "po {0} minučių", + "one": "po {0} minutės", + "few": "po {0} minučių", + "many": "po {0} minutės", + }, + "past": { + "other": "prieš {0} minučių", + "one": "prieš {0} minutę", + "few": "prieš {0} minutes", + "many": "prieš {0} minutės", + }, + }, + "second": { + "future": { + "other": "po {0} sekundžių", + "one": "po {0} sekundės", + "few": "po {0} sekundžių", + "many": "po {0} sekundės", + }, + "past": { + "other": "prieš {0} sekundžių", + "one": "prieš {0} sekundę", + "few": "prieš {0} sekundes", + "many": "prieš {0} sekundės", + }, + }, + }, + "day_periods": { + "midnight": "vidurnaktis", + "am": "priešpiet", + "noon": "perpiet", + "pm": "popiet", + "morning1": "rytas", + "afternoon1": "popietė", + "evening1": "vakaras", + "night1": "naktis", + }, + }, + "custom": custom_translations, +} diff --git a/pendulum/locales/nb/custom.py b/pendulum/locales/nb/custom.py index 666f1b4..4c7cd6a 100644 --- a/pendulum/locales/nb/custom.py +++ b/pendulum/locales/nb/custom.py @@ -1,24 +1,20 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - - -""" -nn custom locale file. -""" - -translations = { - # Relative time - "after": "{0} etter", - "before": "{0} før", - # Ordinals - "ordinal": {"one": ".", "two": ".", "few": ".", "other": "."}, - # Date formats - "date_formats": { - "LTS": "HH:mm:ss", - "LT": "HH:mm", - "LLLL": "dddd Do MMMM YYYY HH:mm", - "LLL": "Do MMMM YYYY HH:mm", - "LL": "Do MMMM YYYY", - "L": "DD.MM.YYYY", - }, -} +""" +nn custom locale file. +""" + +translations = { + # Relative time + "after": "{0} etter", + "before": "{0} før", + # Ordinals + "ordinal": {"one": ".", "two": ".", "few": ".", "other": "."}, + # Date formats + "date_formats": { + "LTS": "HH:mm:ss", + "LT": "HH:mm", + "LLLL": "dddd Do MMMM YYYY HH:mm", + "LLL": "Do MMMM YYYY HH:mm", + "LL": "Do MMMM YYYY", + "L": "DD.MM.YYYY", + }, +} diff --git a/pendulum/locales/nb/locale.py b/pendulum/locales/nb/locale.py index 9ef9160..c8297a8 100644 --- a/pendulum/locales/nb/locale.py +++ b/pendulum/locales/nb/locale.py @@ -1,153 +1,150 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from .custom import translations as custom_translations - - -""" -nb 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: "søn.", - 1: "man.", - 2: "tir.", - 3: "ons.", - 4: "tor.", - 5: "fre.", - 6: "lør.", - }, - "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ø.", - }, - "wide": { - 0: "søndag", - 1: "mandag", - 2: "tirsdag", - 3: "onsdag", - 4: "torsdag", - 5: "fredag", - 6: "lørdag", - }, - }, - "months": { - "abbreviated": { - 1: "jan.", - 2: "feb.", - 3: "mar.", - 4: "apr.", - 5: "mai", - 6: "jun.", - 7: "jul.", - 8: "aug.", - 9: "sep.", - 10: "okt.", - 11: "nov.", - 12: "des.", - }, - "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: "januar", - 2: "februar", - 3: "mars", - 4: "april", - 5: "mai", - 6: "juni", - 7: "juli", - 8: "august", - 9: "september", - 10: "oktober", - 11: "november", - 12: "desember", - }, - }, - "units": { - "year": {"one": "{0} år", "other": "{0} år"}, - "month": {"one": "{0} måned", "other": "{0} måneder"}, - "week": {"one": "{0} uke", "other": "{0} uker"}, - "day": {"one": "{0} dag", "other": "{0} dager"}, - "hour": {"one": "{0} time", "other": "{0} timer"}, - "minute": {"one": "{0} minutt", "other": "{0} minutter"}, - "second": {"one": "{0} sekund", "other": "{0} sekunder"}, - "microsecond": {"one": "{0} mikrosekund", "other": "{0} mikrosekunder"}, - }, - "relative": { - "year": { - "future": {"other": "om {0} år", "one": "om {0} år"}, - "past": {"other": "for {0} år siden", "one": "for {0} år siden"}, - }, - "month": { - "future": {"other": "om {0} måneder", "one": "om {0} måned"}, - "past": { - "other": "for {0} måneder siden", - "one": "for {0} måned siden", - }, - }, - "week": { - "future": {"other": "om {0} uker", "one": "om {0} uke"}, - "past": {"other": "for {0} uker siden", "one": "for {0} uke siden"}, - }, - "day": { - "future": {"other": "om {0} dager", "one": "om {0} dag"}, - "past": {"other": "for {0} dager siden", "one": "for {0} dag siden"}, - }, - "hour": { - "future": {"other": "om {0} timer", "one": "om {0} time"}, - "past": {"other": "for {0} timer siden", "one": "for {0} time siden"}, - }, - "minute": { - "future": {"other": "om {0} minutter", "one": "om {0} minutt"}, - "past": { - "other": "for {0} minutter siden", - "one": "for {0} minutt siden", - }, - }, - "second": { - "future": {"other": "om {0} sekunder", "one": "om {0} sekund"}, - "past": { - "other": "for {0} sekunder siden", - "one": "for {0} sekund siden", - }, - }, - }, - "day_periods": { - "midnight": "midnatt", - "am": "a.m.", - "pm": "p.m.", - "morning1": "morgenen", - "morning2": "formiddagen", - "afternoon1": "ettermiddagen", - "evening1": "kvelden", - "night1": "natten", - }, - }, - "custom": custom_translations, -} +from .custom import translations as custom_translations + + +""" +nb 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: "søn.", + 1: "man.", + 2: "tir.", + 3: "ons.", + 4: "tor.", + 5: "fre.", + 6: "lør.", + }, + "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ø.", + }, + "wide": { + 0: "søndag", + 1: "mandag", + 2: "tirsdag", + 3: "onsdag", + 4: "torsdag", + 5: "fredag", + 6: "lørdag", + }, + }, + "months": { + "abbreviated": { + 1: "jan.", + 2: "feb.", + 3: "mar.", + 4: "apr.", + 5: "mai", + 6: "jun.", + 7: "jul.", + 8: "aug.", + 9: "sep.", + 10: "okt.", + 11: "nov.", + 12: "des.", + }, + "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: "januar", + 2: "februar", + 3: "mars", + 4: "april", + 5: "mai", + 6: "juni", + 7: "juli", + 8: "august", + 9: "september", + 10: "oktober", + 11: "november", + 12: "desember", + }, + }, + "units": { + "year": {"one": "{0} år", "other": "{0} år"}, + "month": {"one": "{0} måned", "other": "{0} måneder"}, + "week": {"one": "{0} uke", "other": "{0} uker"}, + "day": {"one": "{0} dag", "other": "{0} dager"}, + "hour": {"one": "{0} time", "other": "{0} timer"}, + "minute": {"one": "{0} minutt", "other": "{0} minutter"}, + "second": {"one": "{0} sekund", "other": "{0} sekunder"}, + "microsecond": {"one": "{0} mikrosekund", "other": "{0} mikrosekunder"}, + }, + "relative": { + "year": { + "future": {"other": "om {0} år", "one": "om {0} år"}, + "past": {"other": "for {0} år siden", "one": "for {0} år siden"}, + }, + "month": { + "future": {"other": "om {0} måneder", "one": "om {0} måned"}, + "past": { + "other": "for {0} måneder siden", + "one": "for {0} måned siden", + }, + }, + "week": { + "future": {"other": "om {0} uker", "one": "om {0} uke"}, + "past": {"other": "for {0} uker siden", "one": "for {0} uke siden"}, + }, + "day": { + "future": {"other": "om {0} dager", "one": "om {0} dag"}, + "past": {"other": "for {0} dager siden", "one": "for {0} dag siden"}, + }, + "hour": { + "future": {"other": "om {0} timer", "one": "om {0} time"}, + "past": {"other": "for {0} timer siden", "one": "for {0} time siden"}, + }, + "minute": { + "future": {"other": "om {0} minutter", "one": "om {0} minutt"}, + "past": { + "other": "for {0} minutter siden", + "one": "for {0} minutt siden", + }, + }, + "second": { + "future": {"other": "om {0} sekunder", "one": "om {0} sekund"}, + "past": { + "other": "for {0} sekunder siden", + "one": "for {0} sekund siden", + }, + }, + }, + "day_periods": { + "midnight": "midnatt", + "am": "a.m.", + "pm": "p.m.", + "morning1": "morgenen", + "morning2": "formiddagen", + "afternoon1": "ettermiddagen", + "evening1": "kvelden", + "night1": "natten", + }, + }, + "custom": custom_translations, +} diff --git a/pendulum/locales/nl/custom.py b/pendulum/locales/nl/custom.py index c957cda..2ca5a85 100644 --- a/pendulum/locales/nl/custom.py +++ b/pendulum/locales/nl/custom.py @@ -1,27 +1,23 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - - -""" -nl custom locale file. -""" - -translations = { - "units": {"few_second": "enkele seconden"}, - # Relative time - "ago": "{} geleden", - "from_now": "over {}", - "after": "{0} later", - "before": "{0} eerder", - # Ordinals - "ordinal": {"other": "e"}, - # Date formats - "date_formats": { - "L": "DD-MM-YYYY", - "LL": "D MMMM YYYY", - "LLL": "D MMMM YYYY HH:mm", - "LLLL": "dddd D MMMM YYYY HH:mm", - "LT": "HH:mm", - "LTS": "HH:mm:ss", - }, -} +""" +nl custom locale file. +""" + +translations = { + "units": {"few_second": "enkele seconden"}, + # Relative time + "ago": "{} geleden", + "from_now": "over {}", + "after": "{0} later", + "before": "{0} eerder", + # Ordinals + "ordinal": {"other": "e"}, + # Date formats + "date_formats": { + "L": "DD-MM-YYYY", + "LL": "D MMMM YYYY", + "LLL": "D MMMM YYYY HH:mm", + "LLLL": "dddd D MMMM YYYY HH:mm", + "LT": "HH:mm", + "LTS": "HH:mm:ss", + }, +} diff --git a/pendulum/locales/nl/locale.py b/pendulum/locales/nl/locale.py index 270f18e..cb1570d 100644 --- a/pendulum/locales/nl/locale.py +++ b/pendulum/locales/nl/locale.py @@ -1,137 +1,134 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from .custom import translations as custom_translations - - -""" -nl 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: "other", - "translations": { - "days": { - "abbreviated": { - 0: "zo", - 1: "ma", - 2: "di", - 3: "wo", - 4: "do", - 5: "vr", - 6: "za", - }, - "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"}, - "wide": { - 0: "zondag", - 1: "maandag", - 2: "dinsdag", - 3: "woensdag", - 4: "donderdag", - 5: "vrijdag", - 6: "zaterdag", - }, - }, - "months": { - "abbreviated": { - 1: "jan.", - 2: "feb.", - 3: "mrt.", - 4: "apr.", - 5: "mei", - 6: "jun.", - 7: "jul.", - 8: "aug.", - 9: "sep.", - 10: "okt.", - 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: "januari", - 2: "februari", - 3: "maart", - 4: "april", - 5: "mei", - 6: "juni", - 7: "juli", - 8: "augustus", - 9: "september", - 10: "oktober", - 11: "november", - 12: "december", - }, - }, - "units": { - "year": {"one": "{0} jaar", "other": "{0} jaar"}, - "month": {"one": "{0} maand", "other": "{0} maanden"}, - "week": {"one": "{0} week", "other": "{0} weken"}, - "day": {"one": "{0} dag", "other": "{0} dagen"}, - "hour": {"one": "{0} uur", "other": "{0} uur"}, - "minute": {"one": "{0} minuut", "other": "{0} minuten"}, - "second": {"one": "{0} seconde", "other": "{0} seconden"}, - "microsecond": {"one": "{0} microseconde", "other": "{0} microseconden"}, - }, - "relative": { - "year": { - "future": {"other": "over {0} jaar", "one": "over {0} jaar"}, - "past": {"other": "{0} jaar geleden", "one": "{0} jaar geleden"}, - }, - "month": { - "future": {"other": "over {0} maanden", "one": "over {0} maand"}, - "past": {"other": "{0} maanden geleden", "one": "{0} maand geleden"}, - }, - "week": { - "future": {"other": "over {0} weken", "one": "over {0} week"}, - "past": {"other": "{0} weken geleden", "one": "{0} week geleden"}, - }, - "day": { - "future": {"other": "over {0} dagen", "one": "over {0} dag"}, - "past": {"other": "{0} dagen geleden", "one": "{0} dag geleden"}, - }, - "hour": { - "future": {"other": "over {0} uur", "one": "over {0} uur"}, - "past": {"other": "{0} uur geleden", "one": "{0} uur geleden"}, - }, - "minute": { - "future": {"other": "over {0} minuten", "one": "over {0} minuut"}, - "past": {"other": "{0} minuten geleden", "one": "{0} minuut geleden"}, - }, - "second": { - "future": {"other": "over {0} seconden", "one": "over {0} seconde"}, - "past": {"other": "{0} seconden geleden", "one": "{0} seconde geleden"}, - }, - }, - "day_periods": { - "midnight": "middernacht", - "am": "a.m.", - "pm": "p.m.", - "morning1": "‘s ochtends", - "afternoon1": "‘s middags", - "evening1": "‘s avonds", - "night1": "‘s nachts", - }, - }, - "custom": custom_translations, -} +from .custom import translations as custom_translations + + +""" +nl 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: "other", + "translations": { + "days": { + "abbreviated": { + 0: "zo", + 1: "ma", + 2: "di", + 3: "wo", + 4: "do", + 5: "vr", + 6: "za", + }, + "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"}, + "wide": { + 0: "zondag", + 1: "maandag", + 2: "dinsdag", + 3: "woensdag", + 4: "donderdag", + 5: "vrijdag", + 6: "zaterdag", + }, + }, + "months": { + "abbreviated": { + 1: "jan.", + 2: "feb.", + 3: "mrt.", + 4: "apr.", + 5: "mei", + 6: "jun.", + 7: "jul.", + 8: "aug.", + 9: "sep.", + 10: "okt.", + 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: "januari", + 2: "februari", + 3: "maart", + 4: "april", + 5: "mei", + 6: "juni", + 7: "juli", + 8: "augustus", + 9: "september", + 10: "oktober", + 11: "november", + 12: "december", + }, + }, + "units": { + "year": {"one": "{0} jaar", "other": "{0} jaar"}, + "month": {"one": "{0} maand", "other": "{0} maanden"}, + "week": {"one": "{0} week", "other": "{0} weken"}, + "day": {"one": "{0} dag", "other": "{0} dagen"}, + "hour": {"one": "{0} uur", "other": "{0} uur"}, + "minute": {"one": "{0} minuut", "other": "{0} minuten"}, + "second": {"one": "{0} seconde", "other": "{0} seconden"}, + "microsecond": {"one": "{0} microseconde", "other": "{0} microseconden"}, + }, + "relative": { + "year": { + "future": {"other": "over {0} jaar", "one": "over {0} jaar"}, + "past": {"other": "{0} jaar geleden", "one": "{0} jaar geleden"}, + }, + "month": { + "future": {"other": "over {0} maanden", "one": "over {0} maand"}, + "past": {"other": "{0} maanden geleden", "one": "{0} maand geleden"}, + }, + "week": { + "future": {"other": "over {0} weken", "one": "over {0} week"}, + "past": {"other": "{0} weken geleden", "one": "{0} week geleden"}, + }, + "day": { + "future": {"other": "over {0} dagen", "one": "over {0} dag"}, + "past": {"other": "{0} dagen geleden", "one": "{0} dag geleden"}, + }, + "hour": { + "future": {"other": "over {0} uur", "one": "over {0} uur"}, + "past": {"other": "{0} uur geleden", "one": "{0} uur geleden"}, + }, + "minute": { + "future": {"other": "over {0} minuten", "one": "over {0} minuut"}, + "past": {"other": "{0} minuten geleden", "one": "{0} minuut geleden"}, + }, + "second": { + "future": {"other": "over {0} seconden", "one": "over {0} seconde"}, + "past": {"other": "{0} seconden geleden", "one": "{0} seconde geleden"}, + }, + }, + "day_periods": { + "midnight": "middernacht", + "am": "a.m.", + "pm": "p.m.", + "morning1": "‘s ochtends", + "afternoon1": "‘s middags", + "evening1": "‘s avonds", + "night1": "‘s nachts", + }, + }, + "custom": custom_translations, +} diff --git a/pendulum/locales/nn/custom.py b/pendulum/locales/nn/custom.py index 666f1b4..4c7cd6a 100644 --- a/pendulum/locales/nn/custom.py +++ b/pendulum/locales/nn/custom.py @@ -1,24 +1,20 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - - -""" -nn custom locale file. -""" - -translations = { - # Relative time - "after": "{0} etter", - "before": "{0} før", - # Ordinals - "ordinal": {"one": ".", "two": ".", "few": ".", "other": "."}, - # Date formats - "date_formats": { - "LTS": "HH:mm:ss", - "LT": "HH:mm", - "LLLL": "dddd Do MMMM YYYY HH:mm", - "LLL": "Do MMMM YYYY HH:mm", - "LL": "Do MMMM YYYY", - "L": "DD.MM.YYYY", - }, -} +""" +nn custom locale file. +""" + +translations = { + # Relative time + "after": "{0} etter", + "before": "{0} før", + # Ordinals + "ordinal": {"one": ".", "two": ".", "few": ".", "other": "."}, + # Date formats + "date_formats": { + "LTS": "HH:mm:ss", + "LT": "HH:mm", + "LLLL": "dddd Do MMMM YYYY HH:mm", + "LLL": "Do MMMM YYYY HH:mm", + "LL": "Do MMMM YYYY", + "L": "DD.MM.YYYY", + }, +} diff --git a/pendulum/locales/nn/locale.py b/pendulum/locales/nn/locale.py index 7236d0c..eb46e1d 100644 --- a/pendulum/locales/nn/locale.py +++ b/pendulum/locales/nn/locale.py @@ -1,144 +1,141 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from .custom import translations as custom_translations - - -""" -nn 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: "søn.", - 1: "mån.", - 2: "tys.", - 3: "ons.", - 4: "tor.", - 5: "fre.", - 6: "lau.", - }, - "narrow": {0: "S", 1: "M", 2: "T", 3: "O", 4: "T", 5: "F", 6: "L"}, - "short": { - 0: "sø.", - 1: "må.", - 2: "ty.", - 3: "on.", - 4: "to.", - 5: "fr.", - 6: "la.", - }, - "wide": { - 0: "søndag", - 1: "måndag", - 2: "tysdag", - 3: "onsdag", - 4: "torsdag", - 5: "fredag", - 6: "laurdag", - }, - }, - "months": { - "abbreviated": { - 1: "jan.", - 2: "feb.", - 3: "mars", - 4: "apr.", - 5: "mai", - 6: "juni", - 7: "juli", - 8: "aug.", - 9: "sep.", - 10: "okt.", - 11: "nov.", - 12: "des.", - }, - "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: "januar", - 2: "februar", - 3: "mars", - 4: "april", - 5: "mai", - 6: "juni", - 7: "juli", - 8: "august", - 9: "september", - 10: "oktober", - 11: "november", - 12: "desember", - }, - }, - "units": { - "year": {"one": "{0} år", "other": "{0} år"}, - "month": {"one": "{0} månad", "other": "{0} månadar"}, - "week": {"one": "{0} veke", "other": "{0} veker"}, - "day": {"one": "{0} dag", "other": "{0} dagar"}, - "hour": {"one": "{0} time", "other": "{0} timar"}, - "minute": {"one": "{0} minutt", "other": "{0} minutt"}, - "second": {"one": "{0} sekund", "other": "{0} sekund"}, - "microsecond": {"one": "{0} mikrosekund", "other": "{0} mikrosekund"}, - }, - "relative": { - "year": { - "future": {"other": "om {0} år", "one": "om {0} år"}, - "past": {"other": "for {0} år sidan", "one": "for {0} år sidan"}, - }, - "month": { - "future": {"other": "om {0} månadar", "one": "om {0} månad"}, - "past": { - "other": "for {0} månadar sidan", - "one": "for {0} månad sidan", - }, - }, - "week": { - "future": {"other": "om {0} veker", "one": "om {0} veke"}, - "past": {"other": "for {0} veker sidan", "one": "for {0} veke sidan"}, - }, - "day": { - "future": {"other": "om {0} dagar", "one": "om {0} dag"}, - "past": {"other": "for {0} dagar sidan", "one": "for {0} dag sidan"}, - }, - "hour": { - "future": {"other": "om {0} timar", "one": "om {0} time"}, - "past": {"other": "for {0} timar sidan", "one": "for {0} time sidan"}, - }, - "minute": { - "future": {"other": "om {0} minutt", "one": "om {0} minutt"}, - "past": { - "other": "for {0} minutt sidan", - "one": "for {0} minutt sidan", - }, - }, - "second": { - "future": {"other": "om {0} sekund", "one": "om {0} sekund"}, - "past": { - "other": "for {0} sekund sidan", - "one": "for {0} sekund sidan", - }, - }, - }, - "day_periods": {"am": "formiddag", "pm": "ettermiddag"}, - }, - "custom": custom_translations, -} +from .custom import translations as custom_translations + + +""" +nn 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: "søn.", + 1: "mån.", + 2: "tys.", + 3: "ons.", + 4: "tor.", + 5: "fre.", + 6: "lau.", + }, + "narrow": {0: "S", 1: "M", 2: "T", 3: "O", 4: "T", 5: "F", 6: "L"}, + "short": { + 0: "sø.", + 1: "må.", + 2: "ty.", + 3: "on.", + 4: "to.", + 5: "fr.", + 6: "la.", + }, + "wide": { + 0: "søndag", + 1: "måndag", + 2: "tysdag", + 3: "onsdag", + 4: "torsdag", + 5: "fredag", + 6: "laurdag", + }, + }, + "months": { + "abbreviated": { + 1: "jan.", + 2: "feb.", + 3: "mars", + 4: "apr.", + 5: "mai", + 6: "juni", + 7: "juli", + 8: "aug.", + 9: "sep.", + 10: "okt.", + 11: "nov.", + 12: "des.", + }, + "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: "januar", + 2: "februar", + 3: "mars", + 4: "april", + 5: "mai", + 6: "juni", + 7: "juli", + 8: "august", + 9: "september", + 10: "oktober", + 11: "november", + 12: "desember", + }, + }, + "units": { + "year": {"one": "{0} år", "other": "{0} år"}, + "month": {"one": "{0} månad", "other": "{0} månadar"}, + "week": {"one": "{0} veke", "other": "{0} veker"}, + "day": {"one": "{0} dag", "other": "{0} dagar"}, + "hour": {"one": "{0} time", "other": "{0} timar"}, + "minute": {"one": "{0} minutt", "other": "{0} minutt"}, + "second": {"one": "{0} sekund", "other": "{0} sekund"}, + "microsecond": {"one": "{0} mikrosekund", "other": "{0} mikrosekund"}, + }, + "relative": { + "year": { + "future": {"other": "om {0} år", "one": "om {0} år"}, + "past": {"other": "for {0} år sidan", "one": "for {0} år sidan"}, + }, + "month": { + "future": {"other": "om {0} månadar", "one": "om {0} månad"}, + "past": { + "other": "for {0} månadar sidan", + "one": "for {0} månad sidan", + }, + }, + "week": { + "future": {"other": "om {0} veker", "one": "om {0} veke"}, + "past": {"other": "for {0} veker sidan", "one": "for {0} veke sidan"}, + }, + "day": { + "future": {"other": "om {0} dagar", "one": "om {0} dag"}, + "past": {"other": "for {0} dagar sidan", "one": "for {0} dag sidan"}, + }, + "hour": { + "future": {"other": "om {0} timar", "one": "om {0} time"}, + "past": {"other": "for {0} timar sidan", "one": "for {0} time sidan"}, + }, + "minute": { + "future": {"other": "om {0} minutt", "one": "om {0} minutt"}, + "past": { + "other": "for {0} minutt sidan", + "one": "for {0} minutt sidan", + }, + }, + "second": { + "future": {"other": "om {0} sekund", "one": "om {0} sekund"}, + "past": { + "other": "for {0} sekund sidan", + "one": "for {0} sekund sidan", + }, + }, + }, + "day_periods": {"am": "formiddag", "pm": "ettermiddag"}, + }, + "custom": custom_translations, +} diff --git a/pendulum/locales/pl/custom.py b/pendulum/locales/pl/custom.py index dc20eb8..9741b74 100644 --- a/pendulum/locales/pl/custom.py +++ b/pendulum/locales/pl/custom.py @@ -1,25 +1,21 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - - -""" -pl custom locale file. -""" - -translations = { - "units": {"few_second": "kilka sekund"}, - # Relative time - "ago": "{} temu", - "from_now": "za {}", - "after": "{0} po", - "before": "{0} przed", - # 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", - }, -} +""" +pl custom locale file. +""" + +translations = { + "units": {"few_second": "kilka sekund"}, + # Relative time + "ago": "{} temu", + "from_now": "za {}", + "after": "{0} po", + "before": "{0} przed", + # 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/pendulum/locales/pl/locale.py b/pendulum/locales/pl/locale.py index e603efb..bf6af10 100644 --- a/pendulum/locales/pl/locale.py +++ b/pendulum/locales/pl/locale.py @@ -1,282 +1,279 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from .custom import translations as custom_translations - - -""" -pl locale file. - -It has been generated automatically and must not be modified directly. -""" - - -locale = { - "plural": lambda n: "few" - if ( - ( - (0 == 0 and ((0 == 0))) - and ((n % 10) == (n % 10) and (((n % 10) >= 2 and (n % 10) <= 4))) - ) - and (not ((n % 100) == (n % 100) and (((n % 100) >= 12 and (n % 100) <= 14)))) - ) - else "many" - if ( - ( - ( - ((0 == 0 and ((0 == 0))) and (not (n == n and ((n == 1))))) - and ((n % 10) == (n % 10) and (((n % 10) >= 0 and (n % 10) <= 1))) - ) - or ( - (0 == 0 and ((0 == 0))) - and ((n % 10) == (n % 10) and (((n % 10) >= 5 and (n % 10) <= 9))) - ) - ) - or ( - (0 == 0 and ((0 == 0))) - and ((n % 100) == (n % 100) and (((n % 100) >= 12 and (n % 100) <= 14))) - ) - ) - else "one" - if ((n == n and ((n == 1))) and (0 == 0 and ((0 == 0)))) - else "other", - "ordinal": lambda n: "other", - "translations": { - "days": { - "abbreviated": { - 0: "niedz.", - 1: "pon.", - 2: "wt.", - 3: "śr.", - 4: "czw.", - 5: "pt.", - 6: "sob.", - }, - "narrow": {0: "n", 1: "p", 2: "w", 3: "ś", 4: "c", 5: "p", 6: "s"}, - "short": { - 0: "nie", - 1: "pon", - 2: "wto", - 3: "śro", - 4: "czw", - 5: "pią", - 6: "sob", - }, - "wide": { - 0: "niedziela", - 1: "poniedziałek", - 2: "wtorek", - 3: "środa", - 4: "czwartek", - 5: "piątek", - 6: "sobota", - }, - }, - "months": { - "abbreviated": { - 1: "sty", - 2: "lut", - 3: "mar", - 4: "kwi", - 5: "maj", - 6: "cze", - 7: "lip", - 8: "sie", - 9: "wrz", - 10: "paź", - 11: "lis", - 12: "gru", - }, - "narrow": { - 1: "s", - 2: "l", - 3: "m", - 4: "k", - 5: "m", - 6: "c", - 7: "l", - 8: "s", - 9: "w", - 10: "p", - 11: "l", - 12: "g", - }, - "wide": { - 1: "stycznia", - 2: "lutego", - 3: "marca", - 4: "kwietnia", - 5: "maja", - 6: "czerwca", - 7: "lipca", - 8: "sierpnia", - 9: "września", - 10: "października", - 11: "listopada", - 12: "grudnia", - }, - }, - "units": { - "year": { - "one": "{0} rok", - "few": "{0} lata", - "many": "{0} lat", - "other": "{0} roku", - }, - "month": { - "one": "{0} miesiąc", - "few": "{0} miesiące", - "many": "{0} miesięcy", - "other": "{0} miesiąca", - }, - "week": { - "one": "{0} tydzień", - "few": "{0} tygodnie", - "many": "{0} tygodni", - "other": "{0} tygodnia", - }, - "day": { - "one": "{0} dzień", - "few": "{0} dni", - "many": "{0} dni", - "other": "{0} dnia", - }, - "hour": { - "one": "{0} godzina", - "few": "{0} godziny", - "many": "{0} godzin", - "other": "{0} godziny", - }, - "minute": { - "one": "{0} minuta", - "few": "{0} minuty", - "many": "{0} minut", - "other": "{0} minuty", - }, - "second": { - "one": "{0} sekunda", - "few": "{0} sekundy", - "many": "{0} sekund", - "other": "{0} sekundy", - }, - "microsecond": { - "one": "{0} mikrosekunda", - "few": "{0} mikrosekundy", - "many": "{0} mikrosekund", - "other": "{0} mikrosekundy", - }, - }, - "relative": { - "year": { - "future": { - "other": "za {0} roku", - "one": "za {0} rok", - "few": "za {0} lata", - "many": "za {0} lat", - }, - "past": { - "other": "{0} roku temu", - "one": "{0} rok temu", - "few": "{0} lata temu", - "many": "{0} lat temu", - }, - }, - "month": { - "future": { - "other": "za {0} miesiąca", - "one": "za {0} miesiąc", - "few": "za {0} miesiące", - "many": "za {0} miesięcy", - }, - "past": { - "other": "{0} miesiąca temu", - "one": "{0} miesiąc temu", - "few": "{0} miesiące temu", - "many": "{0} miesięcy temu", - }, - }, - "week": { - "future": { - "other": "za {0} tygodnia", - "one": "za {0} tydzień", - "few": "za {0} tygodnie", - "many": "za {0} tygodni", - }, - "past": { - "other": "{0} tygodnia temu", - "one": "{0} tydzień temu", - "few": "{0} tygodnie temu", - "many": "{0} tygodni temu", - }, - }, - "day": { - "future": { - "other": "za {0} dnia", - "one": "za {0} dzień", - "few": "za {0} dni", - "many": "za {0} dni", - }, - "past": { - "other": "{0} dnia temu", - "one": "{0} dzień temu", - "few": "{0} dni temu", - "many": "{0} dni temu", - }, - }, - "hour": { - "future": { - "other": "za {0} godziny", - "one": "za {0} godzinę", - "few": "za {0} godziny", - "many": "za {0} godzin", - }, - "past": { - "other": "{0} godziny temu", - "one": "{0} godzinę temu", - "few": "{0} godziny temu", - "many": "{0} godzin temu", - }, - }, - "minute": { - "future": { - "other": "za {0} minuty", - "one": "za {0} minutę", - "few": "za {0} minuty", - "many": "za {0} minut", - }, - "past": { - "other": "{0} minuty temu", - "one": "{0} minutę temu", - "few": "{0} minuty temu", - "many": "{0} minut temu", - }, - }, - "second": { - "future": { - "other": "za {0} sekundy", - "one": "za {0} sekundę", - "few": "za {0} sekundy", - "many": "za {0} sekund", - }, - "past": { - "other": "{0} sekundy temu", - "one": "{0} sekundę temu", - "few": "{0} sekundy temu", - "many": "{0} sekund temu", - }, - }, - }, - "day_periods": { - "midnight": "o północy", - "am": "AM", - "noon": "w południe", - "pm": "PM", - "morning1": "rano", - "morning2": "przed południem", - "afternoon1": "po południu", - "evening1": "wieczorem", - "night1": "w nocy", - }, - }, - "custom": custom_translations, -} +from .custom import translations as custom_translations + + +""" +pl locale file. + +It has been generated automatically and must not be modified directly. +""" + + +locale = { + "plural": lambda n: "few" + if ( + ( + (0 == 0 and (0 == 0)) + and ((n % 10) == (n % 10) and ((n % 10) >= 2 and (n % 10) <= 4)) + ) + and (not ((n % 100) == (n % 100) and ((n % 100) >= 12 and (n % 100) <= 14))) + ) + else "many" + if ( + ( + ( + ((0 == 0 and (0 == 0)) and (not (n == n and (n == 1)))) + and ((n % 10) == (n % 10) and ((n % 10) >= 0 and (n % 10) <= 1)) + ) + or ( + (0 == 0 and (0 == 0)) + and ((n % 10) == (n % 10) and ((n % 10) >= 5 and (n % 10) <= 9)) + ) + ) + or ( + (0 == 0 and (0 == 0)) + and ((n % 100) == (n % 100) and ((n % 100) >= 12 and (n % 100) <= 14)) + ) + ) + else "one" + if ((n == n and (n == 1)) and (0 == 0 and (0 == 0))) + else "other", + "ordinal": lambda n: "other", + "translations": { + "days": { + "abbreviated": { + 0: "niedz.", + 1: "pon.", + 2: "wt.", + 3: "śr.", + 4: "czw.", + 5: "pt.", + 6: "sob.", + }, + "narrow": {0: "n", 1: "p", 2: "w", 3: "ś", 4: "c", 5: "p", 6: "s"}, + "short": { + 0: "nie", + 1: "pon", + 2: "wto", + 3: "śro", + 4: "czw", + 5: "pią", + 6: "sob", + }, + "wide": { + 0: "niedziela", + 1: "poniedziałek", + 2: "wtorek", + 3: "środa", + 4: "czwartek", + 5: "piątek", + 6: "sobota", + }, + }, + "months": { + "abbreviated": { + 1: "sty", + 2: "lut", + 3: "mar", + 4: "kwi", + 5: "maj", + 6: "cze", + 7: "lip", + 8: "sie", + 9: "wrz", + 10: "paź", + 11: "lis", + 12: "gru", + }, + "narrow": { + 1: "s", + 2: "l", + 3: "m", + 4: "k", + 5: "m", + 6: "c", + 7: "l", + 8: "s", + 9: "w", + 10: "p", + 11: "l", + 12: "g", + }, + "wide": { + 1: "stycznia", + 2: "lutego", + 3: "marca", + 4: "kwietnia", + 5: "maja", + 6: "czerwca", + 7: "lipca", + 8: "sierpnia", + 9: "września", + 10: "października", + 11: "listopada", + 12: "grudnia", + }, + }, + "units": { + "year": { + "one": "{0} rok", + "few": "{0} lata", + "many": "{0} lat", + "other": "{0} roku", + }, + "month": { + "one": "{0} miesiąc", + "few": "{0} miesiące", + "many": "{0} miesięcy", + "other": "{0} miesiąca", + }, + "week": { + "one": "{0} tydzień", + "few": "{0} tygodnie", + "many": "{0} tygodni", + "other": "{0} tygodnia", + }, + "day": { + "one": "{0} dzień", + "few": "{0} dni", + "many": "{0} dni", + "other": "{0} dnia", + }, + "hour": { + "one": "{0} godzina", + "few": "{0} godziny", + "many": "{0} godzin", + "other": "{0} godziny", + }, + "minute": { + "one": "{0} minuta", + "few": "{0} minuty", + "many": "{0} minut", + "other": "{0} minuty", + }, + "second": { + "one": "{0} sekunda", + "few": "{0} sekundy", + "many": "{0} sekund", + "other": "{0} sekundy", + }, + "microsecond": { + "one": "{0} mikrosekunda", + "few": "{0} mikrosekundy", + "many": "{0} mikrosekund", + "other": "{0} mikrosekundy", + }, + }, + "relative": { + "year": { + "future": { + "other": "za {0} roku", + "one": "za {0} rok", + "few": "za {0} lata", + "many": "za {0} lat", + }, + "past": { + "other": "{0} roku temu", + "one": "{0} rok temu", + "few": "{0} lata temu", + "many": "{0} lat temu", + }, + }, + "month": { + "future": { + "other": "za {0} miesiąca", + "one": "za {0} miesiąc", + "few": "za {0} miesiące", + "many": "za {0} miesięcy", + }, + "past": { + "other": "{0} miesiąca temu", + "one": "{0} miesiąc temu", + "few": "{0} miesiące temu", + "many": "{0} miesięcy temu", + }, + }, + "week": { + "future": { + "other": "za {0} tygodnia", + "one": "za {0} tydzień", + "few": "za {0} tygodnie", + "many": "za {0} tygodni", + }, + "past": { + "other": "{0} tygodnia temu", + "one": "{0} tydzień temu", + "few": "{0} tygodnie temu", + "many": "{0} tygodni temu", + }, + }, + "day": { + "future": { + "other": "za {0} dnia", + "one": "za {0} dzień", + "few": "za {0} dni", + "many": "za {0} dni", + }, + "past": { + "other": "{0} dnia temu", + "one": "{0} dzień temu", + "few": "{0} dni temu", + "many": "{0} dni temu", + }, + }, + "hour": { + "future": { + "other": "za {0} godziny", + "one": "za {0} godzinę", + "few": "za {0} godziny", + "many": "za {0} godzin", + }, + "past": { + "other": "{0} godziny temu", + "one": "{0} godzinę temu", + "few": "{0} godziny temu", + "many": "{0} godzin temu", + }, + }, + "minute": { + "future": { + "other": "za {0} minuty", + "one": "za {0} minutę", + "few": "za {0} minuty", + "many": "za {0} minut", + }, + "past": { + "other": "{0} minuty temu", + "one": "{0} minutę temu", + "few": "{0} minuty temu", + "many": "{0} minut temu", + }, + }, + "second": { + "future": { + "other": "za {0} sekundy", + "one": "za {0} sekundę", + "few": "za {0} sekundy", + "many": "za {0} sekund", + }, + "past": { + "other": "{0} sekundy temu", + "one": "{0} sekundę temu", + "few": "{0} sekundy temu", + "many": "{0} sekund temu", + }, + }, + }, + "day_periods": { + "midnight": "o północy", + "am": "AM", + "noon": "w południe", + "pm": "PM", + "morning1": "rano", + "morning2": "przed południem", + "afternoon1": "po południu", + "evening1": "wieczorem", + "night1": "w nocy", + }, + }, + "custom": custom_translations, +} diff --git a/pendulum/locales/pt_br/custom.py b/pendulum/locales/pt_br/custom.py index 3cc3f0d..12aced7 100644 --- a/pendulum/locales/pt_br/custom.py +++ b/pendulum/locales/pt_br/custom.py @@ -1,22 +1,18 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - - -""" -pt-br custom locale file. -""" - -translations = { - # Relative time - "after": "após {0}", - "before": "{0} atrás", - # Date formats - "date_formats": { - "LTS": "HH:mm:ss", - "LT": "HH:mm", - "LLLL": "dddd, D [de] MMMM [de] YYYY [às] HH:mm", - "LLL": "D [de] MMMM [de] YYYY [às] HH:mm", - "LL": "D [de] MMMM [de] YYYY", - "L": "DD/MM/YYYY", - }, -} +""" +pt-br custom locale file. +""" + +translations = { + # Relative time + "after": "após {0}", + "before": "{0} atrás", + # Date formats + "date_formats": { + "LTS": "HH:mm:ss", + "LT": "HH:mm", + "LLLL": "dddd, D [de] MMMM [de] YYYY [às] HH:mm", + "LLL": "D [de] MMMM [de] YYYY [às] HH:mm", + "LL": "D [de] MMMM [de] YYYY", + "L": "DD/MM/YYYY", + }, +} diff --git a/pendulum/locales/pt_br/locale.py b/pendulum/locales/pt_br/locale.py index c70c671..742c41f 100644 --- a/pendulum/locales/pt_br/locale.py +++ b/pendulum/locales/pt_br/locale.py @@ -1,146 +1,143 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from .custom import translations as custom_translations - - -""" -pt_br locale file. - -It has been generated automatically and must not be modified directly. -""" - - -locale = { - "plural": lambda n: "one" - if ((n == n and ((n >= 0 and n <= 2))) and (not (n == n and ((n == 2))))) - else "other", - "ordinal": lambda n: "other", - "translations": { - "days": { - "abbreviated": { - 0: "dom", - 1: "seg", - 2: "ter", - 3: "qua", - 4: "qui", - 5: "sex", - 6: "sáb", - }, - "narrow": {0: "D", 1: "S", 2: "T", 3: "Q", 4: "Q", 5: "S", 6: "S"}, - "short": { - 0: "dom", - 1: "seg", - 2: "ter", - 3: "qua", - 4: "qui", - 5: "sex", - 6: "sáb", - }, - "wide": { - 0: "domingo", - 1: "segunda-feira", - 2: "terça-feira", - 3: "quarta-feira", - 4: "quinta-feira", - 5: "sexta-feira", - 6: "sábado", - }, - }, - "months": { - "abbreviated": { - 1: "jan", - 2: "fev", - 3: "mar", - 4: "abr", - 5: "mai", - 6: "jun", - 7: "jul", - 8: "ago", - 9: "set", - 10: "out", - 11: "nov", - 12: "dez", - }, - "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: "janeiro", - 2: "fevereiro", - 3: "março", - 4: "abril", - 5: "maio", - 6: "junho", - 7: "julho", - 8: "agosto", - 9: "setembro", - 10: "outubro", - 11: "novembro", - 12: "dezembro", - }, - }, - "units": { - "year": {"one": "{0} ano", "other": "{0} anos"}, - "month": {"one": "{0} mês", "other": "{0} meses"}, - "week": {"one": "{0} semana", "other": "{0} semanas"}, - "day": {"one": "{0} dia", "other": "{0} dias"}, - "hour": {"one": "{0} hora", "other": "{0} horas"}, - "minute": {"one": "{0} minuto", "other": "{0} minutos"}, - "second": {"one": "{0} segundo", "other": "{0} segundos"}, - "microsecond": {"one": "{0} microssegundo", "other": "{0} microssegundos"}, - }, - "relative": { - "year": { - "future": {"other": "em {0} anos", "one": "em {0} ano"}, - "past": {"other": "há {0} anos", "one": "há {0} ano"}, - }, - "month": { - "future": {"other": "em {0} meses", "one": "em {0} mês"}, - "past": {"other": "há {0} meses", "one": "há {0} mês"}, - }, - "week": { - "future": {"other": "em {0} semanas", "one": "em {0} semana"}, - "past": {"other": "há {0} semanas", "one": "há {0} semana"}, - }, - "day": { - "future": {"other": "em {0} dias", "one": "em {0} dia"}, - "past": {"other": "há {0} dias", "one": "há {0} dia"}, - }, - "hour": { - "future": {"other": "em {0} horas", "one": "em {0} hora"}, - "past": {"other": "há {0} horas", "one": "há {0} hora"}, - }, - "minute": { - "future": {"other": "em {0} minutos", "one": "em {0} minuto"}, - "past": {"other": "há {0} minutos", "one": "há {0} minuto"}, - }, - "second": { - "future": {"other": "em {0} segundos", "one": "em {0} segundo"}, - "past": {"other": "há {0} segundos", "one": "há {0} segundo"}, - }, - }, - "day_periods": { - "midnight": "meia-noite", - "am": "AM", - "noon": "meio-dia", - "pm": "PM", - "morning1": "da manhã", - "afternoon1": "da tarde", - "evening1": "da noite", - "night1": "da madrugada", - }, - }, - "custom": custom_translations, -} +from .custom import translations as custom_translations + + +""" +pt_br locale file. + +It has been generated automatically and must not be modified directly. +""" + + +locale = { + "plural": lambda n: "one" + if ((n == n and (n >= 0 and n <= 2)) and (not (n == n and (n == 2)))) + else "other", + "ordinal": lambda n: "other", + "translations": { + "days": { + "abbreviated": { + 0: "dom", + 1: "seg", + 2: "ter", + 3: "qua", + 4: "qui", + 5: "sex", + 6: "sáb", + }, + "narrow": {0: "D", 1: "S", 2: "T", 3: "Q", 4: "Q", 5: "S", 6: "S"}, + "short": { + 0: "dom", + 1: "seg", + 2: "ter", + 3: "qua", + 4: "qui", + 5: "sex", + 6: "sáb", + }, + "wide": { + 0: "domingo", + 1: "segunda-feira", + 2: "terça-feira", + 3: "quarta-feira", + 4: "quinta-feira", + 5: "sexta-feira", + 6: "sábado", + }, + }, + "months": { + "abbreviated": { + 1: "jan", + 2: "fev", + 3: "mar", + 4: "abr", + 5: "mai", + 6: "jun", + 7: "jul", + 8: "ago", + 9: "set", + 10: "out", + 11: "nov", + 12: "dez", + }, + "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: "janeiro", + 2: "fevereiro", + 3: "março", + 4: "abril", + 5: "maio", + 6: "junho", + 7: "julho", + 8: "agosto", + 9: "setembro", + 10: "outubro", + 11: "novembro", + 12: "dezembro", + }, + }, + "units": { + "year": {"one": "{0} ano", "other": "{0} anos"}, + "month": {"one": "{0} mês", "other": "{0} meses"}, + "week": {"one": "{0} semana", "other": "{0} semanas"}, + "day": {"one": "{0} dia", "other": "{0} dias"}, + "hour": {"one": "{0} hora", "other": "{0} horas"}, + "minute": {"one": "{0} minuto", "other": "{0} minutos"}, + "second": {"one": "{0} segundo", "other": "{0} segundos"}, + "microsecond": {"one": "{0} microssegundo", "other": "{0} microssegundos"}, + }, + "relative": { + "year": { + "future": {"other": "em {0} anos", "one": "em {0} ano"}, + "past": {"other": "há {0} anos", "one": "há {0} ano"}, + }, + "month": { + "future": {"other": "em {0} meses", "one": "em {0} mês"}, + "past": {"other": "há {0} meses", "one": "há {0} mês"}, + }, + "week": { + "future": {"other": "em {0} semanas", "one": "em {0} semana"}, + "past": {"other": "há {0} semanas", "one": "há {0} semana"}, + }, + "day": { + "future": {"other": "em {0} dias", "one": "em {0} dia"}, + "past": {"other": "há {0} dias", "one": "há {0} dia"}, + }, + "hour": { + "future": {"other": "em {0} horas", "one": "em {0} hora"}, + "past": {"other": "há {0} horas", "one": "há {0} hora"}, + }, + "minute": { + "future": {"other": "em {0} minutos", "one": "em {0} minuto"}, + "past": {"other": "há {0} minutos", "one": "há {0} minuto"}, + }, + "second": { + "future": {"other": "em {0} segundos", "one": "em {0} segundo"}, + "past": {"other": "há {0} segundos", "one": "há {0} segundo"}, + }, + }, + "day_periods": { + "midnight": "meia-noite", + "am": "AM", + "noon": "meio-dia", + "pm": "PM", + "morning1": "da manhã", + "afternoon1": "da tarde", + "evening1": "da noite", + "night1": "da madrugada", + }, + }, + "custom": custom_translations, +} diff --git a/pendulum/locales/ru/custom.py b/pendulum/locales/ru/custom.py index ed770c3..b4c89bb 100644 --- a/pendulum/locales/ru/custom.py +++ b/pendulum/locales/ru/custom.py @@ -1,24 +1,20 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - - -""" -ru custom locale file. -""" - -translations = { - # Relative time - "ago": "{} назад", - "from_now": "через {}", - "after": "{0} после", - "before": "{0} до", - # 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", - }, -} +""" +ru custom locale file. +""" + +translations = { + # Relative time + "ago": "{} назад", + "from_now": "через {}", + "after": "{0} после", + "before": "{0} до", + # 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/pendulum/locales/ru/locale.py b/pendulum/locales/ru/locale.py index 8c7d53b..3736e0b 100644 --- a/pendulum/locales/ru/locale.py +++ b/pendulum/locales/ru/locale.py @@ -1,273 +1,270 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from .custom import translations as custom_translations - - -""" -ru locale file. - -It has been generated automatically and must not be modified directly. -""" - - -locale = { - "plural": lambda n: "few" - if ( - ( - (0 == 0 and ((0 == 0))) - and ((n % 10) == (n % 10) and (((n % 10) >= 2 and (n % 10) <= 4))) - ) - and (not ((n % 100) == (n % 100) and (((n % 100) >= 12 and (n % 100) <= 14)))) - ) - else "many" - if ( - ( - ((0 == 0 and ((0 == 0))) and ((n % 10) == (n % 10) and (((n % 10) == 0)))) - or ( - (0 == 0 and ((0 == 0))) - and ((n % 10) == (n % 10) and (((n % 10) >= 5 and (n % 10) <= 9))) - ) - ) - or ( - (0 == 0 and ((0 == 0))) - and ((n % 100) == (n % 100) and (((n % 100) >= 11 and (n % 100) <= 14))) - ) - ) - else "one" - if ( - ((0 == 0 and ((0 == 0))) and ((n % 10) == (n % 10) and (((n % 10) == 1)))) - and (not ((n % 100) == (n % 100) and (((n % 100) == 11)))) - ) - else "other", - "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: "сб"}, - "wide": { - 0: "воскресенье", - 1: "понедельник", - 2: "вторник", - 3: "среда", - 4: "четверг", - 5: "пятница", - 6: "суббота", - }, - }, - "months": { - "abbreviated": { - 1: "янв.", - 2: "февр.", - 3: "мар.", - 4: "апр.", - 5: "мая", - 6: "июн.", - 7: "июл.", - 8: "авг.", - 9: "сент.", - 10: "окт.", - 11: "нояб.", - 12: "дек.", - }, - "narrow": { - 1: "Я", - 2: "Ф", - 3: "М", - 4: "А", - 5: "М", - 6: "И", - 7: "И", - 8: "А", - 9: "С", - 10: "О", - 11: "Н", - 12: "Д", - }, - "wide": { - 1: "января", - 2: "февраля", - 3: "марта", - 4: "апреля", - 5: "мая", - 6: "июня", - 7: "июля", - 8: "августа", - 9: "сентября", - 10: "октября", - 11: "ноября", - 12: "декабря", - }, - }, - "units": { - "year": { - "one": "{0} год", - "few": "{0} года", - "many": "{0} лет", - "other": "{0} года", - }, - "month": { - "one": "{0} месяц", - "few": "{0} месяца", - "many": "{0} месяцев", - "other": "{0} месяца", - }, - "week": { - "one": "{0} неделя", - "few": "{0} недели", - "many": "{0} недель", - "other": "{0} недели", - }, - "day": { - "one": "{0} день", - "few": "{0} дня", - "many": "{0} дней", - "other": "{0} дня", - }, - "hour": { - "one": "{0} час", - "few": "{0} часа", - "many": "{0} часов", - "other": "{0} часа", - }, - "minute": { - "one": "{0} минута", - "few": "{0} минуты", - "many": "{0} минут", - "other": "{0} минуты", - }, - "second": { - "one": "{0} секунда", - "few": "{0} секунды", - "many": "{0} секунд", - "other": "{0} секунды", - }, - "microsecond": { - "one": "{0} микросекунда", - "few": "{0} микросекунды", - "many": "{0} микросекунд", - "other": "{0} микросекунды", - }, - }, - "relative": { - "year": { - "future": { - "other": "через {0} года", - "one": "через {0} год", - "few": "через {0} года", - "many": "через {0} лет", - }, - "past": { - "other": "{0} года назад", - "one": "{0} год назад", - "few": "{0} года назад", - "many": "{0} лет назад", - }, - }, - "month": { - "future": { - "other": "через {0} месяца", - "one": "через {0} месяц", - "few": "через {0} месяца", - "many": "через {0} месяцев", - }, - "past": { - "other": "{0} месяца назад", - "one": "{0} месяц назад", - "few": "{0} месяца назад", - "many": "{0} месяцев назад", - }, - }, - "week": { - "future": { - "other": "через {0} недели", - "one": "через {0} неделю", - "few": "через {0} недели", - "many": "через {0} недель", - }, - "past": { - "other": "{0} недели назад", - "one": "{0} неделю назад", - "few": "{0} недели назад", - "many": "{0} недель назад", - }, - }, - "day": { - "future": { - "other": "через {0} дня", - "one": "через {0} день", - "few": "через {0} дня", - "many": "через {0} дней", - }, - "past": { - "other": "{0} дня назад", - "one": "{0} день назад", - "few": "{0} дня назад", - "many": "{0} дней назад", - }, - }, - "hour": { - "future": { - "other": "через {0} часа", - "one": "через {0} час", - "few": "через {0} часа", - "many": "через {0} часов", - }, - "past": { - "other": "{0} часа назад", - "one": "{0} час назад", - "few": "{0} часа назад", - "many": "{0} часов назад", - }, - }, - "minute": { - "future": { - "other": "через {0} минуты", - "one": "через {0} минуту", - "few": "через {0} минуты", - "many": "через {0} минут", - }, - "past": { - "other": "{0} минуты назад", - "one": "{0} минуту назад", - "few": "{0} минуты назад", - "many": "{0} минут назад", - }, - }, - "second": { - "future": { - "other": "через {0} секунды", - "one": "через {0} секунду", - "few": "через {0} секунды", - "many": "через {0} секунд", - }, - "past": { - "other": "{0} секунды назад", - "one": "{0} секунду назад", - "few": "{0} секунды назад", - "many": "{0} секунд назад", - }, - }, - }, - "day_periods": { - "midnight": "полночь", - "am": "AM", - "noon": "полдень", - "pm": "PM", - "morning1": "утра", - "afternoon1": "дня", - "evening1": "вечера", - "night1": "ночи", - }, - }, - "custom": custom_translations, -} +from .custom import translations as custom_translations + + +""" +ru locale file. + +It has been generated automatically and must not be modified directly. +""" + + +locale = { + "plural": lambda n: "few" + if ( + ( + (0 == 0 and (0 == 0)) + and ((n % 10) == (n % 10) and ((n % 10) >= 2 and (n % 10) <= 4)) + ) + and (not ((n % 100) == (n % 100) and ((n % 100) >= 12 and (n % 100) <= 14))) + ) + else "many" + if ( + ( + ((0 == 0 and (0 == 0)) and ((n % 10) == (n % 10) and ((n % 10) == 0))) + or ( + (0 == 0 and (0 == 0)) + and ((n % 10) == (n % 10) and ((n % 10) >= 5 and (n % 10) <= 9)) + ) + ) + or ( + (0 == 0 and (0 == 0)) + and ((n % 100) == (n % 100) and ((n % 100) >= 11 and (n % 100) <= 14)) + ) + ) + else "one" + if ( + ((0 == 0 and (0 == 0)) and ((n % 10) == (n % 10) and ((n % 10) == 1))) + and (not ((n % 100) == (n % 100) and ((n % 100) == 11))) + ) + else "other", + "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: "сб"}, + "wide": { + 0: "воскресенье", + 1: "понедельник", + 2: "вторник", + 3: "среда", + 4: "четверг", + 5: "пятница", + 6: "суббота", + }, + }, + "months": { + "abbreviated": { + 1: "янв.", + 2: "февр.", + 3: "мар.", + 4: "апр.", + 5: "мая", + 6: "июн.", + 7: "июл.", + 8: "авг.", + 9: "сент.", + 10: "окт.", + 11: "нояб.", + 12: "дек.", + }, + "narrow": { + 1: "Я", + 2: "Ф", + 3: "М", + 4: "А", + 5: "М", + 6: "И", + 7: "И", + 8: "А", + 9: "С", + 10: "О", + 11: "Н", + 12: "Д", + }, + "wide": { + 1: "января", + 2: "февраля", + 3: "марта", + 4: "апреля", + 5: "мая", + 6: "июня", + 7: "июля", + 8: "августа", + 9: "сентября", + 10: "октября", + 11: "ноября", + 12: "декабря", + }, + }, + "units": { + "year": { + "one": "{0} год", + "few": "{0} года", + "many": "{0} лет", + "other": "{0} года", + }, + "month": { + "one": "{0} месяц", + "few": "{0} месяца", + "many": "{0} месяцев", + "other": "{0} месяца", + }, + "week": { + "one": "{0} неделя", + "few": "{0} недели", + "many": "{0} недель", + "other": "{0} недели", + }, + "day": { + "one": "{0} день", + "few": "{0} дня", + "many": "{0} дней", + "other": "{0} дня", + }, + "hour": { + "one": "{0} час", + "few": "{0} часа", + "many": "{0} часов", + "other": "{0} часа", + }, + "minute": { + "one": "{0} минута", + "few": "{0} минуты", + "many": "{0} минут", + "other": "{0} минуты", + }, + "second": { + "one": "{0} секунда", + "few": "{0} секунды", + "many": "{0} секунд", + "other": "{0} секунды", + }, + "microsecond": { + "one": "{0} микросекунда", + "few": "{0} микросекунды", + "many": "{0} микросекунд", + "other": "{0} микросекунды", + }, + }, + "relative": { + "year": { + "future": { + "other": "через {0} года", + "one": "через {0} год", + "few": "через {0} года", + "many": "через {0} лет", + }, + "past": { + "other": "{0} года назад", + "one": "{0} год назад", + "few": "{0} года назад", + "many": "{0} лет назад", + }, + }, + "month": { + "future": { + "other": "через {0} месяца", + "one": "через {0} месяц", + "few": "через {0} месяца", + "many": "через {0} месяцев", + }, + "past": { + "other": "{0} месяца назад", + "one": "{0} месяц назад", + "few": "{0} месяца назад", + "many": "{0} месяцев назад", + }, + }, + "week": { + "future": { + "other": "через {0} недели", + "one": "через {0} неделю", + "few": "через {0} недели", + "many": "через {0} недель", + }, + "past": { + "other": "{0} недели назад", + "one": "{0} неделю назад", + "few": "{0} недели назад", + "many": "{0} недель назад", + }, + }, + "day": { + "future": { + "other": "через {0} дня", + "one": "через {0} день", + "few": "через {0} дня", + "many": "через {0} дней", + }, + "past": { + "other": "{0} дня назад", + "one": "{0} день назад", + "few": "{0} дня назад", + "many": "{0} дней назад", + }, + }, + "hour": { + "future": { + "other": "через {0} часа", + "one": "через {0} час", + "few": "через {0} часа", + "many": "через {0} часов", + }, + "past": { + "other": "{0} часа назад", + "one": "{0} час назад", + "few": "{0} часа назад", + "many": "{0} часов назад", + }, + }, + "minute": { + "future": { + "other": "через {0} минуты", + "one": "через {0} минуту", + "few": "через {0} минуты", + "many": "через {0} минут", + }, + "past": { + "other": "{0} минуты назад", + "one": "{0} минуту назад", + "few": "{0} минуты назад", + "many": "{0} минут назад", + }, + }, + "second": { + "future": { + "other": "через {0} секунды", + "one": "через {0} секунду", + "few": "через {0} секунды", + "many": "через {0} секунд", + }, + "past": { + "other": "{0} секунды назад", + "one": "{0} секунду назад", + "few": "{0} секунды назад", + "many": "{0} секунд назад", + }, + }, + }, + "day_periods": { + "midnight": "полночь", + "am": "AM", + "noon": "полдень", + "pm": "PM", + "morning1": "утра", + "afternoon1": "дня", + "evening1": "вечера", + "night1": "ночи", + }, + }, + "custom": custom_translations, +} diff --git a/pendulum/locales/sk/__init__.py b/pendulum/locales/sk/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pendulum/locales/sk/custom.py b/pendulum/locales/sk/custom.py new file mode 100644 index 0000000..71afb15 --- /dev/null +++ b/pendulum/locales/sk/custom.py @@ -0,0 +1,20 @@ +""" +sk custom locale file. +""" + +translations = { + # Relative time + "ago": "pred {}", + "from_now": "o {}", + "after": "{0} po", + "before": "{0} pred", + # Date formats + "date_formats": { + "LTS": "HH:mm:ss", + "LT": "HH:mm", + "LLLL": "dddd, D. MMMM YYYY HH:mm", + "LLL": "D. MMMM YYYY HH:mm", + "LL": "D. MMMM YYYY", + "L": "DD.MM.YYYY", + }, +} diff --git a/pendulum/locales/sk/locale.py b/pendulum/locales/sk/locale.py new file mode 100644 index 0000000..8d3459f --- /dev/null +++ b/pendulum/locales/sk/locale.py @@ -0,0 +1,266 @@ +from .custom import translations as custom_translations + + +""" +sk locale file. + +It has been generated automatically and must not be modified directly. +""" + + +locale = { + "plural": lambda n: "few" + if ((n == n and (n >= 2 and n <= 4)) and (0 == 0 and (0 == 0))) + else "many" + if (not (0 == 0 and (0 == 0))) + else "one" + if ((n == n and (n == 1)) and (0 == 0 and (0 == 0))) + else "other", + "ordinal": lambda n: "other", + "translations": { + "days": { + "abbreviated": { + 0: "ne", + 1: "po", + 2: "ut", + 3: "st", + 4: "št", + 5: "pi", + 6: "so", + }, + "narrow": { + 0: "n", + 1: "p", + 2: "u", + 3: "s", + 4: "š", + 5: "p", + 6: "s", + }, + "short": { + 0: "ne", + 1: "po", + 2: "ut", + 3: "st", + 4: "št", + 5: "pi", + 6: "so", + }, + "wide": { + 0: "nedeľa", + 1: "pondelok", + 2: "utorok", + 3: "streda", + 4: "štvrtok", + 5: "piatok", + 6: "sobota", + }, + }, + "months": { + "abbreviated": { + 1: "jan", + 2: "feb", + 3: "mar", + 4: "apr", + 5: "máj", + 6: "jún", + 7: "júl", + 8: "aug", + 9: "sep", + 10: "okt", + 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: "januára", + 2: "februára", + 3: "marca", + 4: "apríla", + 5: "mája", + 6: "júna", + 7: "júla", + 8: "augusta", + 9: "septembra", + 10: "októbra", + 11: "novembra", + 12: "decembra", + }, + }, + "units": { + "year": { + "one": "{0} rok", + "few": "{0} roky", + "many": "{0} roka", + "other": "{0} rokov", + }, + "month": { + "one": "{0} mesiac", + "few": "{0} mesiace", + "many": "{0} mesiaca", + "other": "{0} mesiacov", + }, + "week": { + "one": "{0} týždeň", + "few": "{0} týždne", + "many": "{0} týždňa", + "other": "{0} týždňov", + }, + "day": { + "one": "{0} deň", + "few": "{0} dni", + "many": "{0} dňa", + "other": "{0} dní", + }, + "hour": { + "one": "{0} hodina", + "few": "{0} hodiny", + "many": "{0} hodiny", + "other": "{0} hodín", + }, + "minute": { + "one": "{0} minúta", + "few": "{0} minúty", + "many": "{0} minúty", + "other": "{0} minút", + }, + "second": { + "one": "{0} sekunda", + "few": "{0} sekundy", + "many": "{0} sekundy", + "other": "{0} sekúnd", + }, + "microsecond": { + "one": "{0} mikrosekunda", + "few": "{0} mikrosekundy", + "many": "{0} mikrosekundy", + "other": "{0} mikrosekúnd", + }, + }, + "relative": { + "year": { + "future": { + "other": "o {0} rokov", + "one": "o {0} rok", + "few": "o {0} roky", + "many": "o {0} roka", + }, + "past": { + "other": "pred {0} rokmi", + "one": "pred {0} rokom", + "few": "pred {0} rokmi", + "many": "pred {0} roka", + }, + }, + "month": { + "future": { + "other": "o {0} mesiacov", + "one": "o {0} mesiac", + "few": "o {0} mesiace", + "many": "o {0} mesiaca", + }, + "past": { + "other": "pred {0} mesiacmi", + "one": "pred {0} mesiacom", + "few": "pred {0} mesiacmi", + "many": "pred {0} mesiaca", + }, + }, + "week": { + "future": { + "other": "o {0} týždňov", + "one": "o {0} týždeň", + "few": "o {0} týždne", + "many": "o {0} týždňa", + }, + "past": { + "other": "pred {0} týždňami", + "one": "pred {0} týždňom", + "few": "pred {0} týždňami", + "many": "pred {0} týždňa", + }, + }, + "day": { + "future": { + "other": "o {0} dní", + "one": "o {0} deň", + "few": "o {0} dni", + "many": "o {0} dňa", + }, + "past": { + "other": "pred {0} dňami", + "one": "pred {0} dňom", + "few": "pred {0} dňami", + "many": "pred {0} dňa", + }, + }, + "hour": { + "future": { + "other": "o {0} hodín", + "one": "o {0} hodinu", + "few": "o {0} hodiny", + "many": "o {0} hodiny", + }, + "past": { + "other": "pred {0} hodinami", + "one": "pred {0} hodinou", + "few": "pred {0} hodinami", + "many": "pred {0} hodinou", + }, + }, + "minute": { + "future": { + "other": "o {0} minút", + "one": "o {0} minútu", + "few": "o {0} minúty", + "many": "o {0} minúty", + }, + "past": { + "other": "pred {0} minútami", + "one": "pred {0} minútou", + "few": "pred {0} minútami", + "many": "pred {0} minúty", + }, + }, + "second": { + "future": { + "other": "o {0} sekúnd", + "one": "o {0} sekundu", + "few": "o {0} sekundy", + "many": "o {0} sekundy", + }, + "past": { + "other": "pred {0} sekundami", + "one": "pred {0} sekundou", + "few": "pred {0} sekundami", + "many": "pred {0} sekundy", + }, + }, + }, + "day_periods": { + "midnight": "o polnoci", + "am": "AM", + "noon": "napoludnie", + "pm": "PM", + "morning1": "ráno", + "morning2": "dopoludnia", + "afternoon1": "popoludní", + "evening1": "večer", + "night1": "v noci", + }, + }, + "custom": custom_translations, +} diff --git a/pendulum/locales/sv/__init__.py b/pendulum/locales/sv/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pendulum/locales/sv/custom.py b/pendulum/locales/sv/custom.py new file mode 100644 index 0000000..7158f4b --- /dev/null +++ b/pendulum/locales/sv/custom.py @@ -0,0 +1,20 @@ +""" +sv custom locale file. +""" + +translations = { + # Relative time + "ago": "{} sedan", + "from_now": "från nu {}", + "after": "{0} efter", + "before": "{0} innan", + # Date formats + "date_formats": { + "LTS": "HH:mm:ss", + "LT": "HH:mm", + "L": "YYYY-MM-DD", + "LL": "D MMMM YYYY", + "LLL": "D MMMM YYYY, HH:mm", + "LLLL": "dddd, D MMMM YYYY, HH:mm", + }, +} diff --git a/pendulum/locales/sv/locale.py b/pendulum/locales/sv/locale.py new file mode 100644 index 0000000..5b74a6e --- /dev/null +++ b/pendulum/locales/sv/locale.py @@ -0,0 +1,222 @@ +from .custom import translations as custom_translations + + +""" +sv 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: "one" + if ( + ((n % 10) == (n % 10) and (((n % 10) == 1) or ((n % 10) == 2))) + and (not ((n % 100) == (n % 100) and (((n % 100) == 11) or ((n % 100) == 12)))) + ) + else "other", + "translations": { + "days": { + "abbreviated": { + 0: "sön", + 1: "mån", + 2: "tis", + 3: "ons", + 4: "tors", + 5: "fre", + 6: "lör", + }, + "narrow": { + 0: "S", + 1: "M", + 2: "T", + 3: "O", + 4: "T", + 5: "F", + 6: "L", + }, + "short": { + 0: "sö", + 1: "må", + 2: "ti", + 3: "on", + 4: "to", + 5: "fr", + 6: "lö", + }, + "wide": { + 0: "söndag", + 1: "måndag", + 2: "tisdag", + 3: "onsdag", + 4: "torsdag", + 5: "fredag", + 6: "lördag", + }, + }, + "months": { + "abbreviated": { + 1: "jan.", + 2: "feb.", + 3: "mars", + 4: "apr.", + 5: "maj", + 6: "juni", + 7: "juli", + 8: "aug.", + 9: "sep.", + 10: "okt.", + 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: "januari", + 2: "februari", + 3: "mars", + 4: "april", + 5: "maj", + 6: "juni", + 7: "juli", + 8: "augusti", + 9: "september", + 10: "oktober", + 11: "november", + 12: "december", + }, + }, + "units": { + "year": { + "one": "{0} år", + "other": "{0} år", + }, + "month": { + "one": "{0} månad", + "other": "{0} månader", + }, + "week": { + "one": "{0} vecka", + "other": "{0} veckor", + }, + "day": { + "one": "{0} dygn", + "other": "{0} dygn", + }, + "hour": { + "one": "{0} timme", + "other": "{0} timmar", + }, + "minute": { + "one": "{0} minut", + "other": "{0} minuter", + }, + "second": { + "one": "{0} sekund", + "other": "{0} sekunder", + }, + "microsecond": { + "one": "{0} mikrosekund", + "other": "{0} mikrosekunder", + }, + }, + "relative": { + "year": { + "future": { + "other": "om {0} år", + "one": "om {0} år", + }, + "past": { + "other": "för {0} år sedan", + "one": "för {0} år sedan", + }, + }, + "month": { + "future": { + "other": "om {0} månader", + "one": "om {0} månad", + }, + "past": { + "other": "för {0} månader sedan", + "one": "för {0} månad sedan", + }, + }, + "week": { + "future": { + "other": "om {0} veckor", + "one": "om {0} vecka", + }, + "past": { + "other": "för {0} veckor sedan", + "one": "för {0} vecka sedan", + }, + }, + "day": { + "future": { + "other": "om {0} dagar", + "one": "om {0} dag", + }, + "past": { + "other": "för {0} dagar sedan", + "one": "för {0} dag sedan", + }, + }, + "hour": { + "future": { + "other": "om {0} timmar", + "one": "om {0} timme", + }, + "past": { + "other": "för {0} timmar sedan", + "one": "för {0} timme sedan", + }, + }, + "minute": { + "future": { + "other": "om {0} minuter", + "one": "om {0} minut", + }, + "past": { + "other": "för {0} minuter sedan", + "one": "för {0} minut sedan", + }, + }, + "second": { + "future": { + "other": "om {0} sekunder", + "one": "om {0} sekund", + }, + "past": { + "other": "för {0} sekunder sedan", + "one": "för {0} sekund sedan", + }, + }, + }, + "day_periods": { + "midnight": "midnatt", + "am": "fm", + "pm": "em", + "morning1": "på morgonen", + "morning2": "på förmiddagen", + "afternoon1": "på eftermiddagen", + "evening1": "på kvällen", + "night1": "på natten", + }, + }, + "custom": custom_translations, +} diff --git a/pendulum/locales/zh/custom.py b/pendulum/locales/zh/custom.py index 7b35d66..69bc4ca 100644 --- a/pendulum/locales/zh/custom.py +++ b/pendulum/locales/zh/custom.py @@ -1,22 +1,18 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - - -""" -zh custom locale file. -""" - -translations = { - # Relative time - "after": "{time}后", - "before": "{time}前", - # Date formats - "date_formats": { - "LTS": "Ah点m分s秒", - "LT": "Ah点mm分", - "LLLL": "YYYY年MMMD日ddddAh点mm分", - "LLL": "YYYY年MMMD日Ah点mm分", - "LL": "YYYY年MMMD日", - "L": "YYYY-MM-DD", - }, -} +""" +zh custom locale file. +""" + +translations = { + # Relative time + "after": "{time}后", + "before": "{time}前", + # Date formats + "date_formats": { + "LTS": "Ah点m分s秒", + "LT": "Ah点mm分", + "LLLL": "YYYY年MMMD日ddddAh点mm分", + "LLL": "YYYY年MMMD日Ah点mm分", + "LL": "YYYY年MMMD日", + "L": "YYYY-MM-DD", + }, +} diff --git a/pendulum/locales/zh/locale.py b/pendulum/locales/zh/locale.py index ea04fc3..2df477f 100644 --- a/pendulum/locales/zh/locale.py +++ b/pendulum/locales/zh/locale.py @@ -1,116 +1,113 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from .custom import translations as custom_translations - - -""" -zh locale file. - -It has been generated automatically and must not be modified directly. -""" - - -locale = { - "plural": lambda n: "other", - "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: "周六"}, - "wide": { - 0: "星期日", - 1: "星期一", - 2: "星期二", - 3: "星期三", - 4: "星期四", - 5: "星期五", - 6: "星期六", - }, - }, - "months": { - "abbreviated": { - 1: "1月", - 2: "2月", - 3: "3月", - 4: "4月", - 5: "5月", - 6: "6月", - 7: "7月", - 8: "8月", - 9: "9月", - 10: "10月", - 11: "11月", - 12: "12月", - }, - "narrow": { - 1: "1", - 2: "2", - 3: "3", - 4: "4", - 5: "5", - 6: "6", - 7: "7", - 8: "8", - 9: "9", - 10: "10", - 11: "11", - 12: "12", - }, - "wide": { - 1: "一月", - 2: "二月", - 3: "三月", - 4: "四月", - 5: "五月", - 6: "六月", - 7: "七月", - 8: "八月", - 9: "九月", - 10: "十月", - 11: "十一月", - 12: "十二月", - }, - }, - "units": { - "year": {"other": "{0}年"}, - "month": {"other": "{0}个月"}, - "week": {"other": "{0}周"}, - "day": {"other": "{0}天"}, - "hour": {"other": "{0}小时"}, - "minute": {"other": "{0}分钟"}, - "second": {"other": "{0}秒钟"}, - "microsecond": {"other": "{0}微秒"}, - }, - "relative": { - "year": {"future": {"other": "{0}年后"}, "past": {"other": "{0}年前"}}, - "month": {"future": {"other": "{0}个月后"}, "past": {"other": "{0}个月前"}}, - "week": {"future": {"other": "{0}周后"}, "past": {"other": "{0}周前"}}, - "day": {"future": {"other": "{0}天后"}, "past": {"other": "{0}天前"}}, - "hour": {"future": {"other": "{0}小时后"}, "past": {"other": "{0}小时前"}}, - "minute": {"future": {"other": "{0}分钟后"}, "past": {"other": "{0}分钟前"}}, - "second": {"future": {"other": "{0}秒钟后"}, "past": {"other": "{0}秒钟前"}}, - }, - "day_periods": { - "midnight": "午夜", - "am": "上午", - "pm": "下午", - "morning1": "清晨", - "morning2": "上午", - "afternoon1": "下午", - "afternoon2": "下午", - "evening1": "晚上", - "night1": "凌晨", - }, - }, - "custom": custom_translations, -} +from .custom import translations as custom_translations + + +""" +zh locale file. + +It has been generated automatically and must not be modified directly. +""" + + +locale = { + "plural": lambda n: "other", + "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: "周六"}, + "wide": { + 0: "星期日", + 1: "星期一", + 2: "星期二", + 3: "星期三", + 4: "星期四", + 5: "星期五", + 6: "星期六", + }, + }, + "months": { + "abbreviated": { + 1: "1月", + 2: "2月", + 3: "3月", + 4: "4月", + 5: "5月", + 6: "6月", + 7: "7月", + 8: "8月", + 9: "9月", + 10: "10月", + 11: "11月", + 12: "12月", + }, + "narrow": { + 1: "1", + 2: "2", + 3: "3", + 4: "4", + 5: "5", + 6: "6", + 7: "7", + 8: "8", + 9: "9", + 10: "10", + 11: "11", + 12: "12", + }, + "wide": { + 1: "一月", + 2: "二月", + 3: "三月", + 4: "四月", + 5: "五月", + 6: "六月", + 7: "七月", + 8: "八月", + 9: "九月", + 10: "十月", + 11: "十一月", + 12: "十二月", + }, + }, + "units": { + "year": {"other": "{0}年"}, + "month": {"other": "{0}个月"}, + "week": {"other": "{0}周"}, + "day": {"other": "{0}天"}, + "hour": {"other": "{0}小时"}, + "minute": {"other": "{0}分钟"}, + "second": {"other": "{0}秒钟"}, + "microsecond": {"other": "{0}微秒"}, + }, + "relative": { + "year": {"future": {"other": "{0}年后"}, "past": {"other": "{0}年前"}}, + "month": {"future": {"other": "{0}个月后"}, "past": {"other": "{0}个月前"}}, + "week": {"future": {"other": "{0}周后"}, "past": {"other": "{0}周前"}}, + "day": {"future": {"other": "{0}天后"}, "past": {"other": "{0}天前"}}, + "hour": {"future": {"other": "{0}小时后"}, "past": {"other": "{0}小时前"}}, + "minute": {"future": {"other": "{0}分钟后"}, "past": {"other": "{0}分钟前"}}, + "second": {"future": {"other": "{0}秒钟后"}, "past": {"other": "{0}秒钟前"}}, + }, + "day_periods": { + "midnight": "午夜", + "am": "上午", + "pm": "下午", + "morning1": "清晨", + "morning2": "上午", + "afternoon1": "下午", + "afternoon2": "下午", + "evening1": "晚上", + "night1": "凌晨", + }, + }, + "custom": custom_translations, +} diff --git a/pendulum/mixins/__init__.py b/pendulum/mixins/__init__.py index 4c48b5a..e69de29 100644 --- a/pendulum/mixins/__init__.py +++ b/pendulum/mixins/__init__.py @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/pendulum/mixins/default.py b/pendulum/mixins/default.py index bfb5912..59f985e 100644 --- a/pendulum/mixins/default.py +++ b/pendulum/mixins/default.py @@ -1,43 +1,36 @@ -from ..formatting import Formatter - - -_formatter = Formatter() - - -class FormattableMixin(object): - - _formatter = _formatter - - def format(self, fmt, locale=None): - """ - Formats the instance using the given format. - - :param fmt: The format to use - :type fmt: str - - :param locale: The locale to use - :type locale: str or None - - :rtype: str - """ - return self._formatter.format(self, fmt, locale) - - def for_json(self): - """ - Methods for automatic json serialization by simplejson - - :rtype: str - """ - return str(self) - - def __format__(self, format_spec): - if len(format_spec) > 0: - if "%" in format_spec: - return self.strftime(format_spec) - - return self.format(format_spec) - - return str(self) - - def __str__(self): - return self.isoformat() +from __future__ import annotations + +from pendulum.formatting import Formatter + +_formatter = Formatter() + + +class FormattableMixin: + _formatter: Formatter = _formatter + + def format(self, fmt: str, locale: str | None = None) -> str: + """ + Formats the instance using the given format. + + :param fmt: The format to use + :param locale: The locale to use + """ + return self._formatter.format(self, fmt, locale) + + def for_json(self) -> str: + """ + Methods for automatic json serialization by simplejson. + """ + return str(self) + + def __format__(self, format_spec: str) -> str: + if len(format_spec) > 0: + if "%" in format_spec: + return self.strftime(format_spec) + + return self.format(format_spec) + + return str(self) + + def __str__(self) -> str: + return self.isoformat() diff --git a/pendulum/parser.py b/pendulum/parser.py index 9b9e383..77900e2 100644 --- a/pendulum/parser.py +++ b/pendulum/parser.py @@ -1,121 +1,124 @@ -from __future__ import absolute_import - -import datetime -import typing - -import pendulum - -from .date import Date -from .datetime import DateTime -from .parsing import _Interval -from .parsing import parse as base_parse -from .time import Duration -from .time import Time -from .tz import UTC - - -try: - from .parsing._iso8601 import Duration as CDuration -except ImportError: - CDuration = None - - -def parse( - text, **options -): # type: (str, **typing.Any) -> typing.Union[Date, Time, DateTime, Duration] - # Use the mock now value if it exists - options["now"] = options.get("now", pendulum.get_test_now()) - - return _parse(text, **options) - - -def _parse(text, **options): - """ - Parses a string with the given options. - - :param text: The string to parse. - :type text: str - - :rtype: mixed - """ - # Handling special cases - if text == "now": - return pendulum.now() - - parsed = base_parse(text, **options) - - if isinstance(parsed, datetime.datetime): - return pendulum.datetime( - parsed.year, - parsed.month, - parsed.day, - parsed.hour, - parsed.minute, - parsed.second, - parsed.microsecond, - tz=parsed.tzinfo or options.get("tz", UTC), - ) - - if isinstance(parsed, datetime.date): - return pendulum.date(parsed.year, parsed.month, parsed.day) - - if isinstance(parsed, datetime.time): - return pendulum.time( - parsed.hour, parsed.minute, parsed.second, parsed.microsecond - ) - - if isinstance(parsed, _Interval): - if parsed.duration is not None: - duration = parsed.duration - - if parsed.start is not None: - dt = pendulum.instance(parsed.start, tz=options.get("tz", UTC)) - - return pendulum.period( - dt, - dt.add( - years=duration.years, - months=duration.months, - weeks=duration.weeks, - days=duration.remaining_days, - hours=duration.hours, - minutes=duration.minutes, - seconds=duration.remaining_seconds, - microseconds=duration.microseconds, - ), - ) - - dt = pendulum.instance(parsed.end, tz=options.get("tz", UTC)) - - return pendulum.period( - dt.subtract( - years=duration.years, - months=duration.months, - weeks=duration.weeks, - days=duration.remaining_days, - hours=duration.hours, - minutes=duration.minutes, - seconds=duration.remaining_seconds, - microseconds=duration.microseconds, - ), - dt, - ) - - return pendulum.period( - pendulum.instance(parsed.start, tz=options.get("tz", UTC)), - pendulum.instance(parsed.end, tz=options.get("tz", UTC)), - ) - - if CDuration and isinstance(parsed, CDuration): - return pendulum.duration( - years=parsed.years, - months=parsed.months, - weeks=parsed.weeks, - days=parsed.days, - hours=parsed.hours, - minutes=parsed.minutes, - seconds=parsed.seconds, - microseconds=parsed.microseconds, - ) - - return parsed +from __future__ import annotations + +import datetime +import typing as t + +import pendulum + +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 +except ImportError: + CDuration = None # type: ignore[misc, assignment] + + +def parse(text: str, **options: t.Any) -> Date | Time | DateTime | Duration: + # Use the mock now value if it exists + options["now"] = options.get("now") + + return _parse(text, **options) + + +def _parse(text: str, **options: t.Any) -> Date | DateTime | Time | Duration | Interval: + """ + Parses a string with the given options. + + :param text: The string to parse. + """ + # Handling special cases + if text == "now": + return pendulum.now() + + parsed = base_parse(text, **options) + + if isinstance(parsed, datetime.datetime): + return pendulum.datetime( + parsed.year, + parsed.month, + parsed.day, + parsed.hour, + parsed.minute, + parsed.second, + parsed.microsecond, + tz=parsed.tzinfo or options.get("tz", UTC), + ) + + if isinstance(parsed, datetime.date): + return pendulum.date(parsed.year, parsed.month, parsed.day) + + if isinstance(parsed, datetime.time): + return pendulum.time( + parsed.hour, parsed.minute, parsed.second, parsed.microsecond + ) + + if isinstance(parsed, _Interval): + if parsed.duration is not None: + duration = parsed.duration + + if parsed.start is not None: + dt = pendulum.instance(parsed.start, tz=options.get("tz", UTC)) + + return pendulum.interval( + dt, + dt.add( + years=duration.years, + months=duration.months, + weeks=duration.weeks, + days=duration.remaining_days, + hours=duration.hours, + minutes=duration.minutes, + seconds=duration.remaining_seconds, + microseconds=duration.microseconds, + ), + ) + + dt = pendulum.instance( + t.cast(datetime.datetime, parsed.end), tz=options.get("tz", UTC) + ) + + return pendulum.interval( + dt.subtract( + years=duration.years, + months=duration.months, + weeks=duration.weeks, + days=duration.remaining_days, + hours=duration.hours, + minutes=duration.minutes, + seconds=duration.remaining_seconds, + microseconds=duration.microseconds, + ), + dt, + ) + + return pendulum.interval( + pendulum.instance( + t.cast(datetime.datetime, parsed.start), tz=options.get("tz", UTC) + ), + pendulum.instance( + t.cast(datetime.datetime, parsed.end), tz=options.get("tz", UTC) + ), + ) + + if CDuration and isinstance(parsed, CDuration): + return pendulum.duration( + years=parsed.years, + months=parsed.months, + weeks=parsed.weeks, + days=parsed.days, + hours=parsed.hours, + minutes=parsed.minutes, + seconds=parsed.seconds, + microseconds=parsed.microseconds, + ) + + return parsed diff --git a/pendulum/parsing/__init__.py b/pendulum/parsing/__init__.py index 400f119..0e64065 100644 --- a/pendulum/parsing/__init__.py +++ b/pendulum/parsing/__init__.py @@ -1,234 +1,233 @@ -import copy -import os -import re -import struct - -from datetime import date -from datetime import datetime -from datetime import time - -from dateutil import parser - -from .exceptions import ParserError - - -with_extensions = os.getenv("PENDULUM_EXTENSIONS", "1") == "1" - -try: - if not with_extensions or struct.calcsize("P") == 4: - raise ImportError() - - from ._iso8601 import parse_iso8601 -except ImportError: - from .iso8601 import parse_iso8601 - - -COMMON = re.compile( - # Date (optional) - "^" - "(?P" - " (?P" # Classic date (YYYY-MM-DD) - r" (?P\d{4})" # Year - " (?P" - r" (?P[/:])?(?P\d{2})" # Month (optional) - r" ((?P[/:])?(?P\d{2}))" # Day (optional) - " )?" - " )" - ")?" - # Time (optional) - "(?P