pax_global_header00006660000000000000000000000064141510012360014503gustar00rootroot0000000000000052 comment=d872459ff338f76e4f7a9bef67607054656822cc python-arrow-1.2.1/000077500000000000000000000000001415100123600141555ustar00rootroot00000000000000python-arrow-1.2.1/.github/000077500000000000000000000000001415100123600155155ustar00rootroot00000000000000python-arrow-1.2.1/.github/FUNDING.yml000066400000000000000000000000271415100123600173310ustar00rootroot00000000000000open_collective: arrow python-arrow-1.2.1/.github/ISSUE_TEMPLATE/000077500000000000000000000000001415100123600177005ustar00rootroot00000000000000python-arrow-1.2.1/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000013571415100123600224000ustar00rootroot00000000000000--- name: "🐞 Bug Report" about: Find a bug? Create a report to help us improve. title: '' labels: 'bug' assignees: '' --- ## Issue Description ## System Info - 🖥 **OS name and version**: - 🐍 **Python version**: - 🏹 **Arrow version**: python-arrow-1.2.1/.github/ISSUE_TEMPLATE/documentation.md000066400000000000000000000006331415100123600230750ustar00rootroot00000000000000--- name: "📚 Documentation" about: Find errors or problems in the docs (https://arrow.readthedocs.io)? title: '' labels: 'documentation' assignees: '' --- ## Issue Description python-arrow-1.2.1/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000006061415100123600234270ustar00rootroot00000000000000--- name: "💡 Feature Request" about: Have an idea for a new feature or improvement? title: '' labels: 'enhancement' assignees: '' --- ## Feature Request python-arrow-1.2.1/.github/pull_request_template.md000066400000000000000000000021551415100123600224610ustar00rootroot00000000000000## Pull Request Checklist Thank you for taking the time to improve Arrow! Before submitting your pull request, please check all *appropriate* boxes: - [ ] 🧪 Added **tests** for changed code. - [ ] 🛠️ All tests **pass** when run locally (run `tox` or `make test` to find out!). - [ ] 🧹 All linting checks **pass** when run locally (run `tox -e lint` or `make lint` to find out!). - [ ] 📚 Updated **documentation** for changed code. - [ ] ⏩ Code is **up-to-date** with the `master` branch. If you have *any* questions about your code changes or any of the points above, please submit your questions along with the pull request and we will try our best to help! ## Description of Changes python-arrow-1.2.1/.github/workflows/000077500000000000000000000000001415100123600175525ustar00rootroot00000000000000python-arrow-1.2.1/.github/workflows/continuous_integration.yml000066400000000000000000000063361415100123600251160ustar00rootroot00000000000000name: tests on: pull_request: # Run on all pull requests push: # Run only on pushes to master branches: - master schedule: # Run monthly - cron: "0 0 1 * *" jobs: test: name: ${{ matrix.os }} (${{ matrix.python-version }}) runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: python-version: ["pypy-3.7", "3.6", "3.7", "3.8", "3.9", "3.10"] os: [ubuntu-latest, macos-latest, windows-latest] exclude: # pypy3 randomly fails on Windows builds - os: windows-latest python-version: "pypy-3.7" steps: # Check out latest code - uses: actions/checkout@v2 # Configure pip cache - name: Cache pip (Linux) uses: actions/cache@v2 if: startsWith(runner.os, 'Linux') with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements-dev.txt') }} restore-keys: | ${{ runner.os }}-pip- - name: Cache pip (macOS) uses: actions/cache@v2 if: startsWith(runner.os, 'macOS') with: path: ~/Library/Caches/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements-dev.txt') }} restore-keys: | ${{ runner.os }}-pip- - name: Cache pip (Windows) uses: actions/cache@v2 if: startsWith(runner.os, 'Windows') with: path: ~\AppData\Local\pip\Cache key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements-dev.txt') }} restore-keys: | ${{ runner.os }}-pip- # Set up Python - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} # Install dependencies - name: Install dependencies run: | pip install -U pip setuptools wheel pip install -U tox tox-gh-actions # Run tests - name: Test with tox run: tox # Upload coverage report - name: Upload coverage to Codecov uses: codecov/codecov-action@v2 with: file: coverage.xml lint: runs-on: ubuntu-latest steps: # Check out latest code - uses: actions/checkout@v2 # Set up Python - name: Set up Python 3.10 uses: actions/setup-python@v2 with: python-version: "3.10" # Configure pip cache - name: Cache pip uses: actions/cache@v2 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements-dev.txt') }} restore-keys: | ${{ runner.os }}-pip- # Configure pre-commit cache - name: Cache pre-commit uses: actions/cache@v2 with: path: ~/.cache/pre-commit key: ${{ runner.os }}-pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }} restore-keys: | ${{ runner.os }}-pre-commit- # Install dependencies - name: Install dependencies run: | pip install -U pip setuptools wheel pip install -U tox # Lint code - name: Lint code run: tox -e lint # Lint docs - name: Lint docs run: tox -e docs python-arrow-1.2.1/.pre-commit-config.yaml000066400000000000000000000025671415100123600204500ustar00rootroot00000000000000default_language_version: python: python3 repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.0.1 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: fix-encoding-pragma args: [--remove] - id: requirements-txt-fixer - id: check-ast - id: check-yaml - id: check-case-conflict - id: check-docstring-first - id: check-merge-conflict - id: debug-statements - repo: https://github.com/timothycrosley/isort rev: 5.9.3 hooks: - id: isort - repo: https://github.com/asottile/pyupgrade rev: v2.29.0 hooks: - id: pyupgrade args: [--py36-plus] - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.9.0 hooks: - id: python-no-eval - id: python-check-blanket-noqa - id: python-use-type-annotations - id: rst-backticks - id: rst-directive-colons - id: rst-inline-touching-normal - repo: https://github.com/psf/black rev: 21.9b0 hooks: - id: black args: [--safe, --quiet, --target-version=py36] - repo: https://github.com/pycqa/flake8 rev: 4.0.1 hooks: - id: flake8 additional_dependencies: [flake8-bugbear] - repo: https://github.com/pre-commit/mirrors-mypy rev: 'v0.910-1' hooks: - id: mypy additional_dependencies: [types-python-dateutil] python-arrow-1.2.1/CHANGELOG.rst000066400000000000000000000752621415100123600162120ustar00rootroot00000000000000Changelog ========= 1.2.1 (2021-10-24) ------------------ - [NEW] Added quarter granularity to humanize, for example: .. code-block:: python >>> import arrow >>> now = arrow.now() >>> four_month_shift = now.shift(months=4) >>> now.humanize(four_month_shift, granularity="quarter") 'a quarter ago' >>> four_month_shift.humanize(now, granularity="quarter") 'in a quarter' >>> thirteen_month_shift = now.shift(months=13) >>> thirteen_month_shift.humanize(now, granularity="quarter") 'in 4 quarters' >>> now.humanize(thirteen_month_shift, granularity="quarter") '4 quarters ago' - [NEW] Added Sinhala and Urdu locales. - [NEW] Added official support for Python 3.10. - [CHANGED] Updated Azerbaijani, Hebrew, and Serbian locales and added tests. - [CHANGED] Passing an empty granularity list to ``humanize`` now raises a ``ValueError``. 1.2.0 (2021-09-12) ------------------ - [NEW] Added Albanian, Tamil and Zulu locales. - [NEW] Added support for ``Decimal`` as input to ``arrow.get()``. - [FIX] The Estonian, Finnish, Nepali and Zulu locales now support ``dehumanize``. - [FIX] Improved validation checks when using parser tokens ``A`` and ``hh``. - [FIX] Minor bug fixes to Catalan, Cantonese, Greek and Nepali locales. 1.1.1 (2021-06-24) ------------------ - [NEW] Added Odia, Maltese, Serbian, Sami, and Luxembourgish locales. - [FIXED] All calls to ``arrow.get()`` should now properly pass the ``tzinfo`` argument to the Arrow constructor. See PR `#968 `_ for more info. - [FIXED] Humanize output is now properly truncated when a locale class overrides ``_format_timeframe()``. - [CHANGED] Renamed ``requirements.txt`` to ``requirements-dev.txt`` to prevent confusion with the dependencies in ``setup.py``. - [CHANGED] Updated Turkish locale and added tests. 1.1.0 (2021-04-26) ------------------ - [NEW] Implemented the ``dehumanize`` method for ``Arrow`` objects. This takes human readable input and uses it to perform relative time shifts, for example: .. code-block:: python >>> arw >>> arw.dehumanize("8 hours ago") >>> arw.dehumanize("in 4 days") >>> arw.dehumanize("in an hour 34 minutes 10 seconds") >>> arw.dehumanize("hace 2 años", locale="es") - [NEW] Made the start of the week adjustable when using ``span("week")``, for example: .. code-block:: python >>> arw >>> arw.isoweekday() 1 # Monday >>> arw.span("week") (, ) >>> arw.span("week", week_start=4) (, ) - [NEW] Added Croatian, Latin, Latvian, Lithuanian and Malay locales. - [FIX] Internally standardize locales and improve locale validation. Locales should now use the ISO notation of a dash (``"en-gb"``) rather than an underscore (``"en_gb"``) however this change is backward compatible. - [FIX] Correct type checking for internal locale mapping by using ``_init_subclass``. This now allows subclassing of locales, for example: .. code-block:: python >>> from arrow.locales import EnglishLocale >>> class Klingon(EnglishLocale): ... names = ["tlh"] ... >>> from arrow import locales >>> locales.get_locale("tlh") <__main__.Klingon object at 0x7f7cd1effd30> - [FIX] Correct type checking for ``arrow.get(2021, 3, 9)`` construction. - [FIX] Audited all docstrings for style, typos and outdated info. 1.0.3 (2021-03-05) ------------------ - [FIX] Updated internals to avoid issues when running ``mypy --strict``. - [FIX] Corrections to Swedish locale. - [INTERNAL] Lowered required coverage limit until ``humanize`` month tests are fixed. 1.0.2 (2021-02-28) ------------------ - [FIXED] Fixed an ``OverflowError`` that could occur when running Arrow on a 32-bit OS. 1.0.1 (2021-02-27) ------------------ - [FIXED] A ``py.typed`` file is now bundled with the Arrow package to conform to PEP 561. 1.0.0 (2021-02-26) ------------------ After 8 years we're pleased to announce Arrow v1.0. Thanks to the entire Python community for helping make Arrow the amazing package it is today! - [CHANGE] Arrow has **dropped support** for Python 2.7 and 3.5. - [CHANGE] There are multiple **breaking changes** with this release, please see the `migration guide `_ for a complete overview. - [CHANGE] Arrow is now following `semantic versioning `_. - [CHANGE] Made ``humanize`` granularity="auto" limits more accurate to reduce strange results. - [NEW] Added support for Python 3.9. - [NEW] Added a new keyword argument "exact" to ``span``, ``span_range`` and ``interval`` methods. This makes timespans begin at the start time given and not extend beyond the end time given, for example: .. code-block:: python >>> start = Arrow(2021, 2, 5, 12, 30) >>> end = Arrow(2021, 2, 5, 17, 15) >>> for r in arrow.Arrow.span_range('hour', start, end, exact=True): ... print(r) ... (, ) (, ) (, ) (, ) (, ) - [NEW] Arrow now natively supports PEP 484-style type annotations. - [FIX] Fixed handling of maximum permitted timestamp on Windows systems. - [FIX] Corrections to French, German, Japanese and Norwegian locales. - [INTERNAL] Raise more appropriate errors when string parsing fails to match. 0.17.0 (2020-10-2) ------------------- - [WARN] Arrow will **drop support** for Python 2.7 and 3.5 in the upcoming 1.0.0 release. This is the last major release to support Python 2.7 and Python 3.5. - [NEW] Arrow now properly handles imaginary datetimes during DST shifts. For example: .. code-block:: python >>> just_before = arrow.get(2013, 3, 31, 1, 55, tzinfo="Europe/Paris") >>> just_before.shift(minutes=+10) .. code-block:: python >>> before = arrow.get("2018-03-10 23:00:00", "YYYY-MM-DD HH:mm:ss", tzinfo="US/Pacific") >>> after = arrow.get("2018-03-11 04:00:00", "YYYY-MM-DD HH:mm:ss", tzinfo="US/Pacific") >>> result=[(t, t.to("utc")) for t in arrow.Arrow.range("hour", before, after)] >>> for r in result: ... print(r) ... (, ) (, ) (, ) (, ) (, ) - [NEW] Added ``humanize`` week granularity translation for Tagalog. - [CHANGE] Calls to the ``timestamp`` property now emit a ``DeprecationWarning``. In a future release, ``timestamp`` will be changed to a method to align with Python's datetime module. If you would like to continue using the property, please change your code to use the ``int_timestamp`` or ``float_timestamp`` properties instead. - [CHANGE] Expanded and improved Catalan locale. - [FIX] Fixed a bug that caused ``Arrow.range()`` to incorrectly cut off ranges in certain scenarios when using month, quarter, or year endings. - [FIX] Fixed a bug that caused day of week token parsing to be case sensitive. - [INTERNAL] A number of functions were reordered in arrow.py for better organization and grouping of related methods. This change will have no impact on usage. - [INTERNAL] A minimum tox version is now enforced for compatibility reasons. Contributors must use tox >3.18.0 going forward. 0.16.0 (2020-08-23) ------------------- - [WARN] Arrow will **drop support** for Python 2.7 and 3.5 in the upcoming 1.0.0 release. The 0.16.x and 0.17.x releases are the last to support Python 2.7 and 3.5. - [NEW] Implemented `PEP 495 `_ to handle ambiguous datetimes. This is achieved by the addition of the ``fold`` attribute for Arrow objects. For example: .. code-block:: python >>> before = Arrow(2017, 10, 29, 2, 0, tzinfo='Europe/Stockholm') >>> before.fold 0 >>> before.ambiguous True >>> after = Arrow(2017, 10, 29, 2, 0, tzinfo='Europe/Stockholm', fold=1) >>> after = before.replace(fold=1) - [NEW] Added ``normalize_whitespace`` flag to ``arrow.get``. This is useful for parsing log files and/or any files that may contain inconsistent spacing. For example: .. code-block:: python >>> arrow.get("Jun 1 2005 1:33PM", "MMM D YYYY H:mmA", normalize_whitespace=True) >>> arrow.get("2013-036 \t 04:05:06Z", normalize_whitespace=True) 0.15.8 (2020-07-23) ------------------- - [WARN] Arrow will **drop support** for Python 2.7 and 3.5 in the upcoming 1.0.0 release. The 0.15.x, 0.16.x, and 0.17.x releases are the last to support Python 2.7 and 3.5. - [NEW] Added ``humanize`` week granularity translation for Czech. - [FIX] ``arrow.get`` will now pick sane defaults when weekdays are passed with particular token combinations, see `#446 `_. - [INTERNAL] Moved arrow to an organization. The repo can now be found `here `_. - [INTERNAL] Started issuing deprecation warnings for Python 2.7 and 3.5. - [INTERNAL] Added Python 3.9 to CI pipeline. 0.15.7 (2020-06-19) ------------------- - [NEW] Added a number of built-in format strings. See the `docs `_ for a complete list of supported formats. For example: .. code-block:: python >>> arw = arrow.utcnow() >>> arw.format(arrow.FORMAT_COOKIE) 'Wednesday, 27-May-2020 10:30:35 UTC' - [NEW] Arrow is now fully compatible with Python 3.9 and PyPy3. - [NEW] Added Makefile, tox.ini, and requirements.txt files to the distribution bundle. - [NEW] Added French Canadian and Swahili locales. - [NEW] Added ``humanize`` week granularity translation for Hebrew, Greek, Macedonian, Swedish, Slovak. - [FIX] ms and μs timestamps are now normalized in ``arrow.get()``, ``arrow.fromtimestamp()``, and ``arrow.utcfromtimestamp()``. For example: .. code-block:: python >>> ts = 1591161115194556 >>> arw = arrow.get(ts) >>> arw.timestamp 1591161115 - [FIX] Refactored and updated Macedonian, Hebrew, Korean, and Portuguese locales. 0.15.6 (2020-04-29) ------------------- - [NEW] Added support for parsing and formatting `ISO 8601 week dates `_ via a new token ``W``, for example: .. code-block:: python >>> arrow.get("2013-W29-6", "W") >>> utc=arrow.utcnow() >>> utc >>> utc.format("W") '2020-W04-4' - [NEW] Formatting with ``x`` token (microseconds) is now possible, for example: .. code-block:: python >>> dt = arrow.utcnow() >>> dt.format("x") '1585669870688329' >>> dt.format("X") '1585669870' - [NEW] Added ``humanize`` week granularity translation for German, Italian, Polish & Taiwanese locales. - [FIX] Consolidated and simplified German locales. - [INTERNAL] Moved testing suite from nosetest/Chai to pytest/pytest-mock. - [INTERNAL] Converted xunit-style setup and teardown functions in tests to pytest fixtures. - [INTERNAL] Setup Github Actions for CI alongside Travis. - [INTERNAL] Help support Arrow's future development by donating to the project on `Open Collective `_. 0.15.5 (2020-01-03) ------------------- - [WARN] Python 2 reached EOL on 2020-01-01. arrow will **drop support** for Python 2 in a future release to be decided (see `#739 `_). - [NEW] Added bounds parameter to ``span_range``, ``interval`` and ``span`` methods. This allows you to include or exclude the start and end values. - [NEW] ``arrow.get()`` can now create arrow objects from a timestamp with a timezone, for example: .. code-block:: python >>> arrow.get(1367900664, tzinfo=tz.gettz('US/Pacific')) - [NEW] ``humanize`` can now combine multiple levels of granularity, for example: .. code-block:: python >>> later140 = arrow.utcnow().shift(seconds=+8400) >>> later140.humanize(granularity="minute") 'in 139 minutes' >>> later140.humanize(granularity=["hour", "minute"]) 'in 2 hours and 19 minutes' - [NEW] Added Hong Kong locale (``zh_hk``). - [NEW] Added ``humanize`` week granularity translation for Dutch. - [NEW] Numbers are now displayed when using the seconds granularity in ``humanize``. - [CHANGE] ``range`` now supports both the singular and plural forms of the ``frames`` argument (e.g. day and days). - [FIX] Improved parsing of strings that contain punctuation. - [FIX] Improved behaviour of ``humanize`` when singular seconds are involved. 0.15.4 (2019-11-02) ------------------- - [FIX] Fixed an issue that caused package installs to fail on Conda Forge. 0.15.3 (2019-11-02) ------------------- - [NEW] ``factory.get()`` can now create arrow objects from a ISO calendar tuple, for example: .. code-block:: python >>> arrow.get((2013, 18, 7)) - [NEW] Added a new token ``x`` to allow parsing of integer timestamps with milliseconds and microseconds. - [NEW] Formatting now supports escaping of characters using the same syntax as parsing, for example: .. code-block:: python >>> arw = arrow.now() >>> fmt = "YYYY-MM-DD h [h] m" >>> arw.format(fmt) '2019-11-02 3 h 32' - [NEW] Added ``humanize`` week granularity translations for Chinese, Spanish and Vietnamese. - [CHANGE] Added ``ParserError`` to module exports. - [FIX] Added support for midnight at end of day. See `#703 `_ for details. - [INTERNAL] Created Travis build for macOS. - [INTERNAL] Test parsing and formatting against full timezone database. 0.15.2 (2019-09-14) ------------------- - [NEW] Added ``humanize`` week granularity translations for Portuguese and Brazilian Portuguese. - [NEW] Embedded changelog within docs and added release dates to versions. - [FIX] Fixed a bug that caused test failures on Windows only, see `#668 `_ for details. 0.15.1 (2019-09-10) ------------------- - [NEW] Added ``humanize`` week granularity translations for Japanese. - [FIX] Fixed a bug that caused Arrow to fail when passed a negative timestamp string. - [FIX] Fixed a bug that caused Arrow to fail when passed a datetime object with ``tzinfo`` of type ``StaticTzInfo``. 0.15.0 (2019-09-08) ------------------- - [NEW] Added support for DDD and DDDD ordinal date tokens. The following functionality is now possible: ``arrow.get("1998-045")``, ``arrow.get("1998-45", "YYYY-DDD")``, ``arrow.get("1998-045", "YYYY-DDDD")``. - [NEW] ISO 8601 basic format for dates and times is now supported (e.g. ``YYYYMMDDTHHmmssZ``). - [NEW] Added ``humanize`` week granularity translations for French, Russian and Swiss German locales. - [CHANGE] Timestamps of type ``str`` are no longer supported **without a format string** in the ``arrow.get()`` method. This change was made to support the ISO 8601 basic format and to address bugs such as `#447 `_. The following will NOT work in v0.15.0: .. code-block:: python >>> arrow.get("1565358758") >>> arrow.get("1565358758.123413") The following will work in v0.15.0: .. code-block:: python >>> arrow.get("1565358758", "X") >>> arrow.get("1565358758.123413", "X") >>> arrow.get(1565358758) >>> arrow.get(1565358758.123413) - [CHANGE] When a meridian token (a|A) is passed and no meridians are available for the specified locale (e.g. unsupported or untranslated) a ``ParserError`` is raised. - [CHANGE] The timestamp token (``X``) will now match float timestamps of type ``str``: ``arrow.get(“1565358758.123415”, “X”)``. - [CHANGE] Strings with leading and/or trailing whitespace will no longer be parsed without a format string. Please see `the docs `_ for ways to handle this. - [FIX] The timestamp token (``X``) will now only match on strings that **strictly contain integers and floats**, preventing incorrect matches. - [FIX] Most instances of ``arrow.get()`` returning an incorrect ``Arrow`` object from a partial parsing match have been eliminated. The following issue have been addressed: `#91 `_, `#196 `_, `#396 `_, `#434 `_, `#447 `_, `#456 `_, `#519 `_, `#538 `_, `#560 `_. 0.14.7 (2019-09-04) ------------------- - [CHANGE] ``ArrowParseWarning`` will no longer be printed on every call to ``arrow.get()`` with a datetime string. The purpose of the warning was to start a conversation about the upcoming 0.15.0 changes and we appreciate all the feedback that the community has given us! 0.14.6 (2019-08-28) ------------------- - [NEW] Added support for ``week`` granularity in ``Arrow.humanize()``. For example, ``arrow.utcnow().shift(weeks=-1).humanize(granularity="week")`` outputs "a week ago". This change introduced two new untranslated words, ``week`` and ``weeks``, to all locale dictionaries, so locale contributions are welcome! - [NEW] Fully translated the Brazilian Portuguese locale. - [CHANGE] Updated the Macedonian locale to inherit from a Slavic base. - [FIX] Fixed a bug that caused ``arrow.get()`` to ignore tzinfo arguments of type string (e.g. ``arrow.get(tzinfo="Europe/Paris")``). - [FIX] Fixed a bug that occurred when ``arrow.Arrow()`` was instantiated with a ``pytz`` tzinfo object. - [FIX] Fixed a bug that caused Arrow to fail when passed a sub-second token, that when rounded, had a value greater than 999999 (e.g. ``arrow.get("2015-01-12T01:13:15.9999995")``). Arrow should now accurately propagate the rounding for large sub-second tokens. 0.14.5 (2019-08-09) ------------------- - [NEW] Added Afrikaans locale. - [CHANGE] Removed deprecated ``replace`` shift functionality. Users looking to pass plural properties to the ``replace`` function to shift values should use ``shift`` instead. - [FIX] Fixed bug that occurred when ``factory.get()`` was passed a locale kwarg. 0.14.4 (2019-07-30) ------------------- - [FIX] Fixed a regression in 0.14.3 that prevented a tzinfo argument of type string to be passed to the ``get()`` function. Functionality such as ``arrow.get("2019072807", "YYYYMMDDHH", tzinfo="UTC")`` should work as normal again. - [CHANGE] Moved ``backports.functools_lru_cache`` dependency from ``extra_requires`` to ``install_requires`` for ``Python 2.7`` installs to fix `#495 `_. 0.14.3 (2019-07-28) ------------------- - [NEW] Added full support for Python 3.8. - [CHANGE] Added warnings for upcoming factory.get() parsing changes in 0.15.0. Please see `#612 `_ for full details. - [FIX] Extensive refactor and update of documentation. - [FIX] factory.get() can now construct from kwargs. - [FIX] Added meridians to Spanish Locale. 0.14.2 (2019-06-06) ------------------- - [CHANGE] Travis CI builds now use tox to lint and run tests. - [FIX] Fixed UnicodeDecodeError on certain locales (#600). 0.14.1 (2019-06-06) ------------------- - [FIX] Fixed ``ImportError: No module named 'dateutil'`` (#598). 0.14.0 (2019-06-06) ------------------- - [NEW] Added provisional support for Python 3.8. - [CHANGE] Removed support for EOL Python 3.4. - [FIX] Updated setup.py with modern Python standards. - [FIX] Upgraded dependencies to latest versions. - [FIX] Enabled flake8 and black on travis builds. - [FIX] Formatted code using black and isort. 0.13.2 (2019-05-30) ------------------- - [NEW] Add is_between method. - [FIX] Improved humanize behaviour for near zero durations (#416). - [FIX] Correct humanize behaviour with future days (#541). - [FIX] Documentation updates. - [FIX] Improvements to German Locale. 0.13.1 (2019-02-17) ------------------- - [NEW] Add support for Python 3.7. - [CHANGE] Remove deprecation decorators for Arrow.range(), Arrow.span_range() and Arrow.interval(), all now return generators, wrap with list() to get old behavior. - [FIX] Documentation and docstring updates. 0.13.0 (2019-01-09) ------------------- - [NEW] Added support for Python 3.6. - [CHANGE] Drop support for Python 2.6/3.3. - [CHANGE] Return generator instead of list for Arrow.range(), Arrow.span_range() and Arrow.interval(). - [FIX] Make arrow.get() work with str & tzinfo combo. - [FIX] Make sure special RegEx characters are escaped in format string. - [NEW] Added support for ZZZ when formatting. - [FIX] Stop using datetime.utcnow() in internals, use datetime.now(UTC) instead. - [FIX] Return NotImplemented instead of TypeError in arrow math internals. - [NEW] Added Estonian Locale. - [FIX] Small fixes to Greek locale. - [FIX] TagalogLocale improvements. - [FIX] Added test requirements to setup. - [FIX] Improve docs for get, now and utcnow methods. - [FIX] Correct typo in depreciation warning. 0.12.1 ------ - [FIX] Allow universal wheels to be generated and reliably installed. - [FIX] Make humanize respect only_distance when granularity argument is also given. 0.12.0 ------ - [FIX] Compatibility fix for Python 2.x 0.11.0 ------ - [FIX] Fix grammar of ArabicLocale - [NEW] Add Nepali Locale - [FIX] Fix month name + rename AustriaLocale -> AustrianLocale - [FIX] Fix typo in Basque Locale - [FIX] Fix grammar in PortugueseBrazilian locale - [FIX] Remove pip --user-mirrors flag - [NEW] Add Indonesian Locale 0.10.0 ------ - [FIX] Fix getattr off by one for quarter - [FIX] Fix negative offset for UTC - [FIX] Update arrow.py 0.9.0 ----- - [NEW] Remove duplicate code - [NEW] Support gnu date iso 8601 - [NEW] Add support for universal wheels - [NEW] Slovenian locale - [NEW] Slovak locale - [NEW] Romanian locale - [FIX] respect limit even if end is defined range - [FIX] Separate replace & shift functions - [NEW] Added tox - [FIX] Fix supported Python versions in documentation - [NEW] Azerbaijani locale added, locale issue fixed in Turkish. - [FIX] Format ParserError's raise message 0.8.0 ----- - [] 0.7.1 ----- - [NEW] Esperanto locale (batisteo) 0.7.0 ----- - [FIX] Parse localized strings #228 (swistakm) - [FIX] Modify tzinfo parameter in ``get`` api #221 (bottleimp) - [FIX] Fix Czech locale (PrehistoricTeam) - [FIX] Raise TypeError when adding/subtracting non-dates (itsmeolivia) - [FIX] Fix pytz conversion error (Kudo) - [FIX] Fix overzealous time truncation in span_range (kdeldycke) - [NEW] Humanize for time duration #232 (ybrs) - [NEW] Add Thai locale (sipp11) - [NEW] Adding Belarusian (be) locale (oire) - [NEW] Search date in strings (beenje) - [NEW] Note that arrow's tokens differ from strptime's. (offby1) 0.6.0 ----- - [FIX] Added support for Python 3 - [FIX] Avoid truncating oversized epoch timestamps. Fixes #216. - [FIX] Fixed month abbreviations for Ukrainian - [FIX] Fix typo timezone - [FIX] A couple of dialect fixes and two new languages - [FIX] Spanish locale: ``Miercoles`` should have acute accent - [Fix] Fix Finnish grammar - [FIX] Fix typo in 'Arrow.floor' docstring - [FIX] Use read() utility to open README - [FIX] span_range for week frame - [NEW] Add minimal support for fractional seconds longer than six digits. - [NEW] Adding locale support for Marathi (mr) - [NEW] Add count argument to span method - [NEW] Improved docs 0.5.1 - 0.5.4 ------------- - [FIX] test the behavior of simplejson instead of calling for_json directly (tonyseek) - [FIX] Add Hebrew Locale (doodyparizada) - [FIX] Update documentation location (andrewelkins) - [FIX] Update setup.py Development Status level (andrewelkins) - [FIX] Case insensitive month match (cshowe) 0.5.0 ----- - [NEW] struct_time addition. (mhworth) - [NEW] Version grep (eirnym) - [NEW] Default to ISO 8601 format (emonty) - [NEW] Raise TypeError on comparison (sniekamp) - [NEW] Adding Macedonian(mk) locale (krisfremen) - [FIX] Fix for ISO seconds and fractional seconds (sdispater) (andrewelkins) - [FIX] Use correct Dutch wording for "hours" (wbolster) - [FIX] Complete the list of english locales (indorilftw) - [FIX] Change README to reStructuredText (nyuszika7h) - [FIX] Parse lower-cased 'h' (tamentis) - [FIX] Slight modifications to Dutch locale (nvie) 0.4.4 ----- - [NEW] Include the docs in the released tarball - [NEW] Czech localization Czech localization for Arrow - [NEW] Add fa_ir to locales - [FIX] Fixes parsing of time strings with a final Z - [FIX] Fixes ISO parsing and formatting for fractional seconds - [FIX] test_fromtimestamp sp - [FIX] some typos fixed - [FIX] removed an unused import statement - [FIX] docs table fix - [FIX] Issue with specify 'X' template and no template at all to arrow.get - [FIX] Fix "import" typo in docs/index.rst - [FIX] Fix unit tests for zero passed - [FIX] Update layout.html - [FIX] In Norwegian and new Norwegian months and weekdays should not be capitalized - [FIX] Fixed discrepancy between specifying 'X' to arrow.get and specifying no template 0.4.3 ----- - [NEW] Turkish locale (Emre) - [NEW] Arabic locale (Mosab Ahmad) - [NEW] Danish locale (Holmars) - [NEW] Icelandic locale (Holmars) - [NEW] Hindi locale (Atmb4u) - [NEW] Malayalam locale (Atmb4u) - [NEW] Finnish locale (Stormpat) - [NEW] Portuguese locale (Danielcorreia) - [NEW] ``h`` and ``hh`` strings are now supported (Averyonghub) - [FIX] An incorrect inflection in the Polish locale has been fixed (Avalanchy) - [FIX] ``arrow.get`` now properly handles ``Date`` (Jaapz) - [FIX] Tests are now declared in ``setup.py`` and the manifest (Pypingou) - [FIX] ``__version__`` has been added to ``__init__.py`` (Sametmax) - [FIX] ISO 8601 strings can be parsed without a separator (Ivandiguisto / Root) - [FIX] Documentation is now more clear regarding some inputs on ``arrow.get`` (Eriktaubeneck) - [FIX] Some documentation links have been fixed (Vrutsky) - [FIX] Error messages for parse errors are now more descriptive (Maciej Albin) - [FIX] The parser now correctly checks for separators in strings (Mschwager) 0.4.2 ----- - [NEW] Factory ``get`` method now accepts a single ``Arrow`` argument. - [NEW] Tokens SSSS, SSSSS and SSSSSS are supported in parsing. - [NEW] ``Arrow`` objects have a ``float_timestamp`` property. - [NEW] Vietnamese locale (Iu1nguoi) - [NEW] Factory ``get`` method now accepts a list of format strings (Dgilland) - [NEW] A MANIFEST.in file has been added (Pypingou) - [NEW] Tests can be run directly from ``setup.py`` (Pypingou) - [FIX] Arrow docs now list 'day of week' format tokens correctly (Rudolphfroger) - [FIX] Several issues with the Korean locale have been resolved (Yoloseem) - [FIX] ``humanize`` now correctly returns unicode (Shvechikov) - [FIX] ``Arrow`` objects now pickle / unpickle correctly (Yoloseem) 0.4.1 ----- - [NEW] Table / explanation of formatting & parsing tokens in docs - [NEW] Brazilian locale (Augusto2112) - [NEW] Dutch locale (OrangeTux) - [NEW] Italian locale (Pertux) - [NEW] Austrain locale (LeChewbacca) - [NEW] Tagalog locale (Marksteve) - [FIX] Corrected spelling and day numbers in German locale (LeChewbacca) - [FIX] Factory ``get`` method should now handle unicode strings correctly (Bwells) - [FIX] Midnight and noon should now parse and format correctly (Bwells) 0.4.0 ----- - [NEW] Format-free ISO 8601 parsing in factory ``get`` method - [NEW] Support for 'week' / 'weeks' in ``span``, ``range``, ``span_range``, ``floor`` and ``ceil`` - [NEW] Support for 'weeks' in ``replace`` - [NEW] Norwegian locale (Martinp) - [NEW] Japanese locale (CortYuming) - [FIX] Timezones no longer show the wrong sign when formatted (Bean) - [FIX] Microseconds are parsed correctly from strings (Bsidhom) - [FIX] Locale day-of-week is no longer off by one (Cynddl) - [FIX] Corrected plurals of Ukrainian and Russian nouns (Catchagain) - [CHANGE] Old 0.1 ``arrow`` module method removed - [CHANGE] Dropped timestamp support in ``range`` and ``span_range`` (never worked correctly) - [CHANGE] Dropped parsing of single string as tz string in factory ``get`` method (replaced by ISO 8601) 0.3.5 ----- - [NEW] French locale (Cynddl) - [NEW] Spanish locale (Slapresta) - [FIX] Ranges handle multiple timezones correctly (Ftobia) 0.3.4 ----- - [FIX] Humanize no longer sometimes returns the wrong month delta - [FIX] ``__format__`` works correctly with no format string 0.3.3 ----- - [NEW] Python 2.6 support - [NEW] Initial support for locale-based parsing and formatting - [NEW] ArrowFactory class, now proxied as the module API - [NEW] ``factory`` api method to obtain a factory for a custom type - [FIX] Python 3 support and tests completely ironed out 0.3.2 ----- - [NEW] Python 3+ support 0.3.1 ----- - [FIX] The old ``arrow`` module function handles timestamps correctly as it used to 0.3.0 ----- - [NEW] ``Arrow.replace`` method - [NEW] Accept timestamps, datetimes and Arrows for datetime inputs, where reasonable - [FIX] ``range`` and ``span_range`` respect end and limit parameters correctly - [CHANGE] Arrow objects are no longer mutable - [CHANGE] Plural attribute name semantics altered: single -> absolute, plural -> relative - [CHANGE] Plural names no longer supported as properties (e.g. ``arrow.utcnow().years``) 0.2.1 ----- - [NEW] Support for localized humanization - [NEW] English, Russian, Greek, Korean, Chinese locales 0.2.0 ----- - **REWRITE** - [NEW] Date parsing - [NEW] Date formatting - [NEW] ``floor``, ``ceil`` and ``span`` methods - [NEW] ``datetime`` interface implementation - [NEW] ``clone`` method - [NEW] ``get``, ``now`` and ``utcnow`` API methods 0.1.6 ----- - [NEW] Humanized time deltas - [NEW] ``__eq__`` implemented - [FIX] Issues with conversions related to daylight savings time resolved - [CHANGE] ``__str__`` uses ISO formatting 0.1.5 ----- - **Started tracking changes** - [NEW] Parsing of ISO-formatted time zone offsets (e.g. '+02:30', '-05:00') - [NEW] Resolved some issues with timestamps and delta / Olson time zones python-arrow-1.2.1/LICENSE000066400000000000000000000261151415100123600151670ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2021 Chris Smith Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. python-arrow-1.2.1/MANIFEST.in000066400000000000000000000002351415100123600157130ustar00rootroot00000000000000include LICENSE CHANGELOG.rst README.rst Makefile requirements-dev.txt tox.ini recursive-include tests *.py recursive-include docs *.py *.rst *.bat Makefile python-arrow-1.2.1/Makefile000066400000000000000000000020101415100123600156060ustar00rootroot00000000000000.PHONY: auto test docs clean auto: build39 build36: PYTHON_VER = python3.6 build37: PYTHON_VER = python3.7 build38: PYTHON_VER = python3.8 build39: PYTHON_VER = python3.9 build310: PYTHON_VER = python3.10 build36 build37 build38 build39 build310: clean $(PYTHON_VER) -m venv venv . venv/bin/activate; \ pip install -U pip setuptools wheel; \ pip install -r requirements-dev.txt; \ pre-commit install test: rm -f .coverage coverage.xml . venv/bin/activate; \ pytest lint: . venv/bin/activate; \ pre-commit run --all-files --show-diff-on-failure docs: rm -rf docs/_build . venv/bin/activate; \ cd docs; \ make html clean: clean-dist rm -rf venv .pytest_cache ./**/__pycache__ rm -f .coverage coverage.xml ./**/*.pyc clean-dist: rm -rf dist build .egg .eggs arrow.egg-info build-dist: . venv/bin/activate; \ pip install -U pip setuptools twine wheel; \ python setup.py sdist bdist_wheel upload-dist: . venv/bin/activate; \ twine upload dist/* publish: test clean-dist build-dist upload-dist clean-dist python-arrow-1.2.1/README.rst000066400000000000000000000131221415100123600156430ustar00rootroot00000000000000Arrow: Better dates & times for Python ====================================== .. start-inclusion-marker-do-not-remove .. image:: https://github.com/arrow-py/arrow/workflows/tests/badge.svg?branch=master :alt: Build Status :target: https://github.com/arrow-py/arrow/actions?query=workflow%3Atests+branch%3Amaster .. image:: https://codecov.io/gh/arrow-py/arrow/branch/master/graph/badge.svg :alt: Coverage :target: https://codecov.io/gh/arrow-py/arrow .. image:: https://img.shields.io/pypi/v/arrow.svg :alt: PyPI Version :target: https://pypi.python.org/pypi/arrow .. image:: https://img.shields.io/pypi/pyversions/arrow.svg :alt: Supported Python Versions :target: https://pypi.python.org/pypi/arrow .. image:: https://img.shields.io/pypi/l/arrow.svg :alt: License :target: https://pypi.python.org/pypi/arrow .. image:: https://img.shields.io/badge/code%20style-black-000000.svg :alt: Code Style: Black :target: https://github.com/psf/black **Arrow** is a Python library that offers a sensible and human-friendly approach to creating, manipulating, formatting and converting dates, times and timestamps. It implements and updates the datetime type, plugging gaps in functionality and providing an intelligent module API that supports many common creation scenarios. Simply put, it helps you work with dates and times with fewer imports and a lot less code. Arrow is named after the `arrow of time `_ and is heavily inspired by `moment.js `_ and `requests `_. Why use Arrow over built-in modules? ------------------------------------ Python's standard library and some other low-level modules have near-complete date, time and timezone functionality, but don't work very well from a usability perspective: - Too many modules: datetime, time, calendar, dateutil, pytz and more - Too many types: date, time, datetime, tzinfo, timedelta, relativedelta, etc. - Timezones and timestamp conversions are verbose and unpleasant - Timezone naivety is the norm - Gaps in functionality: ISO 8601 parsing, timespans, humanization Features -------- - Fully-implemented, drop-in replacement for datetime - Support for Python 3.6+ - Timezone-aware and UTC by default - Super-simple creation options for many common input scenarios - ``shift`` method with support for relative offsets, including weeks - Format and parse strings automatically - Wide support for the `ISO 8601 `_ standard - Timezone conversion - Support for ``dateutil``, ``pytz``, and ``ZoneInfo`` tzinfo objects - Generates time spans, ranges, floors and ceilings for time frames ranging from microsecond to year - Humanize dates and times with a growing list of contributed locales - Extensible for your own Arrow-derived types - Full support for PEP 484-style type hints Quick Start ----------- Installation ~~~~~~~~~~~~ To install Arrow, use `pip `_ or `pipenv `_: .. code-block:: console $ pip install -U arrow Example Usage ~~~~~~~~~~~~~ .. code-block:: python >>> import arrow >>> arrow.get('2013-05-11T21:23:58.970460+07:00') >>> utc = arrow.utcnow() >>> utc >>> utc = utc.shift(hours=-1) >>> utc >>> local = utc.to('US/Pacific') >>> local >>> local.timestamp() 1368303838.970460 >>> local.format() '2013-05-11 13:23:58 -07:00' >>> local.format('YYYY-MM-DD HH:mm:ss ZZ') '2013-05-11 13:23:58 -07:00' >>> local.humanize() 'an hour ago' >>> local.humanize(locale='ko-kr') '한시간 전' .. end-inclusion-marker-do-not-remove Documentation ------------- For full documentation, please visit `arrow.readthedocs.io `_. Contributing ------------ Contributions are welcome for both code and localizations (adding and updating locales). Begin by gaining familiarity with the Arrow library and its features. Then, jump into contributing: #. Find an issue or feature to tackle on the `issue tracker `_. Issues marked with the `"good first issue" label `_ may be a great place to start! #. Fork `this repository `_ on GitHub and begin making changes in a branch. #. Add a few tests to ensure that the bug was fixed or the feature works as expected. #. Run the entire test suite and linting checks by running one of the following commands: ``tox && tox -e lint,docs`` (if you have `tox `_ installed) **OR** ``make build39 && make test && make lint`` (if you do not have Python 3.9 installed, replace ``build39`` with the latest Python version on your system). #. Submit a pull request and await feedback 😃. If you have any questions along the way, feel free to ask them `here `_. Support Arrow ------------- `Open Collective `_ is an online funding platform that provides tools to raise money and share your finances with full transparency. It is the platform of choice for individuals and companies to make one-time or recurring donations directly to the project. If you are interested in making a financial contribution, please visit the `Arrow collective `_. python-arrow-1.2.1/arrow/000077500000000000000000000000001415100123600153075ustar00rootroot00000000000000python-arrow-1.2.1/arrow/__init__.py000066400000000000000000000015501415100123600174210ustar00rootroot00000000000000from ._version import __version__ from .api import get, now, utcnow from .arrow import Arrow from .factory import ArrowFactory from .formatter import ( FORMAT_ATOM, FORMAT_COOKIE, FORMAT_RFC822, FORMAT_RFC850, FORMAT_RFC1036, FORMAT_RFC1123, FORMAT_RFC2822, FORMAT_RFC3339, FORMAT_RSS, FORMAT_W3C, ) from .parser import ParserError # https://mypy.readthedocs.io/en/stable/command_line.html#cmdoption-mypy-no-implicit-reexport # Mypy with --strict or --no-implicit-reexport requires an explicit reexport. __all__ = [ "__version__", "get", "now", "utcnow", "Arrow", "ArrowFactory", "FORMAT_ATOM", "FORMAT_COOKIE", "FORMAT_RFC822", "FORMAT_RFC850", "FORMAT_RFC1036", "FORMAT_RFC1123", "FORMAT_RFC2822", "FORMAT_RFC3339", "FORMAT_RSS", "FORMAT_W3C", "ParserError", ] python-arrow-1.2.1/arrow/_version.py000066400000000000000000000000261415100123600175030ustar00rootroot00000000000000__version__ = "1.2.1" python-arrow-1.2.1/arrow/api.py000066400000000000000000000053031415100123600164330ustar00rootroot00000000000000""" Provides the default implementation of :class:`ArrowFactory ` methods for use as a module API. """ from datetime import date, datetime from datetime import tzinfo as dt_tzinfo from time import struct_time from typing import Any, List, Optional, Tuple, Type, Union, overload from arrow.arrow import TZ_EXPR, Arrow from arrow.constants import DEFAULT_LOCALE from arrow.factory import ArrowFactory # internal default factory. _factory = ArrowFactory() # TODO: Use Positional Only Argument (https://www.python.org/dev/peps/pep-0570/) # after Python 3.7 deprecation @overload def get( *, locale: str = DEFAULT_LOCALE, tzinfo: Optional[TZ_EXPR] = None, normalize_whitespace: bool = False, ) -> Arrow: ... # pragma: no cover @overload def get( *args: int, locale: str = DEFAULT_LOCALE, tzinfo: Optional[TZ_EXPR] = None, normalize_whitespace: bool = False, ) -> Arrow: ... # pragma: no cover @overload def get( __obj: Union[ Arrow, datetime, date, struct_time, dt_tzinfo, int, float, str, Tuple[int, int, int], ], *, locale: str = DEFAULT_LOCALE, tzinfo: Optional[TZ_EXPR] = None, normalize_whitespace: bool = False, ) -> Arrow: ... # pragma: no cover @overload def get( __arg1: Union[datetime, date], __arg2: TZ_EXPR, *, locale: str = DEFAULT_LOCALE, tzinfo: Optional[TZ_EXPR] = None, normalize_whitespace: bool = False, ) -> Arrow: ... # pragma: no cover @overload def get( __arg1: str, __arg2: Union[str, List[str]], *, locale: str = DEFAULT_LOCALE, tzinfo: Optional[TZ_EXPR] = None, normalize_whitespace: bool = False, ) -> Arrow: ... # pragma: no cover def get(*args: Any, **kwargs: Any) -> Arrow: """Calls the default :class:`ArrowFactory ` ``get`` method.""" return _factory.get(*args, **kwargs) get.__doc__ = _factory.get.__doc__ def utcnow() -> Arrow: """Calls the default :class:`ArrowFactory ` ``utcnow`` method.""" return _factory.utcnow() utcnow.__doc__ = _factory.utcnow.__doc__ def now(tz: Optional[TZ_EXPR] = None) -> Arrow: """Calls the default :class:`ArrowFactory ` ``now`` method.""" return _factory.now(tz) now.__doc__ = _factory.now.__doc__ def factory(type: Type[Arrow]) -> ArrowFactory: """Returns an :class:`.ArrowFactory` for the specified :class:`Arrow ` or derived type. :param type: the type, :class:`Arrow ` or derived. """ return ArrowFactory(type) __all__ = ["get", "utcnow", "now", "factory"] python-arrow-1.2.1/arrow/arrow.py000066400000000000000000001741101415100123600170170ustar00rootroot00000000000000""" Provides the :class:`Arrow ` class, an enhanced ``datetime`` replacement. """ import calendar import re import sys from datetime import date from datetime import datetime as dt_datetime from datetime import time as dt_time from datetime import timedelta from datetime import tzinfo as dt_tzinfo from math import trunc from time import struct_time from typing import ( Any, ClassVar, Generator, Iterable, List, Mapping, Optional, Tuple, Union, cast, overload, ) from dateutil import tz as dateutil_tz from dateutil.relativedelta import relativedelta from arrow import formatter, locales, parser, util from arrow.constants import DEFAULT_LOCALE, DEHUMANIZE_LOCALES from arrow.locales import TimeFrameLiteral if sys.version_info < (3, 8): # pragma: no cover from typing_extensions import Final, Literal else: from typing import Final, Literal # pragma: no cover TZ_EXPR = Union[dt_tzinfo, str] _T_FRAMES = Literal[ "year", "years", "month", "months", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "microsecond", "microseconds", "week", "weeks", "quarter", "quarters", ] _BOUNDS = Literal["[)", "()", "(]", "[]"] _GRANULARITY = Literal[ "auto", "second", "minute", "hour", "day", "week", "month", "quarter", "year", ] class Arrow: """An :class:`Arrow ` object. Implements the ``datetime`` interface, behaving as an aware ``datetime`` while implementing additional functionality. :param year: the calendar year. :param month: the calendar month. :param day: the calendar day. :param hour: (optional) the hour. Defaults to 0. :param minute: (optional) the minute, Defaults to 0. :param second: (optional) the second, Defaults to 0. :param microsecond: (optional) the microsecond. Defaults to 0. :param tzinfo: (optional) A timezone expression. Defaults to UTC. :param fold: (optional) 0 or 1, used to disambiguate repeated wall times. Defaults to 0. .. _tz-expr: Recognized timezone expressions: - A ``tzinfo`` object. - A ``str`` describing a timezone, similar to 'US/Pacific', or 'Europe/Berlin'. - A ``str`` in ISO 8601 style, as in '+07:00'. - A ``str``, one of the following: 'local', 'utc', 'UTC'. Usage:: >>> import arrow >>> arrow.Arrow(2013, 5, 5, 12, 30, 45) """ resolution: ClassVar[timedelta] = dt_datetime.resolution min: ClassVar["Arrow"] max: ClassVar["Arrow"] _ATTRS: Final[List[str]] = [ "year", "month", "day", "hour", "minute", "second", "microsecond", ] _ATTRS_PLURAL: Final[List[str]] = [f"{a}s" for a in _ATTRS] _MONTHS_PER_QUARTER: Final[int] = 3 _SECS_PER_MINUTE: Final[int] = 60 _SECS_PER_HOUR: Final[int] = 60 * 60 _SECS_PER_DAY: Final[int] = 60 * 60 * 24 _SECS_PER_WEEK: Final[int] = 60 * 60 * 24 * 7 _SECS_PER_MONTH: Final[float] = 60 * 60 * 24 * 30.5 _SECS_PER_QUARTER: Final[float] = 60 * 60 * 24 * 30.5 * 3 _SECS_PER_YEAR: Final[int] = 60 * 60 * 24 * 365 _SECS_MAP: Final[Mapping[TimeFrameLiteral, float]] = { "second": 1.0, "minute": _SECS_PER_MINUTE, "hour": _SECS_PER_HOUR, "day": _SECS_PER_DAY, "week": _SECS_PER_WEEK, "month": _SECS_PER_MONTH, "quarter": _SECS_PER_QUARTER, "year": _SECS_PER_YEAR, } _datetime: dt_datetime def __init__( self, year: int, month: int, day: int, hour: int = 0, minute: int = 0, second: int = 0, microsecond: int = 0, tzinfo: Optional[TZ_EXPR] = None, **kwargs: Any, ) -> None: if tzinfo is None: tzinfo = dateutil_tz.tzutc() # detect that tzinfo is a pytz object (issue #626) elif ( isinstance(tzinfo, dt_tzinfo) and hasattr(tzinfo, "localize") and hasattr(tzinfo, "zone") and tzinfo.zone # type: ignore[attr-defined] ): tzinfo = parser.TzinfoParser.parse(tzinfo.zone) # type: ignore[attr-defined] elif isinstance(tzinfo, str): tzinfo = parser.TzinfoParser.parse(tzinfo) fold = kwargs.get("fold", 0) self._datetime = dt_datetime( year, month, day, hour, minute, second, microsecond, tzinfo, fold=fold ) # factories: single object, both original and from datetime. @classmethod def now(cls, tzinfo: Optional[dt_tzinfo] = None) -> "Arrow": """Constructs an :class:`Arrow ` object, representing "now" in the given timezone. :param tzinfo: (optional) a ``tzinfo`` object. Defaults to local time. Usage:: >>> arrow.now('Asia/Baku') """ if tzinfo is None: tzinfo = dateutil_tz.tzlocal() dt = dt_datetime.now(tzinfo) return cls( dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond, dt.tzinfo, fold=getattr(dt, "fold", 0), ) @classmethod def utcnow(cls) -> "Arrow": """Constructs an :class:`Arrow ` object, representing "now" in UTC time. Usage:: >>> arrow.utcnow() """ dt = dt_datetime.now(dateutil_tz.tzutc()) return cls( dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond, dt.tzinfo, fold=getattr(dt, "fold", 0), ) @classmethod def fromtimestamp( cls, timestamp: Union[int, float, str], tzinfo: Optional[TZ_EXPR] = None, ) -> "Arrow": """Constructs an :class:`Arrow ` object from a timestamp, converted to the given timezone. :param timestamp: an ``int`` or ``float`` timestamp, or a ``str`` that converts to either. :param tzinfo: (optional) a ``tzinfo`` object. Defaults to local time. """ if tzinfo is None: tzinfo = dateutil_tz.tzlocal() elif isinstance(tzinfo, str): tzinfo = parser.TzinfoParser.parse(tzinfo) if not util.is_timestamp(timestamp): raise ValueError(f"The provided timestamp {timestamp!r} is invalid.") timestamp = util.normalize_timestamp(float(timestamp)) dt = dt_datetime.fromtimestamp(timestamp, tzinfo) return cls( dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond, dt.tzinfo, fold=getattr(dt, "fold", 0), ) @classmethod def utcfromtimestamp(cls, timestamp: Union[int, float, str]) -> "Arrow": """Constructs an :class:`Arrow ` object from a timestamp, in UTC time. :param timestamp: an ``int`` or ``float`` timestamp, or a ``str`` that converts to either. """ if not util.is_timestamp(timestamp): raise ValueError(f"The provided timestamp {timestamp!r} is invalid.") timestamp = util.normalize_timestamp(float(timestamp)) dt = dt_datetime.utcfromtimestamp(timestamp) return cls( dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond, dateutil_tz.tzutc(), fold=getattr(dt, "fold", 0), ) @classmethod def fromdatetime(cls, dt: dt_datetime, tzinfo: Optional[TZ_EXPR] = None) -> "Arrow": """Constructs an :class:`Arrow ` object from a ``datetime`` and optional replacement timezone. :param dt: the ``datetime`` :param tzinfo: (optional) A :ref:`timezone expression `. Defaults to ``dt``'s timezone, or UTC if naive. Usage:: >>> dt datetime.datetime(2021, 4, 7, 13, 48, tzinfo=tzfile('/usr/share/zoneinfo/US/Pacific')) >>> arrow.Arrow.fromdatetime(dt) """ if tzinfo is None: if dt.tzinfo is None: tzinfo = dateutil_tz.tzutc() else: tzinfo = dt.tzinfo return cls( dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond, tzinfo, fold=getattr(dt, "fold", 0), ) @classmethod def fromdate(cls, date: date, tzinfo: Optional[TZ_EXPR] = None) -> "Arrow": """Constructs an :class:`Arrow ` object from a ``date`` and optional replacement timezone. All time values are set to 0. :param date: the ``date`` :param tzinfo: (optional) A :ref:`timezone expression `. Defaults to UTC. """ if tzinfo is None: tzinfo = dateutil_tz.tzutc() return cls(date.year, date.month, date.day, tzinfo=tzinfo) @classmethod def strptime( cls, date_str: str, fmt: str, tzinfo: Optional[TZ_EXPR] = None ) -> "Arrow": """Constructs an :class:`Arrow ` object from a date string and format, in the style of ``datetime.strptime``. Optionally replaces the parsed timezone. :param date_str: the date string. :param fmt: the format string using datetime format codes. :param tzinfo: (optional) A :ref:`timezone expression `. Defaults to the parsed timezone if ``fmt`` contains a timezone directive, otherwise UTC. Usage:: >>> arrow.Arrow.strptime('20-01-2019 15:49:10', '%d-%m-%Y %H:%M:%S') """ dt = dt_datetime.strptime(date_str, fmt) if tzinfo is None: tzinfo = dt.tzinfo return cls( dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond, tzinfo, fold=getattr(dt, "fold", 0), ) @classmethod def fromordinal(cls, ordinal: int) -> "Arrow": """Constructs an :class:`Arrow ` object corresponding to the Gregorian Ordinal. :param ordinal: an ``int`` corresponding to a Gregorian Ordinal. Usage:: >>> arrow.fromordinal(737741) """ util.validate_ordinal(ordinal) dt = dt_datetime.fromordinal(ordinal) return cls( dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond, dt.tzinfo, fold=getattr(dt, "fold", 0), ) # factories: ranges and spans @classmethod def range( cls, frame: _T_FRAMES, start: Union["Arrow", dt_datetime], end: Union["Arrow", dt_datetime, None] = None, tz: Optional[TZ_EXPR] = None, limit: Optional[int] = None, ) -> Generator["Arrow", None, None]: """Returns an iterator of :class:`Arrow ` objects, representing points in time between two inputs. :param frame: The timeframe. Can be any ``datetime`` property (day, hour, minute...). :param start: A datetime expression, the start of the range. :param end: (optional) A datetime expression, the end of the range. :param tz: (optional) A :ref:`timezone expression `. Defaults to ``start``'s timezone, or UTC if ``start`` is naive. :param limit: (optional) A maximum number of tuples to return. **NOTE**: The ``end`` or ``limit`` must be provided. Call with ``end`` alone to return the entire range. Call with ``limit`` alone to return a maximum # of results from the start. Call with both to cap a range at a maximum # of results. **NOTE**: ``tz`` internally **replaces** the timezones of both ``start`` and ``end`` before iterating. As such, either call with naive objects and ``tz``, or aware objects from the same timezone and no ``tz``. Supported frame values: year, quarter, month, week, day, hour, minute, second, microsecond. Recognized datetime expressions: - An :class:`Arrow ` object. - A ``datetime`` object. Usage:: >>> start = datetime(2013, 5, 5, 12, 30) >>> end = datetime(2013, 5, 5, 17, 15) >>> for r in arrow.Arrow.range('hour', start, end): ... print(repr(r)) ... **NOTE**: Unlike Python's ``range``, ``end`` *may* be included in the returned iterator:: >>> start = datetime(2013, 5, 5, 12, 30) >>> end = datetime(2013, 5, 5, 13, 30) >>> for r in arrow.Arrow.range('hour', start, end): ... print(repr(r)) ... """ _, frame_relative, relative_steps = cls._get_frames(frame) tzinfo = cls._get_tzinfo(start.tzinfo if tz is None else tz) start = cls._get_datetime(start).replace(tzinfo=tzinfo) end, limit = cls._get_iteration_params(end, limit) end = cls._get_datetime(end).replace(tzinfo=tzinfo) current = cls.fromdatetime(start) original_day = start.day day_is_clipped = False i = 0 while current <= end and i < limit: i += 1 yield current values = [getattr(current, f) for f in cls._ATTRS] current = cls(*values, tzinfo=tzinfo).shift( # type: ignore **{frame_relative: relative_steps} ) if frame in ["month", "quarter", "year"] and current.day < original_day: day_is_clipped = True if day_is_clipped and not cls._is_last_day_of_month(current): current = current.replace(day=original_day) def span( self, frame: _T_FRAMES, count: int = 1, bounds: _BOUNDS = "[)", exact: bool = False, week_start: int = 1, ) -> Tuple["Arrow", "Arrow"]: """Returns a tuple of two new :class:`Arrow ` objects, representing the timespan of the :class:`Arrow ` object in a given timeframe. :param frame: the timeframe. Can be any ``datetime`` property (day, hour, minute...). :param count: (optional) the number of frames to span. :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies whether to include or exclude the start and end values in the span. '(' excludes the start, '[' includes the start, ')' excludes the end, and ']' includes the end. If the bounds are not specified, the default bound '[)' is used. :param exact: (optional) whether to have the start of the timespan begin exactly at the time specified by ``start`` and the end of the timespan truncated so as not to extend beyond ``end``. :param week_start: (optional) only used in combination with the week timeframe. Follows isoweekday() where Monday is 1 and Sunday is 7. Supported frame values: year, quarter, month, week, day, hour, minute, second. Usage:: >>> arrow.utcnow() >>> arrow.utcnow().span('hour') (, ) >>> arrow.utcnow().span('day') (, ) >>> arrow.utcnow().span('day', count=2) (, ) >>> arrow.utcnow().span('day', bounds='[]') (, ) >>> arrow.utcnow().span('week') (, ) >>> arrow.utcnow().span('week', week_start=6) (, ) """ if not 1 <= week_start <= 7: raise ValueError("week_start argument must be between 1 and 7.") util.validate_bounds(bounds) frame_absolute, frame_relative, relative_steps = self._get_frames(frame) if frame_absolute == "week": attr = "day" elif frame_absolute == "quarter": attr = "month" else: attr = frame_absolute floor = self if not exact: index = self._ATTRS.index(attr) frames = self._ATTRS[: index + 1] values = [getattr(self, f) for f in frames] for _ in range(3 - len(values)): values.append(1) floor = self.__class__(*values, tzinfo=self.tzinfo) # type: ignore if frame_absolute == "week": # if week_start is greater than self.isoweekday() go back one week by setting delta = 7 delta = 7 if week_start > self.isoweekday() else 0 floor = floor.shift(days=-(self.isoweekday() - week_start) - delta) elif frame_absolute == "quarter": floor = floor.shift(months=-((self.month - 1) % 3)) ceil = floor.shift(**{frame_relative: count * relative_steps}) if bounds[0] == "(": floor = floor.shift(microseconds=+1) if bounds[1] == ")": ceil = ceil.shift(microseconds=-1) return floor, ceil def floor(self, frame: _T_FRAMES) -> "Arrow": """Returns a new :class:`Arrow ` object, representing the "floor" of the timespan of the :class:`Arrow ` object in a given timeframe. Equivalent to the first element in the 2-tuple returned by :func:`span `. :param frame: the timeframe. Can be any ``datetime`` property (day, hour, minute...). Usage:: >>> arrow.utcnow().floor('hour') """ return self.span(frame)[0] def ceil(self, frame: _T_FRAMES) -> "Arrow": """Returns a new :class:`Arrow ` object, representing the "ceiling" of the timespan of the :class:`Arrow ` object in a given timeframe. Equivalent to the second element in the 2-tuple returned by :func:`span `. :param frame: the timeframe. Can be any ``datetime`` property (day, hour, minute...). Usage:: >>> arrow.utcnow().ceil('hour') """ return self.span(frame)[1] @classmethod def span_range( cls, frame: _T_FRAMES, start: dt_datetime, end: dt_datetime, tz: Optional[TZ_EXPR] = None, limit: Optional[int] = None, bounds: _BOUNDS = "[)", exact: bool = False, ) -> Iterable[Tuple["Arrow", "Arrow"]]: """Returns an iterator of tuples, each :class:`Arrow ` objects, representing a series of timespans between two inputs. :param frame: The timeframe. Can be any ``datetime`` property (day, hour, minute...). :param start: A datetime expression, the start of the range. :param end: (optional) A datetime expression, the end of the range. :param tz: (optional) A :ref:`timezone expression `. Defaults to ``start``'s timezone, or UTC if ``start`` is naive. :param limit: (optional) A maximum number of tuples to return. :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies whether to include or exclude the start and end values in each span in the range. '(' excludes the start, '[' includes the start, ')' excludes the end, and ']' includes the end. If the bounds are not specified, the default bound '[)' is used. :param exact: (optional) whether to have the first timespan start exactly at the time specified by ``start`` and the final span truncated so as not to extend beyond ``end``. **NOTE**: The ``end`` or ``limit`` must be provided. Call with ``end`` alone to return the entire range. Call with ``limit`` alone to return a maximum # of results from the start. Call with both to cap a range at a maximum # of results. **NOTE**: ``tz`` internally **replaces** the timezones of both ``start`` and ``end`` before iterating. As such, either call with naive objects and ``tz``, or aware objects from the same timezone and no ``tz``. Supported frame values: year, quarter, month, week, day, hour, minute, second, microsecond. Recognized datetime expressions: - An :class:`Arrow ` object. - A ``datetime`` object. **NOTE**: Unlike Python's ``range``, ``end`` will *always* be included in the returned iterator of timespans. Usage: >>> start = datetime(2013, 5, 5, 12, 30) >>> end = datetime(2013, 5, 5, 17, 15) >>> for r in arrow.Arrow.span_range('hour', start, end): ... print(r) ... (, ) (, ) (, ) (, ) (, ) (, ) """ tzinfo = cls._get_tzinfo(start.tzinfo if tz is None else tz) start = cls.fromdatetime(start, tzinfo).span(frame, exact=exact)[0] end = cls.fromdatetime(end, tzinfo) _range = cls.range(frame, start, end, tz, limit) if not exact: for r in _range: yield r.span(frame, bounds=bounds, exact=exact) for r in _range: floor, ceil = r.span(frame, bounds=bounds, exact=exact) if ceil > end: ceil = end if bounds[1] == ")": ceil += relativedelta(microseconds=-1) if floor == end: break elif floor + relativedelta(microseconds=-1) == end: break yield floor, ceil @classmethod def interval( cls, frame: _T_FRAMES, start: dt_datetime, end: dt_datetime, interval: int = 1, tz: Optional[TZ_EXPR] = None, bounds: _BOUNDS = "[)", exact: bool = False, ) -> Iterable[Tuple["Arrow", "Arrow"]]: """Returns an iterator of tuples, each :class:`Arrow ` objects, representing a series of intervals between two inputs. :param frame: The timeframe. Can be any ``datetime`` property (day, hour, minute...). :param start: A datetime expression, the start of the range. :param end: (optional) A datetime expression, the end of the range. :param interval: (optional) Time interval for the given time frame. :param tz: (optional) A timezone expression. Defaults to UTC. :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies whether to include or exclude the start and end values in the intervals. '(' excludes the start, '[' includes the start, ')' excludes the end, and ']' includes the end. If the bounds are not specified, the default bound '[)' is used. :param exact: (optional) whether to have the first timespan start exactly at the time specified by ``start`` and the final interval truncated so as not to extend beyond ``end``. Supported frame values: year, quarter, month, week, day, hour, minute, second Recognized datetime expressions: - An :class:`Arrow ` object. - A ``datetime`` object. Recognized timezone expressions: - A ``tzinfo`` object. - A ``str`` describing a timezone, similar to 'US/Pacific', or 'Europe/Berlin'. - A ``str`` in ISO 8601 style, as in '+07:00'. - A ``str``, one of the following: 'local', 'utc', 'UTC'. Usage: >>> start = datetime(2013, 5, 5, 12, 30) >>> end = datetime(2013, 5, 5, 17, 15) >>> for r in arrow.Arrow.interval('hour', start, end, 2): ... print r ... (, ) (, ) (, ) """ if interval < 1: raise ValueError("interval has to be a positive integer") spanRange = iter( cls.span_range(frame, start, end, tz, bounds=bounds, exact=exact) ) while True: try: intvlStart, intvlEnd = next(spanRange) for _ in range(interval - 1): try: _, intvlEnd = next(spanRange) except StopIteration: continue yield intvlStart, intvlEnd except StopIteration: return # representations def __repr__(self) -> str: return f"<{self.__class__.__name__} [{self.__str__()}]>" def __str__(self) -> str: return self._datetime.isoformat() def __format__(self, formatstr: str) -> str: if len(formatstr) > 0: return self.format(formatstr) return str(self) def __hash__(self) -> int: return self._datetime.__hash__() # attributes and properties def __getattr__(self, name: str) -> int: if name == "week": return self.isocalendar()[1] if name == "quarter": return int((self.month - 1) / self._MONTHS_PER_QUARTER) + 1 if not name.startswith("_"): value: Optional[int] = getattr(self._datetime, name, None) if value is not None: return value return cast(int, object.__getattribute__(self, name)) @property def tzinfo(self) -> dt_tzinfo: """Gets the ``tzinfo`` of the :class:`Arrow ` object. Usage:: >>> arw=arrow.utcnow() >>> arw.tzinfo tzutc() """ # In Arrow, `_datetime` cannot be naive. return cast(dt_tzinfo, self._datetime.tzinfo) @property def datetime(self) -> dt_datetime: """Returns a datetime representation of the :class:`Arrow ` object. Usage:: >>> arw=arrow.utcnow() >>> arw.datetime datetime.datetime(2019, 1, 24, 16, 35, 27, 276649, tzinfo=tzutc()) """ return self._datetime @property def naive(self) -> dt_datetime: """Returns a naive datetime representation of the :class:`Arrow ` object. Usage:: >>> nairobi = arrow.now('Africa/Nairobi') >>> nairobi >>> nairobi.naive datetime.datetime(2019, 1, 23, 19, 27, 12, 297999) """ return self._datetime.replace(tzinfo=None) def timestamp(self) -> float: """Returns a timestamp representation of the :class:`Arrow ` object, in UTC time. Usage:: >>> arrow.utcnow().timestamp() 1616882340.256501 """ return self._datetime.timestamp() @property def int_timestamp(self) -> int: """Returns an integer timestamp representation of the :class:`Arrow ` object, in UTC time. Usage:: >>> arrow.utcnow().int_timestamp 1548260567 """ return int(self.timestamp()) @property def float_timestamp(self) -> float: """Returns a floating-point timestamp representation of the :class:`Arrow ` object, in UTC time. Usage:: >>> arrow.utcnow().float_timestamp 1548260516.830896 """ return self.timestamp() @property def fold(self) -> int: """Returns the ``fold`` value of the :class:`Arrow ` object.""" return self._datetime.fold @property def ambiguous(self) -> bool: """Indicates whether the :class:`Arrow ` object is a repeated wall time in the current timezone. """ return dateutil_tz.datetime_ambiguous(self._datetime) @property def imaginary(self) -> bool: """Indicates whether the :class: `Arrow ` object exists in the current timezone.""" return not dateutil_tz.datetime_exists(self._datetime) # mutation and duplication. def clone(self) -> "Arrow": """Returns a new :class:`Arrow ` object, cloned from the current one. Usage: >>> arw = arrow.utcnow() >>> cloned = arw.clone() """ return self.fromdatetime(self._datetime) def replace(self, **kwargs: Any) -> "Arrow": """Returns a new :class:`Arrow ` object with attributes updated according to inputs. Use property names to set their value absolutely:: >>> import arrow >>> arw = arrow.utcnow() >>> arw >>> arw.replace(year=2014, month=6) You can also replace the timezone without conversion, using a :ref:`timezone expression `:: >>> arw.replace(tzinfo=tz.tzlocal()) """ absolute_kwargs = {} for key, value in kwargs.items(): if key in self._ATTRS: absolute_kwargs[key] = value elif key in ["week", "quarter"]: raise ValueError(f"Setting absolute {key} is not supported.") elif key not in ["tzinfo", "fold"]: raise ValueError(f"Unknown attribute: {key!r}.") current = self._datetime.replace(**absolute_kwargs) tzinfo = kwargs.get("tzinfo") if tzinfo is not None: tzinfo = self._get_tzinfo(tzinfo) current = current.replace(tzinfo=tzinfo) fold = kwargs.get("fold") if fold is not None: current = current.replace(fold=fold) return self.fromdatetime(current) def shift(self, **kwargs: Any) -> "Arrow": """Returns a new :class:`Arrow ` object with attributes updated according to inputs. Use pluralized property names to relatively shift their current value: >>> import arrow >>> arw = arrow.utcnow() >>> arw >>> arw.shift(years=1, months=-1) Day-of-the-week relative shifting can use either Python's weekday numbers (Monday = 0, Tuesday = 1 .. Sunday = 6) or using dateutil.relativedelta's day instances (MO, TU .. SU). When using weekday numbers, the returned date will always be greater than or equal to the starting date. Using the above code (which is a Saturday) and asking it to shift to Saturday: >>> arw.shift(weekday=5) While asking for a Monday: >>> arw.shift(weekday=0) """ relative_kwargs = {} additional_attrs = ["weeks", "quarters", "weekday"] for key, value in kwargs.items(): if key in self._ATTRS_PLURAL or key in additional_attrs: relative_kwargs[key] = value else: supported_attr = ", ".join(self._ATTRS_PLURAL + additional_attrs) raise ValueError( f"Invalid shift time frame. Please select one of the following: {supported_attr}." ) # core datetime does not support quarters, translate to months. relative_kwargs.setdefault("months", 0) relative_kwargs["months"] += ( relative_kwargs.pop("quarters", 0) * self._MONTHS_PER_QUARTER ) current = self._datetime + relativedelta(**relative_kwargs) if not dateutil_tz.datetime_exists(current): current = dateutil_tz.resolve_imaginary(current) return self.fromdatetime(current) def to(self, tz: TZ_EXPR) -> "Arrow": """Returns a new :class:`Arrow ` object, converted to the target timezone. :param tz: A :ref:`timezone expression `. Usage:: >>> utc = arrow.utcnow() >>> utc >>> utc.to('US/Pacific') >>> utc.to(tz.tzlocal()) >>> utc.to('-07:00') >>> utc.to('local') >>> utc.to('local').to('utc') """ if not isinstance(tz, dt_tzinfo): tz = parser.TzinfoParser.parse(tz) dt = self._datetime.astimezone(tz) return self.__class__( dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond, dt.tzinfo, fold=getattr(dt, "fold", 0), ) # string output and formatting def format( self, fmt: str = "YYYY-MM-DD HH:mm:ssZZ", locale: str = DEFAULT_LOCALE ) -> str: """Returns a string representation of the :class:`Arrow ` object, formatted according to the provided format string. :param fmt: the format string. :param locale: the locale to format. Usage:: >>> arrow.utcnow().format('YYYY-MM-DD HH:mm:ss ZZ') '2013-05-09 03:56:47 -00:00' >>> arrow.utcnow().format('X') '1368071882' >>> arrow.utcnow().format('MMMM DD, YYYY') 'May 09, 2013' >>> arrow.utcnow().format() '2013-05-09 03:56:47 -00:00' """ return formatter.DateTimeFormatter(locale).format(self._datetime, fmt) def humanize( self, other: Union["Arrow", dt_datetime, None] = None, locale: str = DEFAULT_LOCALE, only_distance: bool = False, granularity: Union[_GRANULARITY, List[_GRANULARITY]] = "auto", ) -> str: """Returns a localized, humanized representation of a relative difference in time. :param other: (optional) an :class:`Arrow ` or ``datetime`` object. Defaults to now in the current :class:`Arrow ` object's timezone. :param locale: (optional) a ``str`` specifying a locale. Defaults to 'en-us'. :param only_distance: (optional) returns only time difference eg: "11 seconds" without "in" or "ago" part. :param granularity: (optional) defines the precision of the output. Set it to strings 'second', 'minute', 'hour', 'day', 'week', 'month' or 'year' or a list of any combination of these strings Usage:: >>> earlier = arrow.utcnow().shift(hours=-2) >>> earlier.humanize() '2 hours ago' >>> later = earlier.shift(hours=4) >>> later.humanize(earlier) 'in 4 hours' """ locale_name = locale locale = locales.get_locale(locale) if other is None: utc = dt_datetime.utcnow().replace(tzinfo=dateutil_tz.tzutc()) dt = utc.astimezone(self._datetime.tzinfo) elif isinstance(other, Arrow): dt = other._datetime elif isinstance(other, dt_datetime): if other.tzinfo is None: dt = other.replace(tzinfo=self._datetime.tzinfo) else: dt = other.astimezone(self._datetime.tzinfo) else: raise TypeError( f"Invalid 'other' argument of type {type(other).__name__!r}. " "Argument must be of type None, Arrow, or datetime." ) if isinstance(granularity, list) and len(granularity) == 1: granularity = granularity[0] _delta = int(round((self._datetime - dt).total_seconds())) sign = -1 if _delta < 0 else 1 delta_second = diff = abs(_delta) try: if granularity == "auto": if diff < 10: return locale.describe("now", only_distance=only_distance) if diff < self._SECS_PER_MINUTE: seconds = sign * delta_second return locale.describe( "seconds", seconds, only_distance=only_distance ) elif diff < self._SECS_PER_MINUTE * 2: return locale.describe("minute", sign, only_distance=only_distance) elif diff < self._SECS_PER_HOUR: minutes = sign * max(delta_second // self._SECS_PER_MINUTE, 2) return locale.describe( "minutes", minutes, only_distance=only_distance ) elif diff < self._SECS_PER_HOUR * 2: return locale.describe("hour", sign, only_distance=only_distance) elif diff < self._SECS_PER_DAY: hours = sign * max(delta_second // self._SECS_PER_HOUR, 2) return locale.describe("hours", hours, only_distance=only_distance) elif diff < self._SECS_PER_DAY * 2: return locale.describe("day", sign, only_distance=only_distance) elif diff < self._SECS_PER_WEEK: days = sign * max(delta_second // self._SECS_PER_DAY, 2) return locale.describe("days", days, only_distance=only_distance) elif diff < self._SECS_PER_WEEK * 2: return locale.describe("week", sign, only_distance=only_distance) elif diff < self._SECS_PER_MONTH: weeks = sign * max(delta_second // self._SECS_PER_WEEK, 2) return locale.describe("weeks", weeks, only_distance=only_distance) elif diff < self._SECS_PER_MONTH * 2: return locale.describe("month", sign, only_distance=only_distance) elif diff < self._SECS_PER_YEAR: # TODO revisit for humanization during leap years self_months = self._datetime.year * 12 + self._datetime.month other_months = dt.year * 12 + dt.month months = sign * max(abs(other_months - self_months), 2) return locale.describe( "months", months, only_distance=only_distance ) elif diff < self._SECS_PER_YEAR * 2: return locale.describe("year", sign, only_distance=only_distance) else: years = sign * max(delta_second // self._SECS_PER_YEAR, 2) return locale.describe("years", years, only_distance=only_distance) elif isinstance(granularity, str): granularity = cast(TimeFrameLiteral, granularity) # type: ignore[assignment] if granularity == "second": delta = sign * float(delta_second) if abs(delta) < 2: return locale.describe("now", only_distance=only_distance) elif granularity == "minute": delta = sign * delta_second / self._SECS_PER_MINUTE elif granularity == "hour": delta = sign * delta_second / self._SECS_PER_HOUR elif granularity == "day": delta = sign * delta_second / self._SECS_PER_DAY elif granularity == "week": delta = sign * delta_second / self._SECS_PER_WEEK elif granularity == "month": delta = sign * delta_second / self._SECS_PER_MONTH elif granularity == "quarter": delta = sign * delta_second / self._SECS_PER_QUARTER elif granularity == "year": delta = sign * delta_second / self._SECS_PER_YEAR else: raise ValueError( "Invalid level of granularity. " "Please select between 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter' or 'year'." ) if trunc(abs(delta)) != 1: granularity += "s" # type: ignore return locale.describe(granularity, delta, only_distance=only_distance) else: if not granularity: raise ValueError( "Empty granularity list provided. " "Please select one or more from 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter', 'year'." ) timeframes: List[Tuple[TimeFrameLiteral, float]] = [] def gather_timeframes(_delta: float, _frame: TimeFrameLiteral) -> float: if _frame in granularity: value = sign * _delta / self._SECS_MAP[_frame] _delta %= self._SECS_MAP[_frame] if trunc(abs(value)) != 1: timeframes.append( (cast(TimeFrameLiteral, _frame + "s"), value) ) else: timeframes.append((_frame, value)) return _delta delta = float(delta_second) frames: Tuple[TimeFrameLiteral, ...] = ( "year", "quarter", "month", "week", "day", "hour", "minute", "second", ) for frame in frames: delta = gather_timeframes(delta, frame) if len(timeframes) < len(granularity): raise ValueError( "Invalid level of granularity. " "Please select between 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter' or 'year'." ) return locale.describe_multi(timeframes, only_distance=only_distance) except KeyError as e: raise ValueError( f"Humanization of the {e} granularity is not currently translated in the {locale_name!r} locale. " "Please consider making a contribution to this locale." ) def dehumanize(self, input_string: str, locale: str = "en_us") -> "Arrow": """Returns a new :class:`Arrow ` object, that represents the time difference relative to the attrbiutes of the :class:`Arrow ` object. :param timestring: a ``str`` representing a humanized relative time. :param locale: (optional) a ``str`` specifying a locale. Defaults to 'en-us'. Usage:: >>> arw = arrow.utcnow() >>> arw >>> earlier = arw.dehumanize("2 days ago") >>> earlier >>> arw = arrow.utcnow() >>> arw >>> later = arw.dehumanize("in a month") >>> later """ # Create a locale object based off given local locale_obj = locales.get_locale(locale) # Check to see if locale is supported normalized_locale_name = locale.lower().replace("_", "-") if normalized_locale_name not in DEHUMANIZE_LOCALES: raise ValueError( f"Dehumanize does not currently support the {locale} locale, please consider making a contribution to add support for this locale." ) current_time = self.fromdatetime(self._datetime) # Create an object containing the relative time info time_object_info = dict.fromkeys( ["seconds", "minutes", "hours", "days", "weeks", "months", "years"], 0 ) # Create an object representing if unit has been seen unit_visited = dict.fromkeys( ["now", "seconds", "minutes", "hours", "days", "weeks", "months", "years"], False, ) # Create a regex pattern object for numbers num_pattern = re.compile(r"\d+") # Search input string for each time unit within locale for unit, unit_object in locale_obj.timeframes.items(): # Need to check the type of unit_object to create the correct dictionary if isinstance(unit_object, Mapping): strings_to_search = unit_object else: strings_to_search = {unit: str(unit_object)} # Search for any matches that exist for that locale's unit. # Needs to cycle all through strings as some locales have strings that # could overlap in a regex match, since input validation isn't being performed. for time_delta, time_string in strings_to_search.items(): # Replace {0} with regex \d representing digits search_string = str(time_string) search_string = search_string.format(r"\d+") # Create search pattern and find within string pattern = re.compile(fr"{search_string}") match = pattern.search(input_string) # If there is no match continue to next iteration if not match: continue match_string = match.group() num_match = num_pattern.search(match_string) # If no number matches # Need for absolute value as some locales have signs included in their objects if not num_match: change_value = ( 1 if not time_delta.isnumeric() else abs(int(time_delta)) ) else: change_value = int(num_match.group()) # No time to update if now is the unit if unit == "now": unit_visited[unit] = True continue # Add change value to the correct unit (incorporates the plurality that exists within timeframe i.e second v.s seconds) time_unit_to_change = str(unit) time_unit_to_change += ( "s" if (str(time_unit_to_change)[-1] != "s") else "" ) time_object_info[time_unit_to_change] = change_value unit_visited[time_unit_to_change] = True # Assert error if string does not modify any units if not any([True for k, v in unit_visited.items() if v]): raise ValueError( "Input string not valid. Note: Some locales do not support the week granulairty in Arrow. " "If you are attempting to use the week granularity on an unsupported locale, this could be the cause of this error." ) # Sign logic future_string = locale_obj.future future_string = future_string.format(".*") future_pattern = re.compile(fr"^{future_string}$") future_pattern_match = future_pattern.findall(input_string) past_string = locale_obj.past past_string = past_string.format(".*") past_pattern = re.compile(fr"^{past_string}$") past_pattern_match = past_pattern.findall(input_string) # If a string contains the now unit, there will be no relative units, hence the need to check if the now unit # was visited before raising a ValueError if past_pattern_match: sign_val = -1 elif future_pattern_match: sign_val = 1 elif unit_visited["now"]: sign_val = 0 else: raise ValueError( "Invalid input String. String does not contain any relative time information. " "String should either represent a time in the future or a time in the past. " "Ex: 'in 5 seconds' or '5 seconds ago'." ) time_changes = {k: sign_val * v for k, v in time_object_info.items()} return current_time.shift(**time_changes) # query functions def is_between( self, start: "Arrow", end: "Arrow", bounds: _BOUNDS = "()", ) -> bool: """Returns a boolean denoting whether the :class:`Arrow ` object is between the start and end limits. :param start: an :class:`Arrow ` object. :param end: an :class:`Arrow ` object. :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies whether to include or exclude the start and end values in the range. '(' excludes the start, '[' includes the start, ')' excludes the end, and ']' includes the end. If the bounds are not specified, the default bound '()' is used. Usage:: >>> start = arrow.get(datetime(2013, 5, 5, 12, 30, 10)) >>> end = arrow.get(datetime(2013, 5, 5, 12, 30, 36)) >>> arrow.get(datetime(2013, 5, 5, 12, 30, 27)).is_between(start, end) True >>> start = arrow.get(datetime(2013, 5, 5)) >>> end = arrow.get(datetime(2013, 5, 8)) >>> arrow.get(datetime(2013, 5, 8)).is_between(start, end, '[]') True >>> start = arrow.get(datetime(2013, 5, 5)) >>> end = arrow.get(datetime(2013, 5, 8)) >>> arrow.get(datetime(2013, 5, 8)).is_between(start, end, '[)') False """ util.validate_bounds(bounds) if not isinstance(start, Arrow): raise TypeError( f"Cannot parse start date argument type of {type(start)!r}." ) if not isinstance(end, Arrow): raise TypeError(f"Cannot parse end date argument type of {type(start)!r}.") include_start = bounds[0] == "[" include_end = bounds[1] == "]" target_ts = self.float_timestamp start_ts = start.float_timestamp end_ts = end.float_timestamp return ( (start_ts <= target_ts <= end_ts) and (include_start or start_ts < target_ts) and (include_end or target_ts < end_ts) ) # datetime methods def date(self) -> date: """Returns a ``date`` object with the same year, month and day. Usage:: >>> arrow.utcnow().date() datetime.date(2019, 1, 23) """ return self._datetime.date() def time(self) -> dt_time: """Returns a ``time`` object with the same hour, minute, second, microsecond. Usage:: >>> arrow.utcnow().time() datetime.time(12, 15, 34, 68352) """ return self._datetime.time() def timetz(self) -> dt_time: """Returns a ``time`` object with the same hour, minute, second, microsecond and tzinfo. Usage:: >>> arrow.utcnow().timetz() datetime.time(12, 5, 18, 298893, tzinfo=tzutc()) """ return self._datetime.timetz() def astimezone(self, tz: Optional[dt_tzinfo]) -> dt_datetime: """Returns a ``datetime`` object, converted to the specified timezone. :param tz: a ``tzinfo`` object. Usage:: >>> pacific=arrow.now('US/Pacific') >>> nyc=arrow.now('America/New_York').tzinfo >>> pacific.astimezone(nyc) datetime.datetime(2019, 1, 20, 10, 24, 22, 328172, tzinfo=tzfile('/usr/share/zoneinfo/America/New_York')) """ return self._datetime.astimezone(tz) def utcoffset(self) -> Optional[timedelta]: """Returns a ``timedelta`` object representing the whole number of minutes difference from UTC time. Usage:: >>> arrow.now('US/Pacific').utcoffset() datetime.timedelta(-1, 57600) """ return self._datetime.utcoffset() def dst(self) -> Optional[timedelta]: """Returns the daylight savings time adjustment. Usage:: >>> arrow.utcnow().dst() datetime.timedelta(0) """ return self._datetime.dst() def timetuple(self) -> struct_time: """Returns a ``time.struct_time``, in the current timezone. Usage:: >>> arrow.utcnow().timetuple() time.struct_time(tm_year=2019, tm_mon=1, tm_mday=20, tm_hour=15, tm_min=17, tm_sec=8, tm_wday=6, tm_yday=20, tm_isdst=0) """ return self._datetime.timetuple() def utctimetuple(self) -> struct_time: """Returns a ``time.struct_time``, in UTC time. Usage:: >>> arrow.utcnow().utctimetuple() time.struct_time(tm_year=2019, tm_mon=1, tm_mday=19, tm_hour=21, tm_min=41, tm_sec=7, tm_wday=5, tm_yday=19, tm_isdst=0) """ return self._datetime.utctimetuple() def toordinal(self) -> int: """Returns the proleptic Gregorian ordinal of the date. Usage:: >>> arrow.utcnow().toordinal() 737078 """ return self._datetime.toordinal() def weekday(self) -> int: """Returns the day of the week as an integer (0-6). Usage:: >>> arrow.utcnow().weekday() 5 """ return self._datetime.weekday() def isoweekday(self) -> int: """Returns the ISO day of the week as an integer (1-7). Usage:: >>> arrow.utcnow().isoweekday() 6 """ return self._datetime.isoweekday() def isocalendar(self) -> Tuple[int, int, int]: """Returns a 3-tuple, (ISO year, ISO week number, ISO weekday). Usage:: >>> arrow.utcnow().isocalendar() (2019, 3, 6) """ return self._datetime.isocalendar() def isoformat(self, sep: str = "T", timespec: str = "auto") -> str: """Returns an ISO 8601 formatted representation of the date and time. Usage:: >>> arrow.utcnow().isoformat() '2019-01-19T18:30:52.442118+00:00' """ return self._datetime.isoformat(sep, timespec) def ctime(self) -> str: """Returns a ctime formatted representation of the date and time. Usage:: >>> arrow.utcnow().ctime() 'Sat Jan 19 18:26:50 2019' """ return self._datetime.ctime() def strftime(self, format: str) -> str: """Formats in the style of ``datetime.strftime``. :param format: the format string. Usage:: >>> arrow.utcnow().strftime('%d-%m-%Y %H:%M:%S') '23-01-2019 12:28:17' """ return self._datetime.strftime(format) def for_json(self) -> str: """Serializes for the ``for_json`` protocol of simplejson. Usage:: >>> arrow.utcnow().for_json() '2019-01-19T18:25:36.760079+00:00' """ return self.isoformat() # math def __add__(self, other: Any) -> "Arrow": if isinstance(other, (timedelta, relativedelta)): return self.fromdatetime(self._datetime + other, self._datetime.tzinfo) return NotImplemented def __radd__(self, other: Union[timedelta, relativedelta]) -> "Arrow": return self.__add__(other) @overload def __sub__(self, other: Union[timedelta, relativedelta]) -> "Arrow": pass # pragma: no cover @overload def __sub__(self, other: Union[dt_datetime, "Arrow"]) -> timedelta: pass # pragma: no cover def __sub__(self, other: Any) -> Union[timedelta, "Arrow"]: if isinstance(other, (timedelta, relativedelta)): return self.fromdatetime(self._datetime - other, self._datetime.tzinfo) elif isinstance(other, dt_datetime): return self._datetime - other elif isinstance(other, Arrow): return self._datetime - other._datetime return NotImplemented def __rsub__(self, other: Any) -> timedelta: if isinstance(other, dt_datetime): return other - self._datetime return NotImplemented # comparisons def __eq__(self, other: Any) -> bool: if not isinstance(other, (Arrow, dt_datetime)): return False return self._datetime == self._get_datetime(other) def __ne__(self, other: Any) -> bool: if not isinstance(other, (Arrow, dt_datetime)): return True return not self.__eq__(other) def __gt__(self, other: Any) -> bool: if not isinstance(other, (Arrow, dt_datetime)): return NotImplemented return self._datetime > self._get_datetime(other) def __ge__(self, other: Any) -> bool: if not isinstance(other, (Arrow, dt_datetime)): return NotImplemented return self._datetime >= self._get_datetime(other) def __lt__(self, other: Any) -> bool: if not isinstance(other, (Arrow, dt_datetime)): return NotImplemented return self._datetime < self._get_datetime(other) def __le__(self, other: Any) -> bool: if not isinstance(other, (Arrow, dt_datetime)): return NotImplemented return self._datetime <= self._get_datetime(other) # internal methods @staticmethod def _get_tzinfo(tz_expr: Optional[TZ_EXPR]) -> dt_tzinfo: """Get normalized tzinfo object from various inputs.""" if tz_expr is None: return dateutil_tz.tzutc() if isinstance(tz_expr, dt_tzinfo): return tz_expr else: try: return parser.TzinfoParser.parse(tz_expr) except parser.ParserError: raise ValueError(f"{tz_expr!r} not recognized as a timezone.") @classmethod def _get_datetime( cls, expr: Union["Arrow", dt_datetime, int, float, str] ) -> dt_datetime: """Get datetime object from a specified expression.""" if isinstance(expr, Arrow): return expr.datetime elif isinstance(expr, dt_datetime): return expr elif util.is_timestamp(expr): timestamp = float(expr) return cls.utcfromtimestamp(timestamp).datetime else: raise ValueError(f"{expr!r} not recognized as a datetime or timestamp.") @classmethod def _get_frames(cls, name: _T_FRAMES) -> Tuple[str, str, int]: """Finds relevant timeframe and steps for use in range and span methods. Returns a 3 element tuple in the form (frame, plural frame, step), for example ("day", "days", 1) """ if name in cls._ATTRS: return name, f"{name}s", 1 elif name[-1] == "s" and name[:-1] in cls._ATTRS: return name[:-1], name, 1 elif name in ["week", "weeks"]: return "week", "weeks", 1 elif name in ["quarter", "quarters"]: return "quarter", "months", 3 else: supported = ", ".join( [ "year(s)", "month(s)", "day(s)", "hour(s)", "minute(s)", "second(s)", "microsecond(s)", "week(s)", "quarter(s)", ] ) raise ValueError( f"Range or span over frame {name} not supported. Supported frames: {supported}." ) @classmethod def _get_iteration_params(cls, end: Any, limit: Optional[int]) -> Tuple[Any, int]: """Sets default end and limit values for range method.""" if end is None: if limit is None: raise ValueError("One of 'end' or 'limit' is required.") return cls.max, limit else: if limit is None: return end, sys.maxsize return end, limit @staticmethod def _is_last_day_of_month(date: "Arrow") -> bool: """Returns a boolean indicating whether the datetime is the last day of the month.""" return date.day == calendar.monthrange(date.year, date.month)[1] Arrow.min = Arrow.fromdatetime(dt_datetime.min) Arrow.max = Arrow.fromdatetime(dt_datetime.max) python-arrow-1.2.1/arrow/constants.py000066400000000000000000000055301415100123600177000ustar00rootroot00000000000000"""Constants used internally in arrow.""" import sys from datetime import datetime if sys.version_info < (3, 8): # pragma: no cover from typing_extensions import Final else: from typing import Final # pragma: no cover # datetime.max.timestamp() errors on Windows, so we must hardcode # the highest possible datetime value that can output a timestamp. # tl;dr platform-independent max timestamps are hard to form # See: https://stackoverflow.com/q/46133223 try: # Get max timestamp. Works on POSIX-based systems like Linux and macOS, # but will trigger an OverflowError, ValueError, or OSError on Windows _MAX_TIMESTAMP = datetime.max.timestamp() except (OverflowError, ValueError, OSError): # pragma: no cover # Fallback for Windows and 32-bit systems if initial max timestamp call fails # Must get max value of ctime on Windows based on architecture (x32 vs x64) # https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/ctime-ctime32-ctime64-wctime-wctime32-wctime64 # Note: this may occur on both 32-bit Linux systems (issue #930) along with Windows systems is_64bits = sys.maxsize > 2 ** 32 _MAX_TIMESTAMP = ( datetime(3000, 1, 1, 23, 59, 59, 999999).timestamp() if is_64bits else datetime(2038, 1, 1, 23, 59, 59, 999999).timestamp() ) MAX_TIMESTAMP: Final[float] = _MAX_TIMESTAMP MAX_TIMESTAMP_MS: Final[float] = MAX_TIMESTAMP * 1000 MAX_TIMESTAMP_US: Final[float] = MAX_TIMESTAMP * 1_000_000 MAX_ORDINAL: Final[int] = datetime.max.toordinal() MIN_ORDINAL: Final[int] = 1 DEFAULT_LOCALE: Final[str] = "en-us" # Supported dehumanize locales DEHUMANIZE_LOCALES = { "en", "en-us", "en-gb", "en-au", "en-be", "en-jp", "en-za", "en-ca", "en-ph", "fr", "fr-fr", "fr-ca", "it", "it-it", "es", "es-es", "el", "el-gr", "ja", "ja-jp", "se", "se-fi", "se-no", "se-se", "sv", "sv-se", "fi", "fi-fi", "zh", "zh-cn", "zh-tw", "zh-hk", "nl", "nl-nl", "af", "de", "de-de", "de-ch", "de-at", "nb", "nb-no", "nn", "nn-no", "pt", "pt-pt", "pt-br", "tl", "tl-ph", "vi", "vi-vn", "tr", "tr-tr", "az", "az-az", "da", "da-dk", "ml", "hi", "fa", "fa-ir", "mr", "ca", "ca-es", "ca-ad", "ca-fr", "ca-it", "eo", "eo-xx", "bn", "bn-bd", "bn-in", "rm", "rm-ch", "ro", "ro-ro", "sl", "sl-si", "id", "id-id", "ne", "ne-np", "ee", "et", "sw", "sw-ke", "sw-tz", "la", "la-va", "lt", "lt-lt", "ms", "ms-my", "ms-bn", "or", "or-in", "lb", "lb-lu", "zu", "zu-za", "sq", "sq-al", "ta", "ta-in", "ta-lk", "ur", "ur-pk", } python-arrow-1.2.1/arrow/factory.py000066400000000000000000000262531415100123600173400ustar00rootroot00000000000000""" Implements the :class:`ArrowFactory ` class, providing factory methods for common :class:`Arrow ` construction scenarios. """ import calendar from datetime import date, datetime from datetime import tzinfo as dt_tzinfo from decimal import Decimal from time import struct_time from typing import Any, List, Optional, Tuple, Type, Union, overload from dateutil import tz as dateutil_tz from arrow import parser from arrow.arrow import TZ_EXPR, Arrow from arrow.constants import DEFAULT_LOCALE from arrow.util import is_timestamp, iso_to_gregorian class ArrowFactory: """A factory for generating :class:`Arrow ` objects. :param type: (optional) the :class:`Arrow `-based class to construct from. Defaults to :class:`Arrow `. """ type: Type[Arrow] def __init__(self, type: Type[Arrow] = Arrow) -> None: self.type = type @overload def get( self, *, locale: str = DEFAULT_LOCALE, tzinfo: Optional[TZ_EXPR] = None, normalize_whitespace: bool = False, ) -> Arrow: ... # pragma: no cover @overload def get( self, __obj: Union[ Arrow, datetime, date, struct_time, dt_tzinfo, int, float, str, Tuple[int, int, int], ], *, locale: str = DEFAULT_LOCALE, tzinfo: Optional[TZ_EXPR] = None, normalize_whitespace: bool = False, ) -> Arrow: ... # pragma: no cover @overload def get( self, __arg1: Union[datetime, date], __arg2: TZ_EXPR, *, locale: str = DEFAULT_LOCALE, tzinfo: Optional[TZ_EXPR] = None, normalize_whitespace: bool = False, ) -> Arrow: ... # pragma: no cover @overload def get( self, __arg1: str, __arg2: Union[str, List[str]], *, locale: str = DEFAULT_LOCALE, tzinfo: Optional[TZ_EXPR] = None, normalize_whitespace: bool = False, ) -> Arrow: ... # pragma: no cover def get(self, *args: Any, **kwargs: Any) -> Arrow: """Returns an :class:`Arrow ` object based on flexible inputs. :param locale: (optional) a ``str`` specifying a locale for the parser. Defaults to 'en-us'. :param tzinfo: (optional) a :ref:`timezone expression ` or tzinfo object. Replaces the timezone unless using an input form that is explicitly UTC or specifies the timezone in a positional argument. Defaults to UTC. :param normalize_whitespace: (optional) a ``bool`` specifying whether or not to normalize redundant whitespace (spaces, tabs, and newlines) in a datetime string before parsing. Defaults to false. Usage:: >>> import arrow **No inputs** to get current UTC time:: >>> arrow.get() **One** :class:`Arrow ` object, to get a copy. >>> arw = arrow.utcnow() >>> arrow.get(arw) **One** ``float`` or ``int``, convertible to a floating-point timestamp, to get that timestamp in UTC:: >>> arrow.get(1367992474.293378) >>> arrow.get(1367992474) **One** ISO 8601-formatted ``str``, to parse it:: >>> arrow.get('2013-09-29T01:26:43.830580') **One** ISO 8601-formatted ``str``, in basic format, to parse it:: >>> arrow.get('20160413T133656.456289') **One** ``tzinfo``, to get the current time **converted** to that timezone:: >>> arrow.get(tz.tzlocal()) **One** naive ``datetime``, to get that datetime in UTC:: >>> arrow.get(datetime(2013, 5, 5)) **One** aware ``datetime``, to get that datetime:: >>> arrow.get(datetime(2013, 5, 5, tzinfo=tz.tzlocal())) **One** naive ``date``, to get that date in UTC:: >>> arrow.get(date(2013, 5, 5)) **One** time.struct time:: >>> arrow.get(gmtime(0)) **One** iso calendar ``tuple``, to get that week date in UTC:: >>> arrow.get((2013, 18, 7)) **Two** arguments, a naive or aware ``datetime``, and a replacement :ref:`timezone expression `:: >>> arrow.get(datetime(2013, 5, 5), 'US/Pacific') **Two** arguments, a naive ``date``, and a replacement :ref:`timezone expression `:: >>> arrow.get(date(2013, 5, 5), 'US/Pacific') **Two** arguments, both ``str``, to parse the first according to the format of the second:: >>> arrow.get('2013-05-05 12:30:45 America/Chicago', 'YYYY-MM-DD HH:mm:ss ZZZ') **Two** arguments, first a ``str`` to parse and second a ``list`` of formats to try:: >>> arrow.get('2013-05-05 12:30:45', ['MM/DD/YYYY', 'YYYY-MM-DD HH:mm:ss']) **Three or more** arguments, as for the direct constructor of an ``Arrow`` object:: >>> arrow.get(2013, 5, 5, 12, 30, 45) """ arg_count = len(args) locale = kwargs.pop("locale", DEFAULT_LOCALE) tz = kwargs.get("tzinfo", None) normalize_whitespace = kwargs.pop("normalize_whitespace", False) # if kwargs given, send to constructor unless only tzinfo provided if len(kwargs) > 1: arg_count = 3 # tzinfo kwarg is not provided if len(kwargs) == 1 and tz is None: arg_count = 3 # () -> now, @ tzinfo or utc if arg_count == 0: if isinstance(tz, str): tz = parser.TzinfoParser.parse(tz) return self.type.now(tzinfo=tz) if isinstance(tz, dt_tzinfo): return self.type.now(tzinfo=tz) return self.type.utcnow() if arg_count == 1: arg = args[0] if isinstance(arg, Decimal): arg = float(arg) # (None) -> raises an exception if arg is None: raise TypeError("Cannot parse argument of type None.") # try (int, float) -> from timestamp @ tzinfo elif not isinstance(arg, str) and is_timestamp(arg): if tz is None: # set to UTC by default tz = dateutil_tz.tzutc() return self.type.fromtimestamp(arg, tzinfo=tz) # (Arrow) -> from the object's datetime @ tzinfo elif isinstance(arg, Arrow): return self.type.fromdatetime(arg.datetime, tzinfo=tz) # (datetime) -> from datetime @ tzinfo elif isinstance(arg, datetime): return self.type.fromdatetime(arg, tzinfo=tz) # (date) -> from date @ tzinfo elif isinstance(arg, date): return self.type.fromdate(arg, tzinfo=tz) # (tzinfo) -> now @ tzinfo elif isinstance(arg, dt_tzinfo): return self.type.now(tzinfo=arg) # (str) -> parse @ tzinfo elif isinstance(arg, str): dt = parser.DateTimeParser(locale).parse_iso(arg, normalize_whitespace) return self.type.fromdatetime(dt, tzinfo=tz) # (struct_time) -> from struct_time elif isinstance(arg, struct_time): return self.type.utcfromtimestamp(calendar.timegm(arg)) # (iso calendar) -> convert then from date @ tzinfo elif isinstance(arg, tuple) and len(arg) == 3: d = iso_to_gregorian(*arg) return self.type.fromdate(d, tzinfo=tz) else: raise TypeError(f"Cannot parse single argument of type {type(arg)!r}.") elif arg_count == 2: arg_1, arg_2 = args[0], args[1] if isinstance(arg_1, datetime): # (datetime, tzinfo/str) -> fromdatetime @ tzinfo if isinstance(arg_2, (dt_tzinfo, str)): return self.type.fromdatetime(arg_1, tzinfo=arg_2) else: raise TypeError( f"Cannot parse two arguments of types 'datetime', {type(arg_2)!r}." ) elif isinstance(arg_1, date): # (date, tzinfo/str) -> fromdate @ tzinfo if isinstance(arg_2, (dt_tzinfo, str)): return self.type.fromdate(arg_1, tzinfo=arg_2) else: raise TypeError( f"Cannot parse two arguments of types 'date', {type(arg_2)!r}." ) # (str, format) -> parse @ tzinfo elif isinstance(arg_1, str) and isinstance(arg_2, (str, list)): dt = parser.DateTimeParser(locale).parse( args[0], args[1], normalize_whitespace ) return self.type.fromdatetime(dt, tzinfo=tz) else: raise TypeError( f"Cannot parse two arguments of types {type(arg_1)!r} and {type(arg_2)!r}." ) # 3+ args -> datetime-like via constructor else: return self.type(*args, **kwargs) def utcnow(self) -> Arrow: """Returns an :class:`Arrow ` object, representing "now" in UTC time. Usage:: >>> import arrow >>> arrow.utcnow() """ return self.type.utcnow() def now(self, tz: Optional[TZ_EXPR] = None) -> Arrow: """Returns an :class:`Arrow ` object, representing "now" in the given timezone. :param tz: (optional) A :ref:`timezone expression `. Defaults to local time. Usage:: >>> import arrow >>> arrow.now() >>> arrow.now('US/Pacific') >>> arrow.now('+02:00') >>> arrow.now('local') """ if tz is None: tz = dateutil_tz.tzlocal() elif not isinstance(tz, dt_tzinfo): tz = parser.TzinfoParser.parse(tz) return self.type.now(tz) python-arrow-1.2.1/arrow/formatter.py000066400000000000000000000122271415100123600176700ustar00rootroot00000000000000"""Provides the :class:`Arrow ` class, an improved formatter for datetimes.""" import re import sys from datetime import datetime, timedelta from typing import Optional, Pattern, cast from dateutil import tz as dateutil_tz from arrow import locales from arrow.constants import DEFAULT_LOCALE if sys.version_info < (3, 8): # pragma: no cover from typing_extensions import Final else: from typing import Final # pragma: no cover FORMAT_ATOM: Final[str] = "YYYY-MM-DD HH:mm:ssZZ" FORMAT_COOKIE: Final[str] = "dddd, DD-MMM-YYYY HH:mm:ss ZZZ" FORMAT_RFC822: Final[str] = "ddd, DD MMM YY HH:mm:ss Z" FORMAT_RFC850: Final[str] = "dddd, DD-MMM-YY HH:mm:ss ZZZ" FORMAT_RFC1036: Final[str] = "ddd, DD MMM YY HH:mm:ss Z" FORMAT_RFC1123: Final[str] = "ddd, DD MMM YYYY HH:mm:ss Z" FORMAT_RFC2822: Final[str] = "ddd, DD MMM YYYY HH:mm:ss Z" FORMAT_RFC3339: Final[str] = "YYYY-MM-DD HH:mm:ssZZ" FORMAT_RSS: Final[str] = "ddd, DD MMM YYYY HH:mm:ss Z" FORMAT_W3C: Final[str] = "YYYY-MM-DD HH:mm:ssZZ" class DateTimeFormatter: # This pattern matches characters enclosed in square brackets are matched as # an atomic group. For more info on atomic groups and how to they are # emulated in Python's re library, see https://stackoverflow.com/a/13577411/2701578 _FORMAT_RE: Final[Pattern[str]] = re.compile( r"(\[(?:(?=(?P[^]]))(?P=literal))*\]|YYY?Y?|MM?M?M?|Do|DD?D?D?|d?dd?d?|HH?|hh?|mm?|ss?|SS?S?S?S?S?|ZZ?Z?|a|A|X|x|W)" ) locale: locales.Locale def __init__(self, locale: str = DEFAULT_LOCALE) -> None: self.locale = locales.get_locale(locale) def format(cls, dt: datetime, fmt: str) -> str: # FIXME: _format_token() is nullable return cls._FORMAT_RE.sub( lambda m: cast(str, cls._format_token(dt, m.group(0))), fmt ) def _format_token(self, dt: datetime, token: Optional[str]) -> Optional[str]: if token and token.startswith("[") and token.endswith("]"): return token[1:-1] if token == "YYYY": return self.locale.year_full(dt.year) if token == "YY": return self.locale.year_abbreviation(dt.year) if token == "MMMM": return self.locale.month_name(dt.month) if token == "MMM": return self.locale.month_abbreviation(dt.month) if token == "MM": return f"{dt.month:02d}" if token == "M": return f"{dt.month}" if token == "DDDD": return f"{dt.timetuple().tm_yday:03d}" if token == "DDD": return f"{dt.timetuple().tm_yday}" if token == "DD": return f"{dt.day:02d}" if token == "D": return f"{dt.day}" if token == "Do": return self.locale.ordinal_number(dt.day) if token == "dddd": return self.locale.day_name(dt.isoweekday()) if token == "ddd": return self.locale.day_abbreviation(dt.isoweekday()) if token == "d": return f"{dt.isoweekday()}" if token == "HH": return f"{dt.hour:02d}" if token == "H": return f"{dt.hour}" if token == "hh": return f"{dt.hour if 0 < dt.hour < 13 else abs(dt.hour - 12):02d}" if token == "h": return f"{dt.hour if 0 < dt.hour < 13 else abs(dt.hour - 12)}" if token == "mm": return f"{dt.minute:02d}" if token == "m": return f"{dt.minute}" if token == "ss": return f"{dt.second:02d}" if token == "s": return f"{dt.second}" if token == "SSSSSS": return f"{dt.microsecond:06d}" if token == "SSSSS": return f"{dt.microsecond // 10:05d}" if token == "SSSS": return f"{dt.microsecond // 100:04d}" if token == "SSS": return f"{dt.microsecond // 1000:03d}" if token == "SS": return f"{dt.microsecond // 10000:02d}" if token == "S": return f"{dt.microsecond // 100000}" if token == "X": return f"{dt.timestamp()}" if token == "x": return f"{dt.timestamp() * 1_000_000:.0f}" if token == "ZZZ": return dt.tzname() if token in ["ZZ", "Z"]: separator = ":" if token == "ZZ" else "" tz = dateutil_tz.tzutc() if dt.tzinfo is None else dt.tzinfo # `dt` must be aware object. Otherwise, this line will raise AttributeError # https://github.com/arrow-py/arrow/pull/883#discussion_r529866834 # datetime awareness: https://docs.python.org/3/library/datetime.html#aware-and-naive-objects total_minutes = int(cast(timedelta, tz.utcoffset(dt)).total_seconds() / 60) sign = "+" if total_minutes >= 0 else "-" total_minutes = abs(total_minutes) hour, minute = divmod(total_minutes, 60) return f"{sign}{hour:02d}{separator}{minute:02d}" if token in ("a", "A"): return self.locale.meridian(dt.hour, token) if token == "W": year, week, day = dt.isocalendar() return f"{year}-W{week:02d}-{day}" python-arrow-1.2.1/arrow/locales.py000066400000000000000000004176351415100123600173230ustar00rootroot00000000000000"""Provides internationalization for arrow in over 60 languages and dialects.""" import sys from math import trunc from typing import ( Any, ClassVar, Dict, List, Mapping, Optional, Sequence, Tuple, Type, Union, cast, ) if sys.version_info < (3, 8): # pragma: no cover from typing_extensions import Literal else: from typing import Literal # pragma: no cover TimeFrameLiteral = Literal[ "now", "second", "seconds", "minute", "minutes", "hour", "hours", "day", "days", "week", "weeks", "month", "months", "quarter", "quarters", "year", "years", ] _TimeFrameElements = Union[ str, Sequence[str], Mapping[str, str], Mapping[str, Sequence[str]] ] _locale_map: Dict[str, Type["Locale"]] = dict() def get_locale(name: str) -> "Locale": """Returns an appropriate :class:`Locale ` corresponding to an input locale name. :param name: the name of the locale. """ normalized_locale_name = name.lower().replace("_", "-") locale_cls = _locale_map.get(normalized_locale_name) if locale_cls is None: raise ValueError(f"Unsupported locale {normalized_locale_name!r}.") return locale_cls() def get_locale_by_class_name(name: str) -> "Locale": """Returns an appropriate :class:`Locale ` corresponding to an locale class name. :param name: the name of the locale class. """ locale_cls: Optional[Type[Locale]] = globals().get(name) if locale_cls is None: raise ValueError(f"Unsupported locale {name!r}.") return locale_cls() class Locale: """Represents locale-specific data and functionality.""" names: ClassVar[List[str]] = [] timeframes: ClassVar[Mapping[TimeFrameLiteral, _TimeFrameElements]] = { "now": "", "second": "", "seconds": "", "minute": "", "minutes": "", "hour": "", "hours": "", "day": "", "days": "", "week": "", "weeks": "", "month": "", "months": "", "quarter": "", "quarters": "", "year": "", "years": "", } meridians: ClassVar[Dict[str, str]] = {"am": "", "pm": "", "AM": "", "PM": ""} past: ClassVar[str] future: ClassVar[str] and_word: ClassVar[Optional[str]] = None month_names: ClassVar[List[str]] = [] month_abbreviations: ClassVar[List[str]] = [] day_names: ClassVar[List[str]] = [] day_abbreviations: ClassVar[List[str]] = [] ordinal_day_re: ClassVar[str] = r"(\d+)" _month_name_to_ordinal: Optional[Dict[str, int]] def __init_subclass__(cls, **kwargs: Any) -> None: for locale_name in cls.names: if locale_name in _locale_map: raise LookupError(f"Duplicated locale name: {locale_name}") _locale_map[locale_name.lower().replace("_", "-")] = cls def __init__(self) -> None: self._month_name_to_ordinal = None def describe( self, timeframe: TimeFrameLiteral, delta: Union[float, int] = 0, only_distance: bool = False, ) -> str: """Describes a delta within a timeframe in plain language. :param timeframe: a string representing a timeframe. :param delta: a quantity representing a delta in a timeframe. :param only_distance: return only distance eg: "11 seconds" without "in" or "ago" keywords """ humanized = self._format_timeframe(timeframe, trunc(delta)) if not only_distance: humanized = self._format_relative(humanized, timeframe, delta) return humanized def describe_multi( self, timeframes: Sequence[Tuple[TimeFrameLiteral, Union[int, float]]], only_distance: bool = False, ) -> str: """Describes a delta within multiple timeframes in plain language. :param timeframes: a list of string, quantity pairs each representing a timeframe and delta. :param only_distance: return only distance eg: "2 hours and 11 seconds" without "in" or "ago" keywords """ parts = [ self._format_timeframe(timeframe, trunc(delta)) for timeframe, delta in timeframes ] if self.and_word: parts.insert(-1, self.and_word) humanized = " ".join(parts) if not only_distance: humanized = self._format_relative(humanized, *timeframes[-1]) return humanized def day_name(self, day: int) -> str: """Returns the day name for a specified day of the week. :param day: the ``int`` day of the week (1-7). """ return self.day_names[day] def day_abbreviation(self, day: int) -> str: """Returns the day abbreviation for a specified day of the week. :param day: the ``int`` day of the week (1-7). """ return self.day_abbreviations[day] def month_name(self, month: int) -> str: """Returns the month name for a specified month of the year. :param month: the ``int`` month of the year (1-12). """ return self.month_names[month] def month_abbreviation(self, month: int) -> str: """Returns the month abbreviation for a specified month of the year. :param month: the ``int`` month of the year (1-12). """ return self.month_abbreviations[month] def month_number(self, name: str) -> Optional[int]: """Returns the month number for a month specified by name or abbreviation. :param name: the month name or abbreviation. """ if self._month_name_to_ordinal is None: self._month_name_to_ordinal = self._name_to_ordinal(self.month_names) self._month_name_to_ordinal.update( self._name_to_ordinal(self.month_abbreviations) ) return self._month_name_to_ordinal.get(name) def year_full(self, year: int) -> str: """Returns the year for specific locale if available :param year: the ``int`` year (4-digit) """ return f"{year:04d}" def year_abbreviation(self, year: int) -> str: """Returns the year for specific locale if available :param year: the ``int`` year (4-digit) """ return f"{year:04d}"[2:] def meridian(self, hour: int, token: Any) -> Optional[str]: """Returns the meridian indicator for a specified hour and format token. :param hour: the ``int`` hour of the day. :param token: the format token. """ if token == "a": return self.meridians["am"] if hour < 12 else self.meridians["pm"] if token == "A": return self.meridians["AM"] if hour < 12 else self.meridians["PM"] return None def ordinal_number(self, n: int) -> str: """Returns the ordinal format of a given integer :param n: an integer """ return self._ordinal_number(n) def _ordinal_number(self, n: int) -> str: return f"{n}" def _name_to_ordinal(self, lst: Sequence[str]) -> Dict[str, int]: return {elem.lower(): i for i, elem in enumerate(lst[1:], 1)} def _format_timeframe(self, timeframe: TimeFrameLiteral, delta: int) -> str: # TODO: remove cast return cast(str, self.timeframes[timeframe]).format(trunc(abs(delta))) def _format_relative( self, humanized: str, timeframe: TimeFrameLiteral, delta: Union[float, int], ) -> str: if timeframe == "now": return humanized direction = self.past if delta < 0 else self.future return direction.format(humanized) class EnglishLocale(Locale): names = [ "en", "en-us", "en-gb", "en-au", "en-be", "en-jp", "en-za", "en-ca", "en-ph", ] past = "{0} ago" future = "in {0}" and_word = "and" timeframes = { "now": "just now", "second": "a second", "seconds": "{0} seconds", "minute": "a minute", "minutes": "{0} minutes", "hour": "an hour", "hours": "{0} hours", "day": "a day", "days": "{0} days", "week": "a week", "weeks": "{0} weeks", "month": "a month", "months": "{0} months", "quarter": "a quarter", "quarters": "{0} quarters", "year": "a year", "years": "{0} years", } meridians = {"am": "am", "pm": "pm", "AM": "AM", "PM": "PM"} month_names = [ "", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", ] month_abbreviations = [ "", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", ] day_names = [ "", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday", ] day_abbreviations = ["", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] ordinal_day_re = r"((?P[2-3]?1(?=st)|[2-3]?2(?=nd)|[2-3]?3(?=rd)|[1-3]?[04-9](?=th)|1[1-3](?=th))(st|nd|rd|th))" def _ordinal_number(self, n: int) -> str: if n % 100 not in (11, 12, 13): remainder = abs(n) % 10 if remainder == 1: return f"{n}st" elif remainder == 2: return f"{n}nd" elif remainder == 3: return f"{n}rd" return f"{n}th" def describe( self, timeframe: TimeFrameLiteral, delta: Union[int, float] = 0, only_distance: bool = False, ) -> str: """Describes a delta within a timeframe in plain language. :param timeframe: a string representing a timeframe. :param delta: a quantity representing a delta in a timeframe. :param only_distance: return only distance eg: "11 seconds" without "in" or "ago" keywords """ humanized = super().describe(timeframe, delta, only_distance) if only_distance and timeframe == "now": humanized = "instantly" return humanized class ItalianLocale(Locale): names = ["it", "it-it"] past = "{0} fa" future = "tra {0}" and_word = "e" timeframes = { "now": "adesso", "second": "un secondo", "seconds": "{0} qualche secondo", "minute": "un minuto", "minutes": "{0} minuti", "hour": "un'ora", "hours": "{0} ore", "day": "un giorno", "days": "{0} giorni", "week": "una settimana,", "weeks": "{0} settimane", "month": "un mese", "months": "{0} mesi", "year": "un anno", "years": "{0} anni", } month_names = [ "", "gennaio", "febbraio", "marzo", "aprile", "maggio", "giugno", "luglio", "agosto", "settembre", "ottobre", "novembre", "dicembre", ] month_abbreviations = [ "", "gen", "feb", "mar", "apr", "mag", "giu", "lug", "ago", "set", "ott", "nov", "dic", ] day_names = [ "", "lunedì", "martedì", "mercoledì", "giovedì", "venerdì", "sabato", "domenica", ] day_abbreviations = ["", "lun", "mar", "mer", "gio", "ven", "sab", "dom"] ordinal_day_re = r"((?P[1-3]?[0-9](?=[ºª]))[ºª])" def _ordinal_number(self, n: int) -> str: return f"{n}º" class SpanishLocale(Locale): names = ["es", "es-es"] past = "hace {0}" future = "en {0}" and_word = "y" timeframes = { "now": "ahora", "second": "un segundo", "seconds": "{0} segundos", "minute": "un minuto", "minutes": "{0} minutos", "hour": "una hora", "hours": "{0} horas", "day": "un día", "days": "{0} días", "week": "una semana", "weeks": "{0} semanas", "month": "un mes", "months": "{0} meses", "year": "un año", "years": "{0} años", } meridians = {"am": "am", "pm": "pm", "AM": "AM", "PM": "PM"} month_names = [ "", "enero", "febrero", "marzo", "abril", "mayo", "junio", "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre", ] month_abbreviations = [ "", "ene", "feb", "mar", "abr", "may", "jun", "jul", "ago", "sep", "oct", "nov", "dic", ] day_names = [ "", "lunes", "martes", "miércoles", "jueves", "viernes", "sábado", "domingo", ] day_abbreviations = ["", "lun", "mar", "mie", "jue", "vie", "sab", "dom"] ordinal_day_re = r"((?P[1-3]?[0-9](?=[ºª]))[ºª])" def _ordinal_number(self, n: int) -> str: return f"{n}º" class FrenchBaseLocale(Locale): past = "il y a {0}" future = "dans {0}" and_word = "et" timeframes = { "now": "maintenant", "second": "une seconde", "seconds": "{0} secondes", "minute": "une minute", "minutes": "{0} minutes", "hour": "une heure", "hours": "{0} heures", "day": "un jour", "days": "{0} jours", "week": "une semaine", "weeks": "{0} semaines", "month": "un mois", "months": "{0} mois", "year": "un an", "years": "{0} ans", } month_names = [ "", "janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre", ] day_names = [ "", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "dimanche", ] day_abbreviations = ["", "lun", "mar", "mer", "jeu", "ven", "sam", "dim"] ordinal_day_re = ( r"((?P\b1(?=er\b)|[1-3]?[02-9](?=e\b)|[1-3]1(?=e\b))(er|e)\b)" ) def _ordinal_number(self, n: int) -> str: if abs(n) == 1: return f"{n}er" return f"{n}e" class FrenchLocale(FrenchBaseLocale, Locale): names = ["fr", "fr-fr"] month_abbreviations = [ "", "janv", "févr", "mars", "avr", "mai", "juin", "juil", "août", "sept", "oct", "nov", "déc", ] class FrenchCanadianLocale(FrenchBaseLocale, Locale): names = ["fr-ca"] month_abbreviations = [ "", "janv", "févr", "mars", "avr", "mai", "juin", "juill", "août", "sept", "oct", "nov", "déc", ] class GreekLocale(Locale): names = ["el", "el-gr"] past = "{0} πριν" future = "σε {0}" and_word = "και" timeframes = { "now": "τώρα", "second": "ένα δεύτερο", "seconds": "{0} δευτερόλεπτα", "minute": "ένα λεπτό", "minutes": "{0} λεπτά", "hour": "μία ώρα", "hours": "{0} ώρες", "day": "μία μέρα", "days": "{0} μέρες", "week": "μία εβδομάδα", "weeks": "{0} εβδομάδες", "month": "ένα μήνα", "months": "{0} μήνες", "year": "ένα χρόνο", "years": "{0} χρόνια", } month_names = [ "", "Ιανουαρίου", "Φεβρουαρίου", "Μαρτίου", "Απριλίου", "Μαΐου", "Ιουνίου", "Ιουλίου", "Αυγούστου", "Σεπτεμβρίου", "Οκτωβρίου", "Νοεμβρίου", "Δεκεμβρίου", ] month_abbreviations = [ "", "Ιαν", "Φεβ", "Μαρ", "Απρ", "Μαϊ", "Ιον", "Ιολ", "Αυγ", "Σεπ", "Οκτ", "Νοε", "Δεκ", ] day_names = [ "", "Δευτέρα", "Τρίτη", "Τετάρτη", "Πέμπτη", "Παρασκευή", "Σάββατο", "Κυριακή", ] day_abbreviations = ["", "Δευ", "Τρι", "Τετ", "Πεμ", "Παρ", "Σαβ", "Κυρ"] class JapaneseLocale(Locale): names = ["ja", "ja-jp"] past = "{0}前" future = "{0}後" and_word = "" timeframes = { "now": "現在", "second": "1秒", "seconds": "{0}秒", "minute": "1分", "minutes": "{0}分", "hour": "1時間", "hours": "{0}時間", "day": "1日", "days": "{0}日", "week": "1週間", "weeks": "{0}週間", "month": "1ヶ月", "months": "{0}ヶ月", "year": "1年", "years": "{0}年", } month_names = [ "", "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月", ] month_abbreviations = [ "", " 1", " 2", " 3", " 4", " 5", " 6", " 7", " 8", " 9", "10", "11", "12", ] day_names = ["", "月曜日", "火曜日", "水曜日", "木曜日", "金曜日", "土曜日", "日曜日"] day_abbreviations = ["", "月", "火", "水", "木", "金", "土", "日"] class SwedishLocale(Locale): names = ["sv", "sv-se"] past = "för {0} sen" future = "om {0}" and_word = "och" timeframes = { "now": "just nu", "second": "en sekund", "seconds": "{0} sekunder", "minute": "en minut", "minutes": "{0} minuter", "hour": "en timme", "hours": "{0} timmar", "day": "en dag", "days": "{0} dagar", "week": "en vecka", "weeks": "{0} veckor", "month": "en månad", "months": "{0} månader", "year": "ett år", "years": "{0} år", } month_names = [ "", "januari", "februari", "mars", "april", "maj", "juni", "juli", "augusti", "september", "oktober", "november", "december", ] month_abbreviations = [ "", "jan", "feb", "mar", "apr", "maj", "jun", "jul", "aug", "sep", "okt", "nov", "dec", ] day_names = [ "", "måndag", "tisdag", "onsdag", "torsdag", "fredag", "lördag", "söndag", ] day_abbreviations = ["", "mån", "tis", "ons", "tor", "fre", "lör", "sön"] class FinnishLocale(Locale): names = ["fi", "fi-fi"] # The finnish grammar is very complex, and its hard to convert # 1-to-1 to something like English. past = "{0} sitten" future = "{0} kuluttua" timeframes: ClassVar[Mapping[TimeFrameLiteral, Union[str, Mapping[str, str]]]] = { "now": "juuri nyt", "second": "sekunti", "seconds": {"past": "{0} muutama sekunti", "future": "{0} muutaman sekunnin"}, "minute": {"past": "minuutti", "future": "minuutin"}, "minutes": {"past": "{0} minuuttia", "future": "{0} minuutin"}, "hour": {"past": "tunti", "future": "tunnin"}, "hours": {"past": "{0} tuntia", "future": "{0} tunnin"}, "day": "päivä", "days": {"past": "{0} päivää", "future": "{0} päivän"}, "month": {"past": "kuukausi", "future": "kuukauden"}, "months": {"past": "{0} kuukautta", "future": "{0} kuukauden"}, "year": {"past": "vuosi", "future": "vuoden"}, "years": {"past": "{0} vuotta", "future": "{0} vuoden"}, } # Months and days are lowercase in Finnish month_names = [ "", "tammikuu", "helmikuu", "maaliskuu", "huhtikuu", "toukokuu", "kesäkuu", "heinäkuu", "elokuu", "syyskuu", "lokakuu", "marraskuu", "joulukuu", ] month_abbreviations = [ "", "tammi", "helmi", "maalis", "huhti", "touko", "kesä", "heinä", "elo", "syys", "loka", "marras", "joulu", ] day_names = [ "", "maanantai", "tiistai", "keskiviikko", "torstai", "perjantai", "lauantai", "sunnuntai", ] day_abbreviations = ["", "ma", "ti", "ke", "to", "pe", "la", "su"] def _format_timeframe(self, timeframe: TimeFrameLiteral, delta: int) -> str: form = self.timeframes[timeframe] if isinstance(form, Mapping): if delta < 0: form = form["past"] else: form = form["future"] return form.format(abs(delta)) def _ordinal_number(self, n: int) -> str: return f"{n}." class ChineseCNLocale(Locale): names = ["zh", "zh-cn"] past = "{0}前" future = "{0}后" timeframes = { "now": "刚才", "second": "一秒", "seconds": "{0}秒", "minute": "1分钟", "minutes": "{0}分钟", "hour": "1小时", "hours": "{0}小时", "day": "1天", "days": "{0}天", "week": "一周", "weeks": "{0}周", "month": "1个月", "months": "{0}个月", "year": "1年", "years": "{0}年", } month_names = [ "", "一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月", ] month_abbreviations = [ "", " 1", " 2", " 3", " 4", " 5", " 6", " 7", " 8", " 9", "10", "11", "12", ] day_names = ["", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"] day_abbreviations = ["", "一", "二", "三", "四", "五", "六", "日"] class ChineseTWLocale(Locale): names = ["zh-tw"] past = "{0}前" future = "{0}後" and_word = "和" timeframes = { "now": "剛才", "second": "1秒", "seconds": "{0}秒", "minute": "1分鐘", "minutes": "{0}分鐘", "hour": "1小時", "hours": "{0}小時", "day": "1天", "days": "{0}天", "week": "1週", "weeks": "{0}週", "month": "1個月", "months": "{0}個月", "year": "1年", "years": "{0}年", } month_names = [ "", "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月", ] month_abbreviations = [ "", " 1", " 2", " 3", " 4", " 5", " 6", " 7", " 8", " 9", "10", "11", "12", ] day_names = ["", "週一", "週二", "週三", "週四", "週五", "週六", "週日"] day_abbreviations = ["", "一", "二", "三", "四", "五", "六", "日"] class HongKongLocale(Locale): names = ["zh-hk"] past = "{0}前" future = "{0}後" timeframes = { "now": "剛才", "second": "1秒", "seconds": "{0}秒", "minute": "1分鐘", "minutes": "{0}分鐘", "hour": "1小時", "hours": "{0}小時", "day": "1天", "days": "{0}天", "week": "1星期", "weeks": "{0}星期", "month": "1個月", "months": "{0}個月", "year": "1年", "years": "{0}年", } month_names = [ "", "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月", ] month_abbreviations = [ "", " 1", " 2", " 3", " 4", " 5", " 6", " 7", " 8", " 9", "10", "11", "12", ] day_names = ["", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"] day_abbreviations = ["", "一", "二", "三", "四", "五", "六", "日"] class KoreanLocale(Locale): names = ["ko", "ko-kr"] past = "{0} 전" future = "{0} 후" timeframes = { "now": "지금", "second": "1초", "seconds": "{0}초", "minute": "1분", "minutes": "{0}분", "hour": "한시간", "hours": "{0}시간", "day": "하루", "days": "{0}일", "week": "1주", "weeks": "{0}주", "month": "한달", "months": "{0}개월", "year": "1년", "years": "{0}년", } special_dayframes = { -3: "그끄제", -2: "그제", -1: "어제", 1: "내일", 2: "모레", 3: "글피", 4: "그글피", } special_yearframes = {-2: "제작년", -1: "작년", 1: "내년", 2: "내후년"} month_names = [ "", "1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월", ] month_abbreviations = [ "", " 1", " 2", " 3", " 4", " 5", " 6", " 7", " 8", " 9", "10", "11", "12", ] day_names = ["", "월요일", "화요일", "수요일", "목요일", "금요일", "토요일", "일요일"] day_abbreviations = ["", "월", "화", "수", "목", "금", "토", "일"] def _ordinal_number(self, n: int) -> str: ordinals = ["0", "첫", "두", "세", "네", "다섯", "여섯", "일곱", "여덟", "아홉", "열"] if n < len(ordinals): return f"{ordinals[n]}번째" return f"{n}번째" def _format_relative( self, humanized: str, timeframe: TimeFrameLiteral, delta: Union[float, int], ) -> str: if timeframe in ("day", "days"): special = self.special_dayframes.get(int(delta)) if special: return special elif timeframe in ("year", "years"): special = self.special_yearframes.get(int(delta)) if special: return special return super()._format_relative(humanized, timeframe, delta) # derived locale types & implementations. class DutchLocale(Locale): names = ["nl", "nl-nl"] past = "{0} geleden" future = "over {0}" timeframes = { "now": "nu", "second": "een seconde", "seconds": "{0} seconden", "minute": "een minuut", "minutes": "{0} minuten", "hour": "een uur", "hours": "{0} uur", "day": "een dag", "days": "{0} dagen", "week": "een week", "weeks": "{0} weken", "month": "een maand", "months": "{0} maanden", "year": "een jaar", "years": "{0} jaar", } # In Dutch names of months and days are not starting with a capital letter # like in the English language. month_names = [ "", "januari", "februari", "maart", "april", "mei", "juni", "juli", "augustus", "september", "oktober", "november", "december", ] month_abbreviations = [ "", "jan", "feb", "mrt", "apr", "mei", "jun", "jul", "aug", "sep", "okt", "nov", "dec", ] day_names = [ "", "maandag", "dinsdag", "woensdag", "donderdag", "vrijdag", "zaterdag", "zondag", ] day_abbreviations = ["", "ma", "di", "wo", "do", "vr", "za", "zo"] class SlavicBaseLocale(Locale): timeframes: ClassVar[Mapping[TimeFrameLiteral, Union[str, Mapping[str, str]]]] def _format_timeframe(self, timeframe: TimeFrameLiteral, delta: int) -> str: form = self.timeframes[timeframe] delta = abs(delta) if isinstance(form, Mapping): if delta % 10 == 1 and delta % 100 != 11: form = form["singular"] elif 2 <= delta % 10 <= 4 and (delta % 100 < 10 or delta % 100 >= 20): form = form["dual"] else: form = form["plural"] return form.format(delta) class BelarusianLocale(SlavicBaseLocale): names = ["be", "be-by"] past = "{0} таму" future = "праз {0}" timeframes: ClassVar[Mapping[TimeFrameLiteral, Union[str, Mapping[str, str]]]] = { "now": "зараз", "second": "секунду", "seconds": "{0} некалькі секунд", "minute": "хвіліну", "minutes": { "singular": "{0} хвіліну", "dual": "{0} хвіліны", "plural": "{0} хвілін", }, "hour": "гадзіну", "hours": { "singular": "{0} гадзіну", "dual": "{0} гадзіны", "plural": "{0} гадзін", }, "day": "дзень", "days": {"singular": "{0} дзень", "dual": "{0} дні", "plural": "{0} дзён"}, "month": "месяц", "months": { "singular": "{0} месяц", "dual": "{0} месяцы", "plural": "{0} месяцаў", }, "year": "год", "years": {"singular": "{0} год", "dual": "{0} гады", "plural": "{0} гадоў"}, } month_names = [ "", "студзеня", "лютага", "сакавіка", "красавіка", "траўня", "чэрвеня", "ліпеня", "жніўня", "верасня", "кастрычніка", "лістапада", "снежня", ] month_abbreviations = [ "", "студ", "лют", "сак", "крас", "трав", "чэрв", "ліп", "жнів", "вер", "каст", "ліст", "снеж", ] day_names = [ "", "панядзелак", "аўторак", "серада", "чацвер", "пятніца", "субота", "нядзеля", ] day_abbreviations = ["", "пн", "ат", "ср", "чц", "пт", "сб", "нд"] class PolishLocale(SlavicBaseLocale): names = ["pl", "pl-pl"] past = "{0} temu" future = "za {0}" # The nouns should be in genitive case (Polish: "dopełniacz") # in order to correctly form `past` & `future` expressions. timeframes: ClassVar[Mapping[TimeFrameLiteral, Union[str, Mapping[str, str]]]] = { "now": "teraz", "second": "sekundę", "seconds": { "singular": "{0} sekund", "dual": "{0} sekundy", "plural": "{0} sekund", }, "minute": "minutę", "minutes": { "singular": "{0} minut", "dual": "{0} minuty", "plural": "{0} minut", }, "hour": "godzinę", "hours": { "singular": "{0} godzin", "dual": "{0} godziny", "plural": "{0} godzin", }, "day": "dzień", "days": "{0} dni", "week": "tydzień", "weeks": { "singular": "{0} tygodni", "dual": "{0} tygodnie", "plural": "{0} tygodni", }, "month": "miesiąc", "months": { "singular": "{0} miesięcy", "dual": "{0} miesiące", "plural": "{0} miesięcy", }, "year": "rok", "years": {"singular": "{0} lat", "dual": "{0} lata", "plural": "{0} lat"}, } month_names = [ "", "styczeń", "luty", "marzec", "kwiecień", "maj", "czerwiec", "lipiec", "sierpień", "wrzesień", "październik", "listopad", "grudzień", ] month_abbreviations = [ "", "sty", "lut", "mar", "kwi", "maj", "cze", "lip", "sie", "wrz", "paź", "lis", "gru", ] day_names = [ "", "poniedziałek", "wtorek", "środa", "czwartek", "piątek", "sobota", "niedziela", ] day_abbreviations = ["", "Pn", "Wt", "Śr", "Czw", "Pt", "So", "Nd"] class RussianLocale(SlavicBaseLocale): names = ["ru", "ru-ru"] past = "{0} назад" future = "через {0}" timeframes: ClassVar[Mapping[TimeFrameLiteral, Union[str, Mapping[str, str]]]] = { "now": "сейчас", "second": "Второй", "seconds": "{0} несколько секунд", "minute": "минуту", "minutes": { "singular": "{0} минуту", "dual": "{0} минуты", "plural": "{0} минут", }, "hour": "час", "hours": {"singular": "{0} час", "dual": "{0} часа", "plural": "{0} часов"}, "day": "день", "days": {"singular": "{0} день", "dual": "{0} дня", "plural": "{0} дней"}, "week": "неделю", "weeks": { "singular": "{0} неделю", "dual": "{0} недели", "plural": "{0} недель", }, "month": "месяц", "months": { "singular": "{0} месяц", "dual": "{0} месяца", "plural": "{0} месяцев", }, "year": "год", "years": {"singular": "{0} год", "dual": "{0} года", "plural": "{0} лет"}, } month_names = [ "", "января", "февраля", "марта", "апреля", "мая", "июня", "июля", "августа", "сентября", "октября", "ноября", "декабря", ] month_abbreviations = [ "", "янв", "фев", "мар", "апр", "май", "июн", "июл", "авг", "сен", "окт", "ноя", "дек", ] day_names = [ "", "понедельник", "вторник", "среда", "четверг", "пятница", "суббота", "воскресенье", ] day_abbreviations = ["", "пн", "вт", "ср", "чт", "пт", "сб", "вс"] class AfrikaansLocale(Locale): names = ["af", "af-nl"] past = "{0} gelede" future = "in {0}" timeframes = { "now": "nou", "second": "n sekonde", "seconds": "{0} sekondes", "minute": "minuut", "minutes": "{0} minute", "hour": "uur", "hours": "{0} ure", "day": "een dag", "days": "{0} dae", "month": "een maand", "months": "{0} maande", "year": "een jaar", "years": "{0} jaar", } month_names = [ "", "Januarie", "Februarie", "Maart", "April", "Mei", "Junie", "Julie", "Augustus", "September", "Oktober", "November", "Desember", ] month_abbreviations = [ "", "Jan", "Feb", "Mrt", "Apr", "Mei", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Des", ] day_names = [ "", "Maandag", "Dinsdag", "Woensdag", "Donderdag", "Vrydag", "Saterdag", "Sondag", ] day_abbreviations = ["", "Ma", "Di", "Wo", "Do", "Vr", "Za", "So"] class BulgarianLocale(SlavicBaseLocale): names = ["bg", "bg-bg"] past = "{0} назад" future = "напред {0}" timeframes: ClassVar[Mapping[TimeFrameLiteral, Union[str, Mapping[str, str]]]] = { "now": "сега", "second": "секунда", "seconds": "{0} няколко секунди", "minute": "минута", "minutes": { "singular": "{0} минута", "dual": "{0} минути", "plural": "{0} минути", }, "hour": "час", "hours": {"singular": "{0} час", "dual": "{0} часа", "plural": "{0} часа"}, "day": "ден", "days": {"singular": "{0} ден", "dual": "{0} дни", "plural": "{0} дни"}, "month": "месец", "months": { "singular": "{0} месец", "dual": "{0} месеца", "plural": "{0} месеца", }, "year": "година", "years": { "singular": "{0} година", "dual": "{0} години", "plural": "{0} години", }, } month_names = [ "", "януари", "февруари", "март", "април", "май", "юни", "юли", "август", "септември", "октомври", "ноември", "декември", ] month_abbreviations = [ "", "ян", "февр", "март", "апр", "май", "юни", "юли", "авг", "септ", "окт", "ноем", "дек", ] day_names = [ "", "понеделник", "вторник", "сряда", "четвъртък", "петък", "събота", "неделя", ] day_abbreviations = ["", "пон", "вт", "ср", "четв", "пет", "съб", "нед"] class UkrainianLocale(SlavicBaseLocale): names = ["ua", "uk", "uk-ua"] past = "{0} тому" future = "за {0}" timeframes: ClassVar[Mapping[TimeFrameLiteral, Union[str, Mapping[str, str]]]] = { "now": "зараз", "second": "секунда", "seconds": "{0} кілька секунд", "minute": "хвилину", "minutes": { "singular": "{0} хвилину", "dual": "{0} хвилини", "plural": "{0} хвилин", }, "hour": "годину", "hours": { "singular": "{0} годину", "dual": "{0} години", "plural": "{0} годин", }, "day": "день", "days": {"singular": "{0} день", "dual": "{0} дні", "plural": "{0} днів"}, "month": "місяць", "months": { "singular": "{0} місяць", "dual": "{0} місяці", "plural": "{0} місяців", }, "year": "рік", "years": {"singular": "{0} рік", "dual": "{0} роки", "plural": "{0} років"}, } month_names = [ "", "січня", "лютого", "березня", "квітня", "травня", "червня", "липня", "серпня", "вересня", "жовтня", "листопада", "грудня", ] month_abbreviations = [ "", "січ", "лют", "бер", "квіт", "трав", "черв", "лип", "серп", "вер", "жовт", "лист", "груд", ] day_names = [ "", "понеділок", "вівторок", "середа", "четвер", "п’ятниця", "субота", "неділя", ] day_abbreviations = ["", "пн", "вт", "ср", "чт", "пт", "сб", "нд"] class MacedonianLocale(SlavicBaseLocale): names = ["mk", "mk-mk"] past = "пред {0}" future = "за {0}" timeframes: ClassVar[Mapping[TimeFrameLiteral, Union[str, Mapping[str, str]]]] = { "now": "сега", "second": "една секунда", "seconds": { "singular": "{0} секунда", "dual": "{0} секунди", "plural": "{0} секунди", }, "minute": "една минута", "minutes": { "singular": "{0} минута", "dual": "{0} минути", "plural": "{0} минути", }, "hour": "еден саат", "hours": {"singular": "{0} саат", "dual": "{0} саати", "plural": "{0} саати"}, "day": "еден ден", "days": {"singular": "{0} ден", "dual": "{0} дена", "plural": "{0} дена"}, "week": "една недела", "weeks": { "singular": "{0} недела", "dual": "{0} недели", "plural": "{0} недели", }, "month": "еден месец", "months": { "singular": "{0} месец", "dual": "{0} месеци", "plural": "{0} месеци", }, "year": "една година", "years": { "singular": "{0} година", "dual": "{0} години", "plural": "{0} години", }, } meridians = {"am": "дп", "pm": "пп", "AM": "претпладне", "PM": "попладне"} month_names = [ "", "Јануари", "Февруари", "Март", "Април", "Мај", "Јуни", "Јули", "Август", "Септември", "Октомври", "Ноември", "Декември", ] month_abbreviations = [ "", "Јан", "Фев", "Мар", "Апр", "Мај", "Јун", "Јул", "Авг", "Септ", "Окт", "Ноем", "Декем", ] day_names = [ "", "Понеделник", "Вторник", "Среда", "Четврток", "Петок", "Сабота", "Недела", ] day_abbreviations = [ "", "Пон", "Вт", "Сре", "Чет", "Пет", "Саб", "Нед", ] class GermanBaseLocale(Locale): past = "vor {0}" future = "in {0}" and_word = "und" timeframes = { "now": "gerade eben", "second": "einer Sekunde", "seconds": "{0} Sekunden", "minute": "einer Minute", "minutes": "{0} Minuten", "hour": "einer Stunde", "hours": "{0} Stunden", "day": "einem Tag", "days": "{0} Tagen", "week": "einer Woche", "weeks": "{0} Wochen", "month": "einem Monat", "months": "{0} Monaten", "year": "einem Jahr", "years": "{0} Jahren", } timeframes_only_distance = timeframes.copy() timeframes_only_distance["second"] = "eine Sekunde" timeframes_only_distance["minute"] = "eine Minute" timeframes_only_distance["hour"] = "eine Stunde" timeframes_only_distance["day"] = "ein Tag" timeframes_only_distance["days"] = "{0} Tage" timeframes_only_distance["week"] = "eine Woche" timeframes_only_distance["month"] = "ein Monat" timeframes_only_distance["months"] = "{0} Monate" timeframes_only_distance["year"] = "ein Jahr" timeframes_only_distance["years"] = "{0} Jahre" month_names = [ "", "Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember", ] month_abbreviations = [ "", "Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez", ] day_names = [ "", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag", ] day_abbreviations = ["", "Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"] def _ordinal_number(self, n: int) -> str: return f"{n}." def describe( self, timeframe: TimeFrameLiteral, delta: Union[int, float] = 0, only_distance: bool = False, ) -> str: """Describes a delta within a timeframe in plain language. :param timeframe: a string representing a timeframe. :param delta: a quantity representing a delta in a timeframe. :param only_distance: return only distance eg: "11 seconds" without "in" or "ago" keywords """ if not only_distance: return super().describe(timeframe, delta, only_distance) # German uses a different case without 'in' or 'ago' humanized = self.timeframes_only_distance[timeframe].format(trunc(abs(delta))) return humanized class GermanLocale(GermanBaseLocale, Locale): names = ["de", "de-de"] class SwissLocale(GermanBaseLocale, Locale): names = ["de-ch"] class AustrianLocale(GermanBaseLocale, Locale): names = ["de-at"] month_names = [ "", "Jänner", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember", ] class NorwegianLocale(Locale): names = ["nb", "nb-no"] past = "for {0} siden" future = "om {0}" timeframes = { "now": "nå nettopp", "second": "ett sekund", "seconds": "{0} sekunder", "minute": "ett minutt", "minutes": "{0} minutter", "hour": "en time", "hours": "{0} timer", "day": "en dag", "days": "{0} dager", "month": "en måned", "months": "{0} måneder", "year": "ett år", "years": "{0} år", } month_names = [ "", "januar", "februar", "mars", "april", "mai", "juni", "juli", "august", "september", "oktober", "november", "desember", ] month_abbreviations = [ "", "jan", "feb", "mar", "apr", "mai", "jun", "jul", "aug", "sep", "okt", "nov", "des", ] day_names = [ "", "mandag", "tirsdag", "onsdag", "torsdag", "fredag", "lørdag", "søndag", ] day_abbreviations = ["", "ma", "ti", "on", "to", "fr", "lø", "sø"] class NewNorwegianLocale(Locale): names = ["nn", "nn-no"] past = "for {0} sidan" future = "om {0}" timeframes = { "now": "no nettopp", "second": "eitt sekund", "seconds": "{0} sekund", "minute": "eitt minutt", "minutes": "{0} minutt", "hour": "ein time", "hours": "{0} timar", "day": "ein dag", "days": "{0} dagar", "month": "en månad", "months": "{0} månader", "year": "eitt år", "years": "{0} år", } month_names = [ "", "januar", "februar", "mars", "april", "mai", "juni", "juli", "august", "september", "oktober", "november", "desember", ] month_abbreviations = [ "", "jan", "feb", "mar", "apr", "mai", "jun", "jul", "aug", "sep", "okt", "nov", "des", ] day_names = [ "", "måndag", "tysdag", "onsdag", "torsdag", "fredag", "laurdag", "sundag", ] day_abbreviations = ["", "må", "ty", "on", "to", "fr", "la", "su"] class PortugueseLocale(Locale): names = ["pt", "pt-pt"] past = "há {0}" future = "em {0}" and_word = "e" timeframes = { "now": "agora", "second": "um segundo", "seconds": "{0} segundos", "minute": "um minuto", "minutes": "{0} minutos", "hour": "uma hora", "hours": "{0} horas", "day": "um dia", "days": "{0} dias", "week": "uma semana", "weeks": "{0} semanas", "month": "um mês", "months": "{0} meses", "year": "um ano", "years": "{0} anos", } month_names = [ "", "Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro", ] month_abbreviations = [ "", "Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez", ] day_names = [ "", "Segunda-feira", "Terça-feira", "Quarta-feira", "Quinta-feira", "Sexta-feira", "Sábado", "Domingo", ] day_abbreviations = ["", "Seg", "Ter", "Qua", "Qui", "Sex", "Sab", "Dom"] class BrazilianPortugueseLocale(PortugueseLocale): names = ["pt-br"] past = "faz {0}" class TagalogLocale(Locale): names = ["tl", "tl-ph"] past = "nakaraang {0}" future = "{0} mula ngayon" timeframes = { "now": "ngayon lang", "second": "isang segundo", "seconds": "{0} segundo", "minute": "isang minuto", "minutes": "{0} minuto", "hour": "isang oras", "hours": "{0} oras", "day": "isang araw", "days": "{0} araw", "week": "isang linggo", "weeks": "{0} linggo", "month": "isang buwan", "months": "{0} buwan", "year": "isang taon", "years": "{0} taon", } month_names = [ "", "Enero", "Pebrero", "Marso", "Abril", "Mayo", "Hunyo", "Hulyo", "Agosto", "Setyembre", "Oktubre", "Nobyembre", "Disyembre", ] month_abbreviations = [ "", "Ene", "Peb", "Mar", "Abr", "May", "Hun", "Hul", "Ago", "Set", "Okt", "Nob", "Dis", ] day_names = [ "", "Lunes", "Martes", "Miyerkules", "Huwebes", "Biyernes", "Sabado", "Linggo", ] day_abbreviations = ["", "Lun", "Mar", "Miy", "Huw", "Biy", "Sab", "Lin"] meridians = {"am": "nu", "pm": "nh", "AM": "ng umaga", "PM": "ng hapon"} def _ordinal_number(self, n: int) -> str: return f"ika-{n}" class VietnameseLocale(Locale): names = ["vi", "vi-vn"] past = "{0} trước" future = "{0} nữa" timeframes = { "now": "hiện tại", "second": "một giây", "seconds": "{0} giây", "minute": "một phút", "minutes": "{0} phút", "hour": "một giờ", "hours": "{0} giờ", "day": "một ngày", "days": "{0} ngày", "week": "một tuần", "weeks": "{0} tuần", "month": "một tháng", "months": "{0} tháng", "year": "một năm", "years": "{0} năm", } month_names = [ "", "Tháng Một", "Tháng Hai", "Tháng Ba", "Tháng Tư", "Tháng Năm", "Tháng Sáu", "Tháng Bảy", "Tháng Tám", "Tháng Chín", "Tháng Mười", "Tháng Mười Một", "Tháng Mười Hai", ] month_abbreviations = [ "", "Tháng 1", "Tháng 2", "Tháng 3", "Tháng 4", "Tháng 5", "Tháng 6", "Tháng 7", "Tháng 8", "Tháng 9", "Tháng 10", "Tháng 11", "Tháng 12", ] day_names = [ "", "Thứ Hai", "Thứ Ba", "Thứ Tư", "Thứ Năm", "Thứ Sáu", "Thứ Bảy", "Chủ Nhật", ] day_abbreviations = ["", "Thứ 2", "Thứ 3", "Thứ 4", "Thứ 5", "Thứ 6", "Thứ 7", "CN"] class TurkishLocale(Locale): names = ["tr", "tr-tr"] past = "{0} önce" future = "{0} sonra" and_word = "ve" timeframes = { "now": "şimdi", "second": "bir saniye", "seconds": "{0} saniye", "minute": "bir dakika", "minutes": "{0} dakika", "hour": "bir saat", "hours": "{0} saat", "day": "bir gün", "days": "{0} gün", "week": "bir hafta", "weeks": "{0} hafta", "month": "bir ay", "months": "{0} ay", "year": "bir yıl", "years": "{0} yıl", } meridians = {"am": "öö", "pm": "ös", "AM": "ÖÖ", "PM": "ÖS"} month_names = [ "", "Ocak", "Şubat", "Mart", "Nisan", "Mayıs", "Haziran", "Temmuz", "Ağustos", "Eylül", "Ekim", "Kasım", "Aralık", ] month_abbreviations = [ "", "Oca", "Şub", "Mar", "Nis", "May", "Haz", "Tem", "Ağu", "Eyl", "Eki", "Kas", "Ara", ] day_names = [ "", "Pazartesi", "Salı", "Çarşamba", "Perşembe", "Cuma", "Cumartesi", "Pazar", ] day_abbreviations = ["", "Pzt", "Sal", "Çar", "Per", "Cum", "Cmt", "Paz"] class AzerbaijaniLocale(Locale): names = ["az", "az-az"] past = "{0} əvvəl" future = "{0} sonra" timeframes = { "now": "indi", "second": "bir saniyə", "seconds": "{0} saniyə", "minute": "bir dəqiqə", "minutes": "{0} dəqiqə", "hour": "bir saat", "hours": "{0} saat", "day": "bir gün", "days": "{0} gün", "week": "bir həftə", "weeks": "{0} həftə", "month": "bir ay", "months": "{0} ay", "year": "bir il", "years": "{0} il", } month_names = [ "", "Yanvar", "Fevral", "Mart", "Aprel", "May", "İyun", "İyul", "Avqust", "Sentyabr", "Oktyabr", "Noyabr", "Dekabr", ] month_abbreviations = [ "", "Yan", "Fev", "Mar", "Apr", "May", "İyn", "İyl", "Avq", "Sen", "Okt", "Noy", "Dek", ] day_names = [ "", "Bazar ertəsi", "Çərşənbə axşamı", "Çərşənbə", "Cümə axşamı", "Cümə", "Şənbə", "Bazar", ] day_abbreviations = ["", "Ber", "Çax", "Çər", "Cax", "Cüm", "Şnb", "Bzr"] class ArabicLocale(Locale): names = [ "ar", "ar-ae", "ar-bh", "ar-dj", "ar-eg", "ar-eh", "ar-er", "ar-km", "ar-kw", "ar-ly", "ar-om", "ar-qa", "ar-sa", "ar-sd", "ar-so", "ar-ss", "ar-td", "ar-ye", ] past = "منذ {0}" future = "خلال {0}" timeframes: ClassVar[Mapping[TimeFrameLiteral, Union[str, Mapping[str, str]]]] = { "now": "الآن", "second": "ثانية", "seconds": {"2": "ثانيتين", "ten": "{0} ثوان", "higher": "{0} ثانية"}, "minute": "دقيقة", "minutes": {"2": "دقيقتين", "ten": "{0} دقائق", "higher": "{0} دقيقة"}, "hour": "ساعة", "hours": {"2": "ساعتين", "ten": "{0} ساعات", "higher": "{0} ساعة"}, "day": "يوم", "days": {"2": "يومين", "ten": "{0} أيام", "higher": "{0} يوم"}, "month": "شهر", "months": {"2": "شهرين", "ten": "{0} أشهر", "higher": "{0} شهر"}, "year": "سنة", "years": {"2": "سنتين", "ten": "{0} سنوات", "higher": "{0} سنة"}, } month_names = [ "", "يناير", "فبراير", "مارس", "أبريل", "مايو", "يونيو", "يوليو", "أغسطس", "سبتمبر", "أكتوبر", "نوفمبر", "ديسمبر", ] month_abbreviations = [ "", "يناير", "فبراير", "مارس", "أبريل", "مايو", "يونيو", "يوليو", "أغسطس", "سبتمبر", "أكتوبر", "نوفمبر", "ديسمبر", ] day_names = [ "", "الإثنين", "الثلاثاء", "الأربعاء", "الخميس", "الجمعة", "السبت", "الأحد", ] day_abbreviations = ["", "إثنين", "ثلاثاء", "أربعاء", "خميس", "جمعة", "سبت", "أحد"] def _format_timeframe(self, timeframe: TimeFrameLiteral, delta: int) -> str: form = self.timeframes[timeframe] delta = abs(delta) if isinstance(form, Mapping): if delta == 2: form = form["2"] elif 2 < delta <= 10: form = form["ten"] else: form = form["higher"] return form.format(delta) class LevantArabicLocale(ArabicLocale): names = ["ar-iq", "ar-jo", "ar-lb", "ar-ps", "ar-sy"] month_names = [ "", "كانون الثاني", "شباط", "آذار", "نيسان", "أيار", "حزيران", "تموز", "آب", "أيلول", "تشرين الأول", "تشرين الثاني", "كانون الأول", ] month_abbreviations = [ "", "كانون الثاني", "شباط", "آذار", "نيسان", "أيار", "حزيران", "تموز", "آب", "أيلول", "تشرين الأول", "تشرين الثاني", "كانون الأول", ] class AlgeriaTunisiaArabicLocale(ArabicLocale): names = ["ar-tn", "ar-dz"] month_names = [ "", "جانفي", "فيفري", "مارس", "أفريل", "ماي", "جوان", "جويلية", "أوت", "سبتمبر", "أكتوبر", "نوفمبر", "ديسمبر", ] month_abbreviations = [ "", "جانفي", "فيفري", "مارس", "أفريل", "ماي", "جوان", "جويلية", "أوت", "سبتمبر", "أكتوبر", "نوفمبر", "ديسمبر", ] class MauritaniaArabicLocale(ArabicLocale): names = ["ar-mr"] month_names = [ "", "يناير", "فبراير", "مارس", "إبريل", "مايو", "يونيو", "يوليو", "أغشت", "شتمبر", "أكتوبر", "نوفمبر", "دجمبر", ] month_abbreviations = [ "", "يناير", "فبراير", "مارس", "إبريل", "مايو", "يونيو", "يوليو", "أغشت", "شتمبر", "أكتوبر", "نوفمبر", "دجمبر", ] class MoroccoArabicLocale(ArabicLocale): names = ["ar-ma"] month_names = [ "", "يناير", "فبراير", "مارس", "أبريل", "ماي", "يونيو", "يوليوز", "غشت", "شتنبر", "أكتوبر", "نونبر", "دجنبر", ] month_abbreviations = [ "", "يناير", "فبراير", "مارس", "أبريل", "ماي", "يونيو", "يوليوز", "غشت", "شتنبر", "أكتوبر", "نونبر", "دجنبر", ] class IcelandicLocale(Locale): def _format_timeframe(self, timeframe: TimeFrameLiteral, delta: int) -> str: form = self.timeframes[timeframe] if isinstance(form, Mapping): if delta < 0: form = form["past"] elif delta > 0: form = form["future"] else: raise ValueError( "Icelandic Locale does not support units with a delta of zero. " "Please consider making a contribution to fix this issue." ) # FIXME: handle when delta is 0 return form.format(abs(delta)) names = ["is", "is-is"] past = "fyrir {0} síðan" future = "eftir {0}" timeframes: ClassVar[Mapping[TimeFrameLiteral, Union[str, Mapping[str, str]]]] = { "now": "rétt í þessu", "second": {"past": "sekúndu", "future": "sekúndu"}, "seconds": {"past": "{0} nokkrum sekúndum", "future": "nokkrar sekúndur"}, "minute": {"past": "einni mínútu", "future": "eina mínútu"}, "minutes": {"past": "{0} mínútum", "future": "{0} mínútur"}, "hour": {"past": "einum tíma", "future": "einn tíma"}, "hours": {"past": "{0} tímum", "future": "{0} tíma"}, "day": {"past": "einum degi", "future": "einn dag"}, "days": {"past": "{0} dögum", "future": "{0} daga"}, "month": {"past": "einum mánuði", "future": "einn mánuð"}, "months": {"past": "{0} mánuðum", "future": "{0} mánuði"}, "year": {"past": "einu ári", "future": "eitt ár"}, "years": {"past": "{0} árum", "future": "{0} ár"}, } meridians = {"am": "f.h.", "pm": "e.h.", "AM": "f.h.", "PM": "e.h."} month_names = [ "", "janúar", "febrúar", "mars", "apríl", "maí", "júní", "júlí", "ágúst", "september", "október", "nóvember", "desember", ] month_abbreviations = [ "", "jan", "feb", "mar", "apr", "maí", "jún", "júl", "ágú", "sep", "okt", "nóv", "des", ] day_names = [ "", "mánudagur", "þriðjudagur", "miðvikudagur", "fimmtudagur", "föstudagur", "laugardagur", "sunnudagur", ] day_abbreviations = ["", "mán", "þri", "mið", "fim", "fös", "lau", "sun"] class DanishLocale(Locale): names = ["da", "da-dk"] past = "for {0} siden" future = "efter {0}" and_word = "og" timeframes = { "now": "lige nu", "second": "et sekund", "seconds": "{0} et par sekunder", "minute": "et minut", "minutes": "{0} minutter", "hour": "en time", "hours": "{0} timer", "day": "en dag", "days": "{0} dage", "month": "en måned", "months": "{0} måneder", "year": "et år", "years": "{0} år", } month_names = [ "", "januar", "februar", "marts", "april", "maj", "juni", "juli", "august", "september", "oktober", "november", "december", ] month_abbreviations = [ "", "jan", "feb", "mar", "apr", "maj", "jun", "jul", "aug", "sep", "okt", "nov", "dec", ] day_names = [ "", "mandag", "tirsdag", "onsdag", "torsdag", "fredag", "lørdag", "søndag", ] day_abbreviations = ["", "man", "tir", "ons", "tor", "fre", "lør", "søn"] class MalayalamLocale(Locale): names = ["ml"] past = "{0} മുമ്പ്" future = "{0} ശേഷം" timeframes = { "now": "ഇപ്പോൾ", "second": "ഒരു നിമിഷം", "seconds": "{0} സെക്കന്റ്‌", "minute": "ഒരു മിനിറ്റ്", "minutes": "{0} മിനിറ്റ്", "hour": "ഒരു മണിക്കൂർ", "hours": "{0} മണിക്കൂർ", "day": "ഒരു ദിവസം ", "days": "{0} ദിവസം ", "month": "ഒരു മാസം ", "months": "{0} മാസം ", "year": "ഒരു വർഷം ", "years": "{0} വർഷം ", } meridians = { "am": "രാവിലെ", "pm": "ഉച്ചക്ക് ശേഷം", "AM": "രാവിലെ", "PM": "ഉച്ചക്ക് ശേഷം", } month_names = [ "", "ജനുവരി", "ഫെബ്രുവരി", "മാർച്ച്‌", "ഏപ്രിൽ ", "മെയ്‌ ", "ജൂണ്‍", "ജൂലൈ", "ഓഗസ്റ്റ്‌", "സെപ്റ്റംബർ", "ഒക്ടോബർ", "നവംബർ", "ഡിസംബർ", ] month_abbreviations = [ "", "ജനു", "ഫെബ് ", "മാർ", "ഏപ്രിൽ", "മേയ്", "ജൂണ്‍", "ജൂലൈ", "ഓഗസ്റ", "സെപ്റ്റ", "ഒക്ടോ", "നവം", "ഡിസം", ] day_names = ["", "തിങ്കള്‍", "ചൊവ്വ", "ബുധന്‍", "വ്യാഴം", "വെള്ളി", "ശനി", "ഞായര്‍"] day_abbreviations = [ "", "തിങ്കള്‍", "ചൊവ്വ", "ബുധന്‍", "വ്യാഴം", "വെള്ളി", "ശനി", "ഞായര്‍", ] class HindiLocale(Locale): names = ["hi", "hi-in"] past = "{0} पहले" future = "{0} बाद" timeframes = { "now": "अभी", "second": "एक पल", "seconds": "{0} सेकंड्", "minute": "एक मिनट ", "minutes": "{0} मिनट ", "hour": "एक घंटा", "hours": "{0} घंटे", "day": "एक दिन", "days": "{0} दिन", "month": "एक माह ", "months": "{0} महीने ", "year": "एक वर्ष ", "years": "{0} साल ", } meridians = {"am": "सुबह", "pm": "शाम", "AM": "सुबह", "PM": "शाम"} month_names = [ "", "जनवरी", "फरवरी", "मार्च", "अप्रैल ", "मई", "जून", "जुलाई", "अगस्त", "सितंबर", "अक्टूबर", "नवंबर", "दिसंबर", ] month_abbreviations = [ "", "जन", "फ़र", "मार्च", "अप्रै", "मई", "जून", "जुलाई", "आग", "सित", "अकत", "नवे", "दिस", ] day_names = [ "", "सोमवार", "मंगलवार", "बुधवार", "गुरुवार", "शुक्रवार", "शनिवार", "रविवार", ] day_abbreviations = ["", "सोम", "मंगल", "बुध", "गुरुवार", "शुक्र", "शनि", "रवि"] class CzechLocale(Locale): names = ["cs", "cs-cz"] timeframes: ClassVar[Mapping[TimeFrameLiteral, Union[str, Mapping[str, str]]]] = { "now": "Teď", "second": {"past": "vteřina", "future": "vteřina", "zero": "vteřina"}, "seconds": { "past": "{0} sekundami", "future-singular": "{0} sekundy", "future-paucal": "{0} sekund", }, "minute": {"past": "minutou", "future": "minutu", "zero": "{0} minut"}, "minutes": { "past": "{0} minutami", "future-singular": "{0} minuty", "future-paucal": "{0} minut", }, "hour": {"past": "hodinou", "future": "hodinu", "zero": "{0} hodin"}, "hours": { "past": "{0} hodinami", "future-singular": "{0} hodiny", "future-paucal": "{0} hodin", }, "day": {"past": "dnem", "future": "den", "zero": "{0} dnů"}, "days": { "past": "{0} dny", "future-singular": "{0} dny", "future-paucal": "{0} dnů", }, "week": {"past": "týdnem", "future": "týden", "zero": "{0} týdnů"}, "weeks": { "past": "{0} týdny", "future-singular": "{0} týdny", "future-paucal": "{0} týdnů", }, "month": {"past": "měsícem", "future": "měsíc", "zero": "{0} měsíců"}, "months": { "past": "{0} měsíci", "future-singular": "{0} měsíce", "future-paucal": "{0} měsíců", }, "year": {"past": "rokem", "future": "rok", "zero": "{0} let"}, "years": { "past": "{0} lety", "future-singular": "{0} roky", "future-paucal": "{0} let", }, } past = "Před {0}" future = "Za {0}" month_names = [ "", "leden", "únor", "březen", "duben", "květen", "červen", "červenec", "srpen", "září", "říjen", "listopad", "prosinec", ] month_abbreviations = [ "", "led", "úno", "bře", "dub", "kvě", "čvn", "čvc", "srp", "zář", "říj", "lis", "pro", ] day_names = [ "", "pondělí", "úterý", "středa", "čtvrtek", "pátek", "sobota", "neděle", ] day_abbreviations = ["", "po", "út", "st", "čt", "pá", "so", "ne"] def _format_timeframe(self, timeframe: TimeFrameLiteral, delta: int) -> str: """Czech aware time frame format function, takes into account the differences between past and future forms.""" abs_delta = abs(delta) form = self.timeframes[timeframe] if isinstance(form, str): return form.format(abs_delta) if delta == 0: key = "zero" # And *never* use 0 in the singular! elif delta < 0: key = "past" else: # Needed since both regular future and future-singular and future-paucal cases if "future-singular" not in form: key = "future" elif 2 <= abs_delta % 10 <= 4 and ( abs_delta % 100 < 10 or abs_delta % 100 >= 20 ): key = "future-singular" else: key = "future-paucal" form: str = form[key] return form.format(abs_delta) class SlovakLocale(Locale): names = ["sk", "sk-sk"] timeframes: ClassVar[Mapping[TimeFrameLiteral, Union[str, Mapping[str, str]]]] = { "now": "Teraz", "second": {"past": "sekundou", "future": "sekundu", "zero": "{0} sekúnd"}, "seconds": { "past": "{0} sekundami", "future-singular": "{0} sekundy", "future-paucal": "{0} sekúnd", }, "minute": {"past": "minútou", "future": "minútu", "zero": "{0} minút"}, "minutes": { "past": "{0} minútami", "future-singular": "{0} minúty", "future-paucal": "{0} minút", }, "hour": {"past": "hodinou", "future": "hodinu", "zero": "{0} hodín"}, "hours": { "past": "{0} hodinami", "future-singular": "{0} hodiny", "future-paucal": "{0} hodín", }, "day": {"past": "dňom", "future": "deň", "zero": "{0} dní"}, "days": { "past": "{0} dňami", "future-singular": "{0} dni", "future-paucal": "{0} dní", }, "week": {"past": "týždňom", "future": "týždeň", "zero": "{0} týždňov"}, "weeks": { "past": "{0} týždňami", "future-singular": "{0} týždne", "future-paucal": "{0} týždňov", }, "month": {"past": "mesiacom", "future": "mesiac", "zero": "{0} mesiacov"}, "months": { "past": "{0} mesiacmi", "future-singular": "{0} mesiace", "future-paucal": "{0} mesiacov", }, "year": {"past": "rokom", "future": "rok", "zero": "{0} rokov"}, "years": { "past": "{0} rokmi", "future-singular": "{0} roky", "future-paucal": "{0} rokov", }, } past = "Pred {0}" future = "O {0}" and_word = "a" month_names = [ "", "január", "február", "marec", "apríl", "máj", "jún", "júl", "august", "september", "október", "november", "december", ] month_abbreviations = [ "", "jan", "feb", "mar", "apr", "máj", "jún", "júl", "aug", "sep", "okt", "nov", "dec", ] day_names = [ "", "pondelok", "utorok", "streda", "štvrtok", "piatok", "sobota", "nedeľa", ] day_abbreviations = ["", "po", "ut", "st", "št", "pi", "so", "ne"] def _format_timeframe(self, timeframe: TimeFrameLiteral, delta: int) -> str: """Slovak aware time frame format function, takes into account the differences between past and future forms.""" abs_delta = abs(delta) form = self.timeframes[timeframe] if isinstance(form, str): return form.format(abs_delta) if delta == 0: key = "zero" # And *never* use 0 in the singular! elif delta < 0: key = "past" else: if "future-singular" not in form: key = "future" elif 2 <= abs_delta % 10 <= 4 and ( abs_delta % 100 < 10 or abs_delta % 100 >= 20 ): key = "future-singular" else: key = "future-paucal" form: str = form[key] return form.format(abs_delta) class FarsiLocale(Locale): names = ["fa", "fa-ir"] past = "{0} قبل" future = "در {0}" timeframes = { "now": "اکنون", "second": "یک لحظه", "seconds": "{0} ثانیه", "minute": "یک دقیقه", "minutes": "{0} دقیقه", "hour": "یک ساعت", "hours": "{0} ساعت", "day": "یک روز", "days": "{0} روز", "month": "یک ماه", "months": "{0} ماه", "year": "یک سال", "years": "{0} سال", } meridians = { "am": "قبل از ظهر", "pm": "بعد از ظهر", "AM": "قبل از ظهر", "PM": "بعد از ظهر", } month_names = [ "", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", ] month_abbreviations = [ "", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", ] day_names = [ "", "دو شنبه", "سه شنبه", "چهارشنبه", "پنجشنبه", "جمعه", "شنبه", "یکشنبه", ] day_abbreviations = ["", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] class HebrewLocale(Locale): names = ["he", "he-il"] past = "לפני {0}" future = "בעוד {0}" and_word = "ו" timeframes: ClassVar[Mapping[TimeFrameLiteral, Union[str, Mapping[str, str]]]] = { "now": "הרגע", "second": "שנייה", "seconds": "{0} שניות", "minute": "דקה", "minutes": "{0} דקות", "hour": "שעה", "hours": {"2": "שעתיים", "ten": "{0} שעות", "higher": "{0} שעות"}, "day": "יום", "days": {"2": "יומיים", "ten": "{0} ימים", "higher": "{0} יום"}, "week": "שבוע", "weeks": {"2": "שבועיים", "ten": "{0} שבועות", "higher": "{0} שבועות"}, "month": "חודש", "months": {"2": "חודשיים", "ten": "{0} חודשים", "higher": "{0} חודשים"}, "year": "שנה", "years": {"2": "שנתיים", "ten": "{0} שנים", "higher": "{0} שנה"}, } meridians = { "am": 'לפנ"צ', "pm": 'אחר"צ', "AM": "לפני הצהריים", "PM": "אחרי הצהריים", } month_names = [ "", "ינואר", "פברואר", "מרץ", "אפריל", "מאי", "יוני", "יולי", "אוגוסט", "ספטמבר", "אוקטובר", "נובמבר", "דצמבר", ] month_abbreviations = [ "", "ינו׳", "פבר׳", "מרץ", "אפר׳", "מאי", "יוני", "יולי", "אוג׳", "ספט׳", "אוק׳", "נוב׳", "דצמ׳", ] day_names = ["", "שני", "שלישי", "רביעי", "חמישי", "שישי", "שבת", "ראשון"] day_abbreviations = ["", "ב׳", "ג׳", "ד׳", "ה׳", "ו׳", "ש׳", "א׳"] def _format_timeframe(self, timeframe: TimeFrameLiteral, delta: int) -> str: form = self.timeframes[timeframe] delta = abs(delta) if isinstance(form, Mapping): if delta == 2: form = form["2"] elif delta == 0 or 2 < delta <= 10: form = form["ten"] else: form = form["higher"] return form.format(delta) def describe_multi( self, timeframes: Sequence[Tuple[TimeFrameLiteral, Union[int, float]]], only_distance: bool = False, ) -> str: """Describes a delta within multiple timeframes in plain language. In Hebrew, the and word behaves a bit differently. :param timeframes: a list of string, quantity pairs each representing a timeframe and delta. :param only_distance: return only distance eg: "2 hours and 11 seconds" without "in" or "ago" keywords """ humanized = "" for index, (timeframe, delta) in enumerate(timeframes): last_humanized = self._format_timeframe(timeframe, trunc(delta)) if index == 0: humanized = last_humanized elif index == len(timeframes) - 1: # Must have at least 2 items humanized += " " + self.and_word if last_humanized[0].isdecimal(): humanized += "־" humanized += last_humanized else: # Don't add for the last one humanized += ", " + last_humanized if not only_distance: humanized = self._format_relative(humanized, timeframe, trunc(delta)) return humanized class MarathiLocale(Locale): names = ["mr"] past = "{0} आधी" future = "{0} नंतर" timeframes = { "now": "सद्य", "second": "एक सेकंद", "seconds": "{0} सेकंद", "minute": "एक मिनिट ", "minutes": "{0} मिनिट ", "hour": "एक तास", "hours": "{0} तास", "day": "एक दिवस", "days": "{0} दिवस", "month": "एक महिना ", "months": "{0} महिने ", "year": "एक वर्ष ", "years": "{0} वर्ष ", } meridians = {"am": "सकाळ", "pm": "संध्याकाळ", "AM": "सकाळ", "PM": "संध्याकाळ"} month_names = [ "", "जानेवारी", "फेब्रुवारी", "मार्च", "एप्रिल", "मे", "जून", "जुलै", "अॉगस्ट", "सप्टेंबर", "अॉक्टोबर", "नोव्हेंबर", "डिसेंबर", ] month_abbreviations = [ "", "जान", "फेब्रु", "मार्च", "एप्रि", "मे", "जून", "जुलै", "अॉग", "सप्टें", "अॉक्टो", "नोव्हें", "डिसें", ] day_names = [ "", "सोमवार", "मंगळवार", "बुधवार", "गुरुवार", "शुक्रवार", "शनिवार", "रविवार", ] day_abbreviations = ["", "सोम", "मंगळ", "बुध", "गुरु", "शुक्र", "शनि", "रवि"] class CatalanLocale(Locale): names = ["ca", "ca-es", "ca-ad", "ca-fr", "ca-it"] past = "Fa {0}" future = "En {0}" and_word = "i" timeframes = { "now": "Ara mateix", "second": "un segon", "seconds": "{0} segons", "minute": "un minut", "minutes": "{0} minuts", "hour": "una hora", "hours": "{0} hores", "day": "un dia", "days": "{0} dies", "month": "un mes", "months": "{0} mesos", "year": "un any", "years": "{0} anys", } month_names = [ "", "gener", "febrer", "març", "abril", "maig", "juny", "juliol", "agost", "setembre", "octubre", "novembre", "desembre", ] month_abbreviations = [ "", "gen.", "febr.", "març", "abr.", "maig", "juny", "jul.", "ag.", "set.", "oct.", "nov.", "des.", ] day_names = [ "", "dilluns", "dimarts", "dimecres", "dijous", "divendres", "dissabte", "diumenge", ] day_abbreviations = [ "", "dl.", "dt.", "dc.", "dj.", "dv.", "ds.", "dg.", ] class BasqueLocale(Locale): names = ["eu", "eu-eu"] past = "duela {0}" future = "{0}" # I don't know what's the right phrase in Basque for the future. timeframes = { "now": "Orain", "second": "segundo bat", "seconds": "{0} segundu", "minute": "minutu bat", "minutes": "{0} minutu", "hour": "ordu bat", "hours": "{0} ordu", "day": "egun bat", "days": "{0} egun", "month": "hilabete bat", "months": "{0} hilabet", "year": "urte bat", "years": "{0} urte", } month_names = [ "", "urtarrilak", "otsailak", "martxoak", "apirilak", "maiatzak", "ekainak", "uztailak", "abuztuak", "irailak", "urriak", "azaroak", "abenduak", ] month_abbreviations = [ "", "urt", "ots", "mar", "api", "mai", "eka", "uzt", "abu", "ira", "urr", "aza", "abe", ] day_names = [ "", "astelehena", "asteartea", "asteazkena", "osteguna", "ostirala", "larunbata", "igandea", ] day_abbreviations = ["", "al", "ar", "az", "og", "ol", "lr", "ig"] class HungarianLocale(Locale): names = ["hu", "hu-hu"] past = "{0} ezelőtt" future = "{0} múlva" timeframes: ClassVar[Mapping[TimeFrameLiteral, Union[str, Mapping[str, str]]]] = { "now": "éppen most", "second": {"past": "egy második", "future": "egy második"}, "seconds": {"past": "{0} másodpercekkel", "future": "{0} pár másodperc"}, "minute": {"past": "egy perccel", "future": "egy perc"}, "minutes": {"past": "{0} perccel", "future": "{0} perc"}, "hour": {"past": "egy órával", "future": "egy óra"}, "hours": {"past": "{0} órával", "future": "{0} óra"}, "day": {"past": "egy nappal", "future": "egy nap"}, "days": {"past": "{0} nappal", "future": "{0} nap"}, "month": {"past": "egy hónappal", "future": "egy hónap"}, "months": {"past": "{0} hónappal", "future": "{0} hónap"}, "year": {"past": "egy évvel", "future": "egy év"}, "years": {"past": "{0} évvel", "future": "{0} év"}, } month_names = [ "", "január", "február", "március", "április", "május", "június", "július", "augusztus", "szeptember", "október", "november", "december", ] month_abbreviations = [ "", "jan", "febr", "márc", "ápr", "máj", "jún", "júl", "aug", "szept", "okt", "nov", "dec", ] day_names = [ "", "hétfő", "kedd", "szerda", "csütörtök", "péntek", "szombat", "vasárnap", ] day_abbreviations = ["", "hét", "kedd", "szer", "csüt", "pént", "szom", "vas"] meridians = {"am": "de", "pm": "du", "AM": "DE", "PM": "DU"} def _format_timeframe(self, timeframe: TimeFrameLiteral, delta: int) -> str: form = self.timeframes[timeframe] if isinstance(form, Mapping): if delta > 0: form = form["future"] else: form = form["past"] return form.format(abs(delta)) class EsperantoLocale(Locale): names = ["eo", "eo-xx"] past = "antaŭ {0}" future = "post {0}" timeframes = { "now": "nun", "second": "sekundo", "seconds": "{0} kelkaj sekundoj", "minute": "unu minuto", "minutes": "{0} minutoj", "hour": "un horo", "hours": "{0} horoj", "day": "unu tago", "days": "{0} tagoj", "month": "unu monato", "months": "{0} monatoj", "year": "unu jaro", "years": "{0} jaroj", } month_names = [ "", "januaro", "februaro", "marto", "aprilo", "majo", "junio", "julio", "aŭgusto", "septembro", "oktobro", "novembro", "decembro", ] month_abbreviations = [ "", "jan", "feb", "mar", "apr", "maj", "jun", "jul", "aŭg", "sep", "okt", "nov", "dec", ] day_names = [ "", "lundo", "mardo", "merkredo", "ĵaŭdo", "vendredo", "sabato", "dimanĉo", ] day_abbreviations = ["", "lun", "mar", "mer", "ĵaŭ", "ven", "sab", "dim"] meridians = {"am": "atm", "pm": "ptm", "AM": "ATM", "PM": "PTM"} ordinal_day_re = r"((?P[1-3]?[0-9](?=a))a)" def _ordinal_number(self, n: int) -> str: return f"{n}a" class ThaiLocale(Locale): names = ["th", "th-th"] past = "{0} ที่ผ่านมา" future = "ในอีก {0}" timeframes = { "now": "ขณะนี้", "second": "วินาที", "seconds": "{0} ไม่กี่วินาที", "minute": "1 นาที", "minutes": "{0} นาที", "hour": "1 ชั่วโมง", "hours": "{0} ชั่วโมง", "day": "1 วัน", "days": "{0} วัน", "month": "1 เดือน", "months": "{0} เดือน", "year": "1 ปี", "years": "{0} ปี", } month_names = [ "", "มกราคม", "กุมภาพันธ์", "มีนาคม", "เมษายน", "พฤษภาคม", "มิถุนายน", "กรกฎาคม", "สิงหาคม", "กันยายน", "ตุลาคม", "พฤศจิกายน", "ธันวาคม", ] month_abbreviations = [ "", "ม.ค.", "ก.พ.", "มี.ค.", "เม.ย.", "พ.ค.", "มิ.ย.", "ก.ค.", "ส.ค.", "ก.ย.", "ต.ค.", "พ.ย.", "ธ.ค.", ] day_names = ["", "จันทร์", "อังคาร", "พุธ", "พฤหัสบดี", "ศุกร์", "เสาร์", "อาทิตย์"] day_abbreviations = ["", "จ", "อ", "พ", "พฤ", "ศ", "ส", "อา"] meridians = {"am": "am", "pm": "pm", "AM": "AM", "PM": "PM"} BE_OFFSET = 543 def year_full(self, year: int) -> str: """Thai always use Buddhist Era (BE) which is CE + 543""" year += self.BE_OFFSET return f"{year:04d}" def year_abbreviation(self, year: int) -> str: """Thai always use Buddhist Era (BE) which is CE + 543""" year += self.BE_OFFSET return f"{year:04d}"[2:] def _format_relative( self, humanized: str, timeframe: TimeFrameLiteral, delta: Union[float, int], ) -> str: """Thai normally doesn't have any space between words""" if timeframe == "now": return humanized direction = self.past if delta < 0 else self.future relative_string = direction.format(humanized) if timeframe == "seconds": relative_string = relative_string.replace(" ", "") return relative_string class BengaliLocale(Locale): names = ["bn", "bn-bd", "bn-in"] past = "{0} আগে" future = "{0} পরে" timeframes = { "now": "এখন", "second": "একটি দ্বিতীয়", "seconds": "{0} সেকেন্ড", "minute": "এক মিনিট", "minutes": "{0} মিনিট", "hour": "এক ঘণ্টা", "hours": "{0} ঘণ্টা", "day": "এক দিন", "days": "{0} দিন", "month": "এক মাস", "months": "{0} মাস ", "year": "এক বছর", "years": "{0} বছর", } meridians = {"am": "সকাল", "pm": "বিকাল", "AM": "সকাল", "PM": "বিকাল"} month_names = [ "", "জানুয়ারি", "ফেব্রুয়ারি", "মার্চ", "এপ্রিল", "মে", "জুন", "জুলাই", "আগস্ট", "সেপ্টেম্বর", "অক্টোবর", "নভেম্বর", "ডিসেম্বর", ] month_abbreviations = [ "", "জানু", "ফেব", "মার্চ", "এপ্রি", "মে", "জুন", "জুল", "অগা", "সেপ্ট", "অক্টো", "নভে", "ডিসে", ] day_names = [ "", "সোমবার", "মঙ্গলবার", "বুধবার", "বৃহস্পতিবার", "শুক্রবার", "শনিবার", "রবিবার", ] day_abbreviations = ["", "সোম", "মঙ্গল", "বুধ", "বৃহঃ", "শুক্র", "শনি", "রবি"] def _ordinal_number(self, n: int) -> str: if n > 10 or n == 0: return f"{n}তম" if n in [1, 5, 7, 8, 9, 10]: return f"{n}ম" if n in [2, 3]: return f"{n}য়" if n == 4: return f"{n}র্থ" if n == 6: return f"{n}ষ্ঠ" class RomanshLocale(Locale): names = ["rm", "rm-ch"] past = "avant {0}" future = "en {0}" timeframes = { "now": "en quest mument", "second": "in secunda", "seconds": "{0} secundas", "minute": "ina minuta", "minutes": "{0} minutas", "hour": "in'ura", "hours": "{0} ura", "day": "in di", "days": "{0} dis", "month": "in mais", "months": "{0} mais", "year": "in onn", "years": "{0} onns", } month_names = [ "", "schaner", "favrer", "mars", "avrigl", "matg", "zercladur", "fanadur", "avust", "settember", "october", "november", "december", ] month_abbreviations = [ "", "schan", "fav", "mars", "avr", "matg", "zer", "fan", "avu", "set", "oct", "nov", "dec", ] day_names = [ "", "glindesdi", "mardi", "mesemna", "gievgia", "venderdi", "sonda", "dumengia", ] day_abbreviations = ["", "gli", "ma", "me", "gie", "ve", "so", "du"] class RomanianLocale(Locale): names = ["ro", "ro-ro"] past = "{0} în urmă" future = "peste {0}" and_word = "și" timeframes = { "now": "acum", "second": "o secunda", "seconds": "{0} câteva secunde", "minute": "un minut", "minutes": "{0} minute", "hour": "o oră", "hours": "{0} ore", "day": "o zi", "days": "{0} zile", "month": "o lună", "months": "{0} luni", "year": "un an", "years": "{0} ani", } month_names = [ "", "ianuarie", "februarie", "martie", "aprilie", "mai", "iunie", "iulie", "august", "septembrie", "octombrie", "noiembrie", "decembrie", ] month_abbreviations = [ "", "ian", "febr", "mart", "apr", "mai", "iun", "iul", "aug", "sept", "oct", "nov", "dec", ] day_names = [ "", "luni", "marți", "miercuri", "joi", "vineri", "sâmbătă", "duminică", ] day_abbreviations = ["", "Lun", "Mar", "Mie", "Joi", "Vin", "Sâm", "Dum"] class SlovenianLocale(Locale): names = ["sl", "sl-si"] past = "pred {0}" future = "čez {0}" and_word = "in" timeframes = { "now": "zdaj", "second": "sekundo", "seconds": "{0} sekund", "minute": "minuta", "minutes": "{0} minutami", "hour": "uro", "hours": "{0} ur", "day": "dan", "days": "{0} dni", "month": "mesec", "months": "{0} mesecev", "year": "leto", "years": "{0} let", } meridians = {"am": "", "pm": "", "AM": "", "PM": ""} month_names = [ "", "Januar", "Februar", "Marec", "April", "Maj", "Junij", "Julij", "Avgust", "September", "Oktober", "November", "December", ] month_abbreviations = [ "", "Jan", "Feb", "Mar", "Apr", "Maj", "Jun", "Jul", "Avg", "Sep", "Okt", "Nov", "Dec", ] day_names = [ "", "Ponedeljek", "Torek", "Sreda", "Četrtek", "Petek", "Sobota", "Nedelja", ] day_abbreviations = ["", "Pon", "Tor", "Sre", "Čet", "Pet", "Sob", "Ned"] class IndonesianLocale(Locale): names = ["id", "id-id"] past = "{0} yang lalu" future = "dalam {0}" and_word = "dan" timeframes = { "now": "baru saja", "second": "1 sebentar", "seconds": "{0} detik", "minute": "1 menit", "minutes": "{0} menit", "hour": "1 jam", "hours": "{0} jam", "day": "1 hari", "days": "{0} hari", "month": "1 bulan", "months": "{0} bulan", "year": "1 tahun", "years": "{0} tahun", } meridians = {"am": "", "pm": "", "AM": "", "PM": ""} month_names = [ "", "Januari", "Februari", "Maret", "April", "Mei", "Juni", "Juli", "Agustus", "September", "Oktober", "November", "Desember", ] month_abbreviations = [ "", "Jan", "Feb", "Mar", "Apr", "Mei", "Jun", "Jul", "Ags", "Sept", "Okt", "Nov", "Des", ] day_names = ["", "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu", "Minggu"] day_abbreviations = [ "", "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu", "Minggu", ] class NepaliLocale(Locale): names = ["ne", "ne-np"] past = "{0} पहिले" future = "{0} पछी" timeframes = { "now": "अहिले", "second": "एक सेकेन्ड", "seconds": "{0} सेकण्ड", "minute": "मिनेट", "minutes": "{0} मिनेट", "hour": "एक घण्टा", "hours": "{0} घण्टा", "day": "एक दिन", "days": "{0} दिन", "month": "एक महिना", "months": "{0} महिना", "year": "एक बर्ष", "years": "{0} बर्ष", } meridians = {"am": "पूर्वाह्न", "pm": "अपरान्ह", "AM": "पूर्वाह्न", "PM": "अपरान्ह"} month_names = [ "", "जनवरी", "फेब्रुअरी", "मार्च", "एप्रील", "मे", "जुन", "जुलाई", "अगष्ट", "सेप्टेम्बर", "अक्टोबर", "नोवेम्बर", "डिसेम्बर", ] month_abbreviations = [ "", "जन", "फेब", "मार्च", "एप्रील", "मे", "जुन", "जुलाई", "अग", "सेप", "अक्ट", "नोव", "डिस", ] day_names = [ "", "सोमवार", "मंगलवार", "बुधवार", "बिहिवार", "शुक्रवार", "शनिवार", "आइतवार", ] day_abbreviations = ["", "सोम", "मंगल", "बुध", "बिहि", "शुक्र", "शनि", "आइत"] class EstonianLocale(Locale): names = ["ee", "et"] past = "{0} tagasi" future = "{0} pärast" and_word = "ja" timeframes: ClassVar[Mapping[TimeFrameLiteral, Mapping[str, str]]] = { "now": {"past": "just nüüd", "future": "just nüüd"}, "second": {"past": "üks sekund", "future": "ühe sekundi"}, "seconds": {"past": "{0} sekundit", "future": "{0} sekundi"}, "minute": {"past": "üks minut", "future": "ühe minuti"}, "minutes": {"past": "{0} minutit", "future": "{0} minuti"}, "hour": {"past": "tund aega", "future": "tunni aja"}, "hours": {"past": "{0} tundi", "future": "{0} tunni"}, "day": {"past": "üks päev", "future": "ühe päeva"}, "days": {"past": "{0} päeva", "future": "{0} päeva"}, "month": {"past": "üks kuu", "future": "ühe kuu"}, "months": {"past": "{0} kuud", "future": "{0} kuu"}, "year": {"past": "üks aasta", "future": "ühe aasta"}, "years": {"past": "{0} aastat", "future": "{0} aasta"}, } month_names = [ "", "Jaanuar", "Veebruar", "Märts", "Aprill", "Mai", "Juuni", "Juuli", "August", "September", "Oktoober", "November", "Detsember", ] month_abbreviations = [ "", "Jan", "Veb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dets", ] day_names = [ "", "Esmaspäev", "Teisipäev", "Kolmapäev", "Neljapäev", "Reede", "Laupäev", "Pühapäev", ] day_abbreviations = ["", "Esm", "Teis", "Kolm", "Nelj", "Re", "Lau", "Püh"] def _format_timeframe(self, timeframe: TimeFrameLiteral, delta: int) -> str: form = self.timeframes[timeframe] if delta > 0: _form = form["future"] else: _form = form["past"] return _form.format(abs(delta)) class LatvianLocale(Locale): names = ["lv", "lv-lv"] past = "pirms {0}" future = "pēc {0}" and_word = "un" timeframes: ClassVar[Mapping[TimeFrameLiteral, Union[str, Mapping[str, str]]]] = { "now": "tagad", "second": "sekundes", "seconds": "{0} sekundēm", "minute": "minūtes", "minutes": "{0} minūtēm", "hour": "stundas", "hours": "{0} stundām", "day": "dienas", "days": "{0} dienām", "week": "nedēļas", "weeks": "{0} nedēļām", "month": "mēneša", "months": "{0} mēnešiem", "year": "gada", "years": "{0} gadiem", } month_names = [ "", "janvāris", "februāris", "marts", "aprīlis", "maijs", "jūnijs", "jūlijs", "augusts", "septembris", "oktobris", "novembris", "decembris", ] month_abbreviations = [ "", "jan", "feb", "marts", "apr", "maijs", "jūnijs", "jūlijs", "aug", "sept", "okt", "nov", "dec", ] day_names = [ "", "pirmdiena", "otrdiena", "trešdiena", "ceturtdiena", "piektdiena", "sestdiena", "svētdiena", ] day_abbreviations = [ "", "pi", "ot", "tr", "ce", "pi", "se", "sv", ] class SwahiliLocale(Locale): names = [ "sw", "sw-ke", "sw-tz", ] past = "{0} iliyopita" future = "muda wa {0}" and_word = "na" timeframes = { "now": "sasa hivi", "second": "sekunde", "seconds": "sekunde {0}", "minute": "dakika moja", "minutes": "dakika {0}", "hour": "saa moja", "hours": "saa {0}", "day": "siku moja", "days": "siku {0}", "week": "wiki moja", "weeks": "wiki {0}", "month": "mwezi moja", "months": "miezi {0}", "year": "mwaka moja", "years": "miaka {0}", } meridians = {"am": "asu", "pm": "mch", "AM": "ASU", "PM": "MCH"} month_names = [ "", "Januari", "Februari", "Machi", "Aprili", "Mei", "Juni", "Julai", "Agosti", "Septemba", "Oktoba", "Novemba", "Desemba", ] month_abbreviations = [ "", "Jan", "Feb", "Mac", "Apr", "Mei", "Jun", "Jul", "Ago", "Sep", "Okt", "Nov", "Des", ] day_names = [ "", "Jumatatu", "Jumanne", "Jumatano", "Alhamisi", "Ijumaa", "Jumamosi", "Jumapili", ] day_abbreviations = [ "", "Jumatatu", "Jumanne", "Jumatano", "Alhamisi", "Ijumaa", "Jumamosi", "Jumapili", ] class CroatianLocale(Locale): names = ["hr", "hr-hr"] past = "prije {0}" future = "za {0}" and_word = "i" timeframes: ClassVar[Mapping[TimeFrameLiteral, Union[str, Mapping[str, str]]]] = { "now": "upravo sad", "second": "sekundu", "seconds": {"double": "{0} sekunde", "higher": "{0} sekundi"}, "minute": "minutu", "minutes": {"double": "{0} minute", "higher": "{0} minuta"}, "hour": "sat", "hours": {"double": "{0} sata", "higher": "{0} sati"}, "day": "jedan dan", "days": {"double": "{0} dana", "higher": "{0} dana"}, "week": "tjedan", "weeks": {"double": "{0} tjedna", "higher": "{0} tjedana"}, "month": "mjesec", "months": {"double": "{0} mjeseca", "higher": "{0} mjeseci"}, "year": "godinu", "years": {"double": "{0} godine", "higher": "{0} godina"}, } month_names = [ "", "siječanj", "veljača", "ožujak", "travanj", "svibanj", "lipanj", "srpanj", "kolovoz", "rujan", "listopad", "studeni", "prosinac", ] month_abbreviations = [ "", "siječ", "velj", "ožuj", "trav", "svib", "lip", "srp", "kol", "ruj", "list", "stud", "pros", ] day_names = [ "", "ponedjeljak", "utorak", "srijeda", "četvrtak", "petak", "subota", "nedjelja", ] day_abbreviations = [ "", "po", "ut", "sr", "če", "pe", "su", "ne", ] def _format_timeframe(self, timeframe: TimeFrameLiteral, delta: int) -> str: form = self.timeframes[timeframe] delta = abs(delta) if isinstance(form, Mapping): if 1 < delta <= 4: form = form["double"] else: form = form["higher"] return form.format(delta) class LatinLocale(Locale): names = ["la", "la-va"] past = "ante {0}" future = "in {0}" and_word = "et" timeframes: ClassVar[Mapping[TimeFrameLiteral, Union[str, Mapping[str, str]]]] = { "now": "nunc", "second": "secundum", "seconds": "{0} secundis", "minute": "minutam", "minutes": "{0} minutis", "hour": "horam", "hours": "{0} horas", "day": "diem", "days": "{0} dies", "week": "hebdomadem", "weeks": "{0} hebdomades", "month": "mensem", "months": "{0} mensis", "year": "annum", "years": "{0} annos", } month_names = [ "", "Ianuarius", "Februarius", "Martius", "Aprilis", "Maius", "Iunius", "Iulius", "Augustus", "September", "October", "November", "December", ] month_abbreviations = [ "", "Ian", "Febr", "Mart", "Apr", "Mai", "Iun", "Iul", "Aug", "Sept", "Oct", "Nov", "Dec", ] day_names = [ "", "dies Lunae", "dies Martis", "dies Mercurii", "dies Iovis", "dies Veneris", "dies Saturni", "dies Solis", ] day_abbreviations = [ "", "dies Lunae", "dies Martis", "dies Mercurii", "dies Iovis", "dies Veneris", "dies Saturni", "dies Solis", ] class LithuanianLocale(Locale): names = ["lt", "lt-lt"] past = "prieš {0}" future = "po {0}" and_word = "ir" timeframes: ClassVar[Mapping[TimeFrameLiteral, Union[str, Mapping[str, str]]]] = { "now": "dabar", "second": "sekundės", "seconds": "{0} sekundžių", "minute": "minutės", "minutes": "{0} minučių", "hour": "valandos", "hours": "{0} valandų", "day": "dieną", "days": "{0} dienų", "week": "savaitės", "weeks": "{0} savaičių", "month": "mėnesio", "months": "{0} mėnesių", "year": "metų", "years": "{0} metų", } month_names = [ "", "sausis", "vasaris", "kovas", "balandis", "gegužė", "birželis", "liepa", "rugpjūtis", "rugsėjis", "spalis", "lapkritis", "gruodis", ] month_abbreviations = [ "", "saus", "vas", "kovas", "bal", "geg", "birž", "liepa", "rugp", "rugs", "spalis", "lapkr", "gr", ] day_names = [ "", "pirmadienis", "antradienis", "trečiadienis", "ketvirtadienis", "penktadienis", "šeštadienis", "sekmadienis", ] day_abbreviations = [ "", "pi", "an", "tr", "ke", "pe", "še", "se", ] class MalayLocale(Locale): names = ["ms", "ms-my", "ms-bn"] past = "{0} yang lalu" future = "dalam {0}" and_word = "dan" timeframes: ClassVar[Mapping[TimeFrameLiteral, Union[str, Mapping[str, str]]]] = { "now": "sekarang", "second": "saat", "seconds": "{0} saat", "minute": "minit", "minutes": "{0} minit", "hour": "jam", "hours": "{0} jam", "day": "hari", "days": "{0} hari", "week": "minggu", "weeks": "{0} minggu", "month": "bulan", "months": "{0} bulan", "year": "tahun", "years": "{0} tahun", } month_names = [ "", "Januari", "Februari", "Mac", "April", "Mei", "Jun", "Julai", "Ogos", "September", "Oktober", "November", "Disember", ] month_abbreviations = [ "", "Jan.", "Feb.", "Mac", "Apr.", "Mei", "Jun", "Julai", "Og.", "Sept.", "Okt.", "Nov.", "Dis.", ] day_names = [ "", "Isnin", "Selasa", "Rabu", "Khamis", "Jumaat", "Sabtu", "Ahad", ] day_abbreviations = [ "", "Isnin", "Selasa", "Rabu", "Khamis", "Jumaat", "Sabtu", "Ahad", ] class MalteseLocale(Locale): names = ["mt", "mt-mt"] past = "{0} ilu" future = "fi {0}" and_word = "u" timeframes: ClassVar[Mapping[TimeFrameLiteral, Union[str, Mapping[str, str]]]] = { "now": "issa", "second": "sekonda", "seconds": "{0} sekondi", "minute": "minuta", "minutes": "{0} minuti", "hour": "siegħa", "hours": {"dual": "{0} sagħtejn", "plural": "{0} sigħat"}, "day": "jum", "days": {"dual": "{0} jumejn", "plural": "{0} ijiem"}, "week": "ġimgħa", "weeks": {"dual": "{0} ġimagħtejn", "plural": "{0} ġimgħat"}, "month": "xahar", "months": {"dual": "{0} xahrejn", "plural": "{0} xhur"}, "year": "sena", "years": {"dual": "{0} sentejn", "plural": "{0} snin"}, } month_names = [ "", "Jannar", "Frar", "Marzu", "April", "Mejju", "Ġunju", "Lulju", "Awwissu", "Settembru", "Ottubru", "Novembru", "Diċembru", ] month_abbreviations = [ "", "Jan", "Fr", "Mar", "Apr", "Mejju", "Ġun", "Lul", "Aw", "Sett", "Ott", "Nov", "Diċ", ] day_names = [ "", "It-Tnejn", "It-Tlieta", "L-Erbgħa", "Il-Ħamis", "Il-Ġimgħa", "Is-Sibt", "Il-Ħadd", ] day_abbreviations = [ "", "T", "TL", "E", "Ħ", "Ġ", "S", "Ħ", ] def _format_timeframe(self, timeframe: TimeFrameLiteral, delta: int) -> str: form = self.timeframes[timeframe] delta = abs(delta) if isinstance(form, Mapping): if delta == 2: form = form["dual"] else: form = form["plural"] return form.format(delta) class SamiLocale(Locale): names = ["se", "se-fi", "se-no", "se-se"] past = "{0} dassái" future = "{0} " # NOTE: couldn't find preposition for Sami here, none needed? timeframes: ClassVar[Mapping[TimeFrameLiteral, Union[str, Mapping[str, str]]]] = { "now": "dál", "second": "sekunda", "seconds": "{0} sekundda", "minute": "minuhta", "minutes": "{0} minuhta", "hour": "diimmu", "hours": "{0} diimmu", "day": "beaivvi", "days": "{0} beaivvi", "week": "vahku", "weeks": "{0} vahku", "month": "mánu", "months": "{0} mánu", "year": "jagi", "years": "{0} jagi", } month_names = [ "", "Ođđajagimánnu", "Guovvamánnu", "Njukčamánnu", "Cuoŋománnu", "Miessemánnu", "Geassemánnu", "Suoidnemánnu", "Borgemánnu", "Čakčamánnu", "Golggotmánnu", "Skábmamánnu", "Juovlamánnu", ] month_abbreviations = [ "", "Ođđajagimánnu", "Guovvamánnu", "Njukčamánnu", "Cuoŋománnu", "Miessemánnu", "Geassemánnu", "Suoidnemánnu", "Borgemánnu", "Čakčamánnu", "Golggotmánnu", "Skábmamánnu", "Juovlamánnu", ] day_names = [ "", "Mánnodat", "Disdat", "Gaskavahkku", "Duorastat", "Bearjadat", "Lávvordat", "Sotnabeaivi", ] day_abbreviations = [ "", "Mánnodat", "Disdat", "Gaskavahkku", "Duorastat", "Bearjadat", "Lávvordat", "Sotnabeaivi", ] class OdiaLocale(Locale): names = ["or", "or-in"] past = "{0} ପୂର୍ବେ" future = "{0} ପରେ" timeframes = { "now": "ବର୍ତ୍ତମାନ", "second": "ଏକ ସେକେଣ୍ଡ", "seconds": "{0} ସେକେଣ୍ଡ", "minute": "ଏକ ମିନଟ", "minutes": "{0} ମିନଟ", "hour": "ଏକ ଘଣ୍ଟା", "hours": "{0} ଘଣ୍ଟା", "day": "ଏକ ଦିନ", "days": "{0} ଦିନ", "month": "ଏକ ମାସ", "months": "{0} ମାସ ", "year": "ଏକ ବର୍ଷ", "years": "{0} ବର୍ଷ", } meridians = {"am": "ପୂର୍ବାହ୍ନ", "pm": "ଅପରାହ୍ନ", "AM": "ପୂର୍ବାହ୍ନ", "PM": "ଅପରାହ୍ନ"} month_names = [ "", "ଜାନୁଆରୀ", "ଫେବୃଆରୀ", "ମାର୍ଚ୍ଚ୍", "ଅପ୍ରେଲ", "ମଇ", "ଜୁନ୍", "ଜୁଲାଇ", "ଅଗଷ୍ଟ", "ସେପ୍ଟେମ୍ବର", "ଅକ୍ଟୋବର୍", "ନଭେମ୍ବର୍", "ଡିସେମ୍ବର୍", ] month_abbreviations = [ "", "ଜାନୁ", "ଫେବୃ", "ମାର୍ଚ୍ଚ୍", "ଅପ୍ରେ", "ମଇ", "ଜୁନ୍", "ଜୁଲା", "ଅଗ", "ସେପ୍ଟେ", "ଅକ୍ଟୋ", "ନଭେ", "ଡିସେ", ] day_names = [ "", "ସୋମବାର", "ମଙ୍ଗଳବାର", "ବୁଧବାର", "ଗୁରୁବାର", "ଶୁକ୍ରବାର", "ଶନିବାର", "ରବିବାର", ] day_abbreviations = [ "", "ସୋମ", "ମଙ୍ଗଳ", "ବୁଧ", "ଗୁରୁ", "ଶୁକ୍ର", "ଶନି", "ରବି", ] def _ordinal_number(self, n: int) -> str: if n > 10 or n == 0: return f"{n}ତମ" if n in [1, 5, 7, 8, 9, 10]: return f"{n}ମ" if n in [2, 3]: return f"{n}ୟ" if n == 4: return f"{n}ର୍ଥ" if n == 6: return f"{n}ଷ୍ଠ" return "" class SerbianLocale(Locale): names = ["sr", "sr-rs", "sr-sp"] past = "pre {0}" future = "za {0}" and_word = "i" timeframes: ClassVar[Mapping[TimeFrameLiteral, Union[str, Mapping[str, str]]]] = { "now": "sada", "second": "sekundu", "seconds": {"double": "{0} sekunde", "higher": "{0} sekundi"}, "minute": "minutu", "minutes": {"double": "{0} minute", "higher": "{0} minuta"}, "hour": "sat", "hours": {"double": "{0} sata", "higher": "{0} sati"}, "day": "dan", "days": {"double": "{0} dana", "higher": "{0} dana"}, "week": "nedelju", "weeks": {"double": "{0} nedelje", "higher": "{0} nedelja"}, "month": "mesec", "months": {"double": "{0} meseca", "higher": "{0} meseci"}, "year": "godinu", "years": {"double": "{0} godine", "higher": "{0} godina"}, } month_names = [ "", "januar", # јануар "februar", # фебруар "mart", # март "april", # април "maj", # мај "jun", # јун "jul", # јул "avgust", # август "septembar", # септембар "oktobar", # октобар "novembar", # новембар "decembar", # децембар ] month_abbreviations = [ "", "jan", "feb", "mar", "apr", "maj", "jun", "jul", "avg", "sep", "okt", "nov", "dec", ] day_names = [ "", "ponedeljak", # понедељак "utorak", # уторак "sreda", # среда "četvrtak", # четвртак "petak", # петак "subota", # субота "nedelja", # недеља ] day_abbreviations = [ "", "po", # по "ut", # ут "sr", # ср "če", # че "pe", # пе "su", # су "ne", # не ] def _format_timeframe(self, timeframe: TimeFrameLiteral, delta: int) -> str: form = self.timeframes[timeframe] delta = abs(delta) if isinstance(form, Mapping): if 1 < delta <= 4: form = form["double"] else: form = form["higher"] return form.format(delta) class LuxembourgishLocale(Locale): names = ["lb", "lb-lu"] past = "virun {0}" future = "an {0}" and_word = "an" timeframes = { "now": "just elo", "second": "enger Sekonn", "seconds": "{0} Sekonnen", "minute": "enger Minutt", "minutes": "{0} Minutten", "hour": "enger Stonn", "hours": "{0} Stonnen", "day": "engem Dag", "days": "{0} Deeg", "week": "enger Woch", "weeks": "{0} Wochen", "month": "engem Mount", "months": "{0} Méint", "year": "engem Joer", "years": "{0} Jahren", } timeframes_only_distance = timeframes.copy() timeframes_only_distance["second"] = "eng Sekonn" timeframes_only_distance["minute"] = "eng Minutt" timeframes_only_distance["hour"] = "eng Stonn" timeframes_only_distance["day"] = "een Dag" timeframes_only_distance["days"] = "{0} Deeg" timeframes_only_distance["week"] = "eng Woch" timeframes_only_distance["month"] = "ee Mount" timeframes_only_distance["months"] = "{0} Méint" timeframes_only_distance["year"] = "ee Joer" timeframes_only_distance["years"] = "{0} Joer" month_names = [ "", "Januar", "Februar", "Mäerz", "Abrëll", "Mee", "Juni", "Juli", "August", "September", "Oktouber", "November", "Dezember", ] month_abbreviations = [ "", "Jan", "Feb", "Mäe", "Abr", "Mee", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez", ] day_names = [ "", "Méindeg", "Dënschdeg", "Mëttwoch", "Donneschdeg", "Freideg", "Samschdeg", "Sonndeg", ] day_abbreviations = ["", "Méi", "Dën", "Mët", "Don", "Fre", "Sam", "Son"] def _ordinal_number(self, n: int) -> str: return f"{n}." def describe( self, timeframe: TimeFrameLiteral, delta: Union[int, float] = 0, only_distance: bool = False, ) -> str: if not only_distance: return super().describe(timeframe, delta, only_distance) # Luxembourgish uses a different case without 'in' or 'ago' humanized = self.timeframes_only_distance[timeframe].format(trunc(abs(delta))) return humanized class ZuluLocale(Locale): names = ["zu", "zu-za"] past = "{0} edlule" future = "{0} " and_word = "futhi" timeframes: ClassVar[Mapping[TimeFrameLiteral, Union[Mapping[str, str], str]]] = { "now": "manje", "second": {"past": "umzuzwana", "future": "ngomzuzwana"}, "seconds": {"past": "{0} imizuzwana", "future": "{0} ngemizuzwana"}, "minute": {"past": "umzuzu", "future": "ngomzuzu"}, "minutes": {"past": "{0} imizuzu", "future": "{0} ngemizuzu"}, "hour": {"past": "ihora", "future": "ngehora"}, "hours": {"past": "{0} amahora", "future": "{0} emahoreni"}, "day": {"past": "usuku", "future": "ngosuku"}, "days": {"past": "{0} izinsuku", "future": "{0} ezinsukwini"}, "week": {"past": "isonto", "future": "ngesonto"}, "weeks": {"past": "{0} amasonto", "future": "{0} emasontweni"}, "month": {"past": "inyanga", "future": "ngenyanga"}, "months": {"past": "{0} izinyanga", "future": "{0} ezinyangeni"}, "year": {"past": "unyaka", "future": "ngonyak"}, "years": {"past": "{0} iminyaka", "future": "{0} eminyakeni"}, } def _format_timeframe(self, timeframe: TimeFrameLiteral, delta: int) -> str: """Zulu aware time frame format function, takes into account the differences between past and future forms.""" abs_delta = abs(delta) form = self.timeframes[timeframe] if isinstance(form, str): return form.format(abs_delta) if delta > 0: key = "future" else: key = "past" form = form[key] return form.format(abs_delta) month_names = [ "", "uMasingane", "uNhlolanja", "uNdasa", "UMbasa", "UNhlaba", "UNhlangulana", "uNtulikazi", "UNcwaba", "uMandulo", "uMfumfu", "uLwezi", "uZibandlela", ] month_abbreviations = [ "", "uMasingane", "uNhlolanja", "uNdasa", "UMbasa", "UNhlaba", "UNhlangulana", "uNtulikazi", "UNcwaba", "uMandulo", "uMfumfu", "uLwezi", "uZibandlela", ] day_names = [ "", "uMsombuluko", "uLwesibili", "uLwesithathu", "uLwesine", "uLwesihlanu", "uMgqibelo", "iSonto", ] day_abbreviations = [ "", "uMsombuluko", "uLwesibili", "uLwesithathu", "uLwesine", "uLwesihlanu", "uMgqibelo", "iSonto", ] class TamilLocale(Locale): names = ["ta", "ta-in", "ta-lk"] past = "{0} நேரத்திற்கு முன்பு" future = "இல் {0}" timeframes = { "now": "இப்போது", "second": "ஒரு இரண்டாவது", "seconds": "{0} விநாடிகள்", "minute": "ஒரு நிமிடம்", "minutes": "{0} நிமிடங்கள்", "hour": "ஒரு மணி", "hours": "{0} மணிநேரம்", "day": "ஒரு நாள்", "days": "{0} நாட்கள்", "week": "ஒரு வாரம்", "weeks": "{0} வாரங்கள்", "month": "ஒரு மாதம்", "months": "{0} மாதங்கள்", "year": "ஒரு ஆண்டு", "years": "{0} ஆண்டுகள்", } month_names = [ "", "சித்திரை", "வைகாசி", "ஆனி", "ஆடி", "ஆவணி", "புரட்டாசி", "ஐப்பசி", "கார்த்திகை", "மார்கழி", "தை", "மாசி", "பங்குனி", ] month_abbreviations = [ "", "ஜன", "பிப்", "மார்", "ஏப்", "மே", "ஜூன்", "ஜூலை", "ஆக", "செப்", "அக்", "நவ", "டிச", ] day_names = [ "", "திங்கட்கிழமை", "செவ்வாய்க்கிழமை", "புதன்கிழமை", "வியாழக்கிழமை", "வெள்ளிக்கிழமை", "சனிக்கிழமை", "ஞாயிற்றுக்கிழமை", ] day_abbreviations = [ "", "திங்கட்", "செவ்வாய்", "புதன்", "வியாழன்", "வெள்ளி", "சனி", "ஞாயிறு", ] def _ordinal_number(self, n: int) -> str: if n == 1: return f"{n}வது" elif n >= 0: return f"{n}ஆம்" else: return "" class AlbanianLocale(Locale): names = ["sq", "sq-al"] past = "{0} më parë" future = "në {0}" and_word = "dhe" timeframes = { "now": "tani", "second": "sekondë", "seconds": "{0} sekonda", "minute": "minutë", "minutes": "{0} minuta", "hour": "orë", "hours": "{0} orë", "day": "ditë", "days": "{0} ditë", "week": "javë", "weeks": "{0} javë", "month": "muaj", "months": "{0} muaj", "year": "vit", "years": "{0} vjet", } month_names = [ "", "janar", "shkurt", "mars", "prill", "maj", "qershor", "korrik", "gusht", "shtator", "tetor", "nëntor", "dhjetor", ] month_abbreviations = [ "", "jan", "shk", "mar", "pri", "maj", "qer", "korr", "gush", "sht", "tet", "nën", "dhj", ] day_names = [ "", "e hënë", "e martë", "e mërkurë", "e enjte", "e premte", "e shtunë", "e diel", ] day_abbreviations = [ "", "hën", "mar", "mër", "enj", "pre", "sht", "die", ] class SinhalaLocale(Locale): names = ["si", "si-lk"] past = "{0}ට පෙර" future = "{0}" and_word = "සහ" timeframes: ClassVar[Mapping[TimeFrameLiteral, Union[Mapping[str, str], str]]] = { "now": "දැන්", "second": { "past": "තත්පරයක", "future": "තත්පරයකින්", }, # ක් is the article "seconds": { "past": "තත්පර {0} ක", "future": "තත්පර {0} කින්", }, "minute": { "past": "විනාඩියක", "future": "විනාඩියකින්", }, "minutes": { "past": "විනාඩි {0} ක", "future": "මිනිත්තු {0} කින්", }, "hour": {"past": "පැයක", "future": "පැයකින්"}, "hours": { "past": "පැය {0} ක", "future": "පැය {0} කින්", }, "day": {"past": "දිනක", "future": "දිනකට"}, "days": { "past": "දින {0} ක", "future": "දින {0} කින්", }, "week": {"past": "සතියක", "future": "සතියකින්"}, "weeks": { "past": "සති {0} ක", "future": "සති {0} කින්", }, "month": {"past": "මාසයක", "future": "එය මාසය තුළ"}, "months": { "past": "මාස {0} ක", "future": "මාස {0} කින්", }, "year": {"past": "වසරක", "future": "වසරක් තුළ"}, "years": { "past": "අවුරුදු {0} ක", "future": "අවුරුදු {0} තුළ", }, } # Sinhala: the general format to describe timeframe is different from past and future, # so we do not copy the original timeframes dictionary timeframes_only_distance = dict() timeframes_only_distance["second"] = "තත්පරයක්" timeframes_only_distance["seconds"] = "තත්පර {0}" timeframes_only_distance["minute"] = "මිනිත්තුවක්" timeframes_only_distance["minutes"] = "විනාඩි {0}" timeframes_only_distance["hour"] = "පැයක්" timeframes_only_distance["hours"] = "පැය {0}" timeframes_only_distance["day"] = "දවසක්" timeframes_only_distance["days"] = "දවස් {0}" timeframes_only_distance["week"] = "සතියක්" timeframes_only_distance["weeks"] = "සති {0}" timeframes_only_distance["month"] = "මාසයක්" timeframes_only_distance["months"] = "මාස {0}" timeframes_only_distance["year"] = "අවුරුද්දක්" timeframes_only_distance["years"] = "අවුරුදු {0}" def _format_timeframe(self, timeframe: TimeFrameLiteral, delta: int) -> str: """ Sinhala awares time frame format function, takes into account the differences between general, past, and future forms (three different suffixes). """ abs_delta = abs(delta) form = self.timeframes[timeframe] if isinstance(form, str): return form.format(abs_delta) if delta > 0: key = "future" else: key = "past" form = form[key] return form.format(abs_delta) def describe( self, timeframe: TimeFrameLiteral, delta: Union[float, int] = 1, # key is always future when only_distance=False only_distance: bool = False, ) -> str: """Describes a delta within a timeframe in plain language. :param timeframe: a string representing a timeframe. :param delta: a quantity representing a delta in a timeframe. :param only_distance: return only distance eg: "11 seconds" without "in" or "ago" keywords """ if not only_distance: return super().describe(timeframe, delta, only_distance) # Sinhala uses a different case without 'in' or 'ago' humanized = self.timeframes_only_distance[timeframe].format(trunc(abs(delta))) return humanized month_names = [ "", "ජනවාරි", "පෙබරවාරි", "මාර්තු", "අප්‍රේල්", "මැයි", "ජූනි", "ජූලි", "අගෝස්තු", "සැප්තැම්බර්", "ඔක්තෝබර්", "නොවැම්බර්", "දෙසැම්බර්", ] month_abbreviations = [ "", "ජන", "පෙබ", "මාර්", "අප්‍රේ", "මැයි", "ජුනි", "ජූලි", "අගෝ", "සැප්", "ඔක්", "නොවැ", "දෙසැ", ] day_names = [ "", "සදුදා", "අඟහරැවදා", "බදාදා", "බ්‍රහස්‍පතින්‍දා", "සිකුරාදා", "සෙනසුරාදා", "ඉරිදා", ] day_abbreviations = [ "", "සදුද", "බදා", "බදා", "සිකු", "සෙන", "අ", "ඉරිදා", ] class UrduLocale(Locale): names = ["ur", "ur-pk"] past = "پہلے {0}" future = "میں {0}" and_word = "اور" timeframes = { "now": "ابھی", "second": "ایک سیکنڈ", "seconds": "{0} سیکنڈ", "minute": "ایک منٹ", "minutes": "{0} منٹ", "hour": "ایک گھنٹے", "hours": "{0} گھنٹے", "day": "ایک دن", "days": "{0} دن", "week": "ایک ہفتے", "weeks": "{0} ہفتے", "month": "ایک مہینہ", "months": "{0} ماہ", "year": "ایک سال", "years": "{0} سال", } month_names = [ "", "جنوری", "فروری", "مارچ", "اپریل", "مئی", "جون", "جولائی", "اگست", "ستمبر", "اکتوبر", "نومبر", "دسمبر", ] month_abbreviations = [ "", "جنوری", "فروری", "مارچ", "اپریل", "مئی", "جون", "جولائی", "اگست", "ستمبر", "اکتوبر", "نومبر", "دسمبر", ] day_names = [ "", "سوموار", "منگل", "بدھ", "جمعرات", "جمعہ", "ہفتہ", "اتوار", ] day_abbreviations = [ "", "سوموار", "منگل", "بدھ", "جمعرات", "جمعہ", "ہفتہ", "اتوار", ] python-arrow-1.2.1/arrow/parser.py000066400000000000000000000621701415100123600171630ustar00rootroot00000000000000"""Provides the :class:`Arrow ` class, a better way to parse datetime strings.""" import re import sys from datetime import datetime, timedelta from datetime import tzinfo as dt_tzinfo from functools import lru_cache from typing import ( Any, ClassVar, Dict, Iterable, List, Match, Optional, Pattern, SupportsFloat, SupportsInt, Tuple, Union, cast, overload, ) from dateutil import tz from arrow import locales from arrow.constants import DEFAULT_LOCALE from arrow.util import next_weekday, normalize_timestamp if sys.version_info < (3, 8): # pragma: no cover from typing_extensions import Literal, TypedDict else: from typing import Literal, TypedDict # pragma: no cover class ParserError(ValueError): pass # Allows for ParserErrors to be propagated from _build_datetime() # when day_of_year errors occur. # Before this, the ParserErrors were caught by the try/except in # _parse_multiformat() and the appropriate error message was not # transmitted to the user. class ParserMatchError(ParserError): pass _WEEKDATE_ELEMENT = Union[str, bytes, SupportsInt, bytearray] _FORMAT_TYPE = Literal[ "YYYY", "YY", "MM", "M", "DDDD", "DDD", "DD", "D", "HH", "H", "hh", "h", "mm", "m", "ss", "s", "X", "x", "ZZZ", "ZZ", "Z", "S", "W", "MMMM", "MMM", "Do", "dddd", "ddd", "d", "a", "A", ] class _Parts(TypedDict, total=False): year: int month: int day_of_year: int day: int hour: int minute: int second: int microsecond: int timestamp: float expanded_timestamp: int tzinfo: dt_tzinfo am_pm: Literal["am", "pm"] day_of_week: int weekdate: Tuple[_WEEKDATE_ELEMENT, _WEEKDATE_ELEMENT, Optional[_WEEKDATE_ELEMENT]] class DateTimeParser: _FORMAT_RE: ClassVar[Pattern[str]] = re.compile( r"(YYY?Y?|MM?M?M?|Do|DD?D?D?|d?d?d?d|HH?|hh?|mm?|ss?|S+|ZZ?Z?|a|A|x|X|W)" ) _ESCAPE_RE: ClassVar[Pattern[str]] = re.compile(r"\[[^\[\]]*\]") _ONE_OR_TWO_DIGIT_RE: ClassVar[Pattern[str]] = re.compile(r"\d{1,2}") _ONE_OR_TWO_OR_THREE_DIGIT_RE: ClassVar[Pattern[str]] = re.compile(r"\d{1,3}") _ONE_OR_MORE_DIGIT_RE: ClassVar[Pattern[str]] = re.compile(r"\d+") _TWO_DIGIT_RE: ClassVar[Pattern[str]] = re.compile(r"\d{2}") _THREE_DIGIT_RE: ClassVar[Pattern[str]] = re.compile(r"\d{3}") _FOUR_DIGIT_RE: ClassVar[Pattern[str]] = re.compile(r"\d{4}") _TZ_Z_RE: ClassVar[Pattern[str]] = re.compile(r"([\+\-])(\d{2})(?:(\d{2}))?|Z") _TZ_ZZ_RE: ClassVar[Pattern[str]] = re.compile(r"([\+\-])(\d{2})(?:\:(\d{2}))?|Z") _TZ_NAME_RE: ClassVar[Pattern[str]] = re.compile(r"\w[\w+\-/]+") # NOTE: timestamps cannot be parsed from natural language strings (by removing the ^...$) because it will # break cases like "15 Jul 2000" and a format list (see issue #447) _TIMESTAMP_RE: ClassVar[Pattern[str]] = re.compile(r"^\-?\d+\.?\d+$") _TIMESTAMP_EXPANDED_RE: ClassVar[Pattern[str]] = re.compile(r"^\-?\d+$") _TIME_RE: ClassVar[Pattern[str]] = re.compile( r"^(\d{2})(?:\:?(\d{2}))?(?:\:?(\d{2}))?(?:([\.\,])(\d+))?$" ) _WEEK_DATE_RE: ClassVar[Pattern[str]] = re.compile( r"(?P\d{4})[\-]?W(?P\d{2})[\-]?(?P\d)?" ) _BASE_INPUT_RE_MAP: ClassVar[Dict[_FORMAT_TYPE, Pattern[str]]] = { "YYYY": _FOUR_DIGIT_RE, "YY": _TWO_DIGIT_RE, "MM": _TWO_DIGIT_RE, "M": _ONE_OR_TWO_DIGIT_RE, "DDDD": _THREE_DIGIT_RE, "DDD": _ONE_OR_TWO_OR_THREE_DIGIT_RE, "DD": _TWO_DIGIT_RE, "D": _ONE_OR_TWO_DIGIT_RE, "HH": _TWO_DIGIT_RE, "H": _ONE_OR_TWO_DIGIT_RE, "hh": _TWO_DIGIT_RE, "h": _ONE_OR_TWO_DIGIT_RE, "mm": _TWO_DIGIT_RE, "m": _ONE_OR_TWO_DIGIT_RE, "ss": _TWO_DIGIT_RE, "s": _ONE_OR_TWO_DIGIT_RE, "X": _TIMESTAMP_RE, "x": _TIMESTAMP_EXPANDED_RE, "ZZZ": _TZ_NAME_RE, "ZZ": _TZ_ZZ_RE, "Z": _TZ_Z_RE, "S": _ONE_OR_MORE_DIGIT_RE, "W": _WEEK_DATE_RE, } SEPARATORS: ClassVar[List[str]] = ["-", "/", "."] locale: locales.Locale _input_re_map: Dict[_FORMAT_TYPE, Pattern[str]] def __init__(self, locale: str = DEFAULT_LOCALE, cache_size: int = 0) -> None: self.locale = locales.get_locale(locale) self._input_re_map = self._BASE_INPUT_RE_MAP.copy() self._input_re_map.update( { "MMMM": self._generate_choice_re( self.locale.month_names[1:], re.IGNORECASE ), "MMM": self._generate_choice_re( self.locale.month_abbreviations[1:], re.IGNORECASE ), "Do": re.compile(self.locale.ordinal_day_re), "dddd": self._generate_choice_re( self.locale.day_names[1:], re.IGNORECASE ), "ddd": self._generate_choice_re( self.locale.day_abbreviations[1:], re.IGNORECASE ), "d": re.compile(r"[1-7]"), "a": self._generate_choice_re( (self.locale.meridians["am"], self.locale.meridians["pm"]) ), # note: 'A' token accepts both 'am/pm' and 'AM/PM' formats to # ensure backwards compatibility of this token "A": self._generate_choice_re(self.locale.meridians.values()), } ) if cache_size > 0: self._generate_pattern_re = lru_cache(maxsize=cache_size)( # type: ignore self._generate_pattern_re ) # TODO: since we support more than ISO 8601, we should rename this function # IDEA: break into multiple functions def parse_iso( self, datetime_string: str, normalize_whitespace: bool = False ) -> datetime: if normalize_whitespace: datetime_string = re.sub(r"\s+", " ", datetime_string.strip()) has_space_divider = " " in datetime_string has_t_divider = "T" in datetime_string num_spaces = datetime_string.count(" ") if has_space_divider and num_spaces != 1 or has_t_divider and num_spaces > 0: raise ParserError( f"Expected an ISO 8601-like string, but was given {datetime_string!r}. " "Try passing in a format string to resolve this." ) has_time = has_space_divider or has_t_divider has_tz = False # date formats (ISO 8601 and others) to test against # NOTE: YYYYMM is omitted to avoid confusion with YYMMDD (no longer part of ISO 8601, but is still often used) formats = [ "YYYY-MM-DD", "YYYY-M-DD", "YYYY-M-D", "YYYY/MM/DD", "YYYY/M/DD", "YYYY/M/D", "YYYY.MM.DD", "YYYY.M.DD", "YYYY.M.D", "YYYYMMDD", "YYYY-DDDD", "YYYYDDDD", "YYYY-MM", "YYYY/MM", "YYYY.MM", "YYYY", "W", ] if has_time: if has_space_divider: date_string, time_string = datetime_string.split(" ", 1) else: date_string, time_string = datetime_string.split("T", 1) time_parts = re.split(r"[\+\-Z]", time_string, 1, re.IGNORECASE) time_components: Optional[Match[str]] = self._TIME_RE.match(time_parts[0]) if time_components is None: raise ParserError( "Invalid time component provided. " "Please specify a format or provide a valid time component in the basic or extended ISO 8601 time format." ) ( hours, minutes, seconds, subseconds_sep, subseconds, ) = time_components.groups() has_tz = len(time_parts) == 2 has_minutes = minutes is not None has_seconds = seconds is not None has_subseconds = subseconds is not None is_basic_time_format = ":" not in time_parts[0] tz_format = "Z" # use 'ZZ' token instead since tz offset is present in non-basic format if has_tz and ":" in time_parts[1]: tz_format = "ZZ" time_sep = "" if is_basic_time_format else ":" if has_subseconds: time_string = "HH{time_sep}mm{time_sep}ss{subseconds_sep}S".format( time_sep=time_sep, subseconds_sep=subseconds_sep ) elif has_seconds: time_string = "HH{time_sep}mm{time_sep}ss".format(time_sep=time_sep) elif has_minutes: time_string = f"HH{time_sep}mm" else: time_string = "HH" if has_space_divider: formats = [f"{f} {time_string}" for f in formats] else: formats = [f"{f}T{time_string}" for f in formats] if has_time and has_tz: # Add "Z" or "ZZ" to the format strings to indicate to # _parse_token() that a timezone needs to be parsed formats = [f"{f}{tz_format}" for f in formats] return self._parse_multiformat(datetime_string, formats) def parse( self, datetime_string: str, fmt: Union[List[str], str], normalize_whitespace: bool = False, ) -> datetime: if normalize_whitespace: datetime_string = re.sub(r"\s+", " ", datetime_string) if isinstance(fmt, list): return self._parse_multiformat(datetime_string, fmt) try: fmt_tokens: List[_FORMAT_TYPE] fmt_pattern_re: Pattern[str] fmt_tokens, fmt_pattern_re = self._generate_pattern_re(fmt) except re.error as e: raise ParserMatchError( f"Failed to generate regular expression pattern: {e}." ) match = fmt_pattern_re.search(datetime_string) if match is None: raise ParserMatchError( f"Failed to match {fmt!r} when parsing {datetime_string!r}." ) parts: _Parts = {} for token in fmt_tokens: value: Union[Tuple[str, str, str], str] if token == "Do": value = match.group("value") elif token == "W": value = (match.group("year"), match.group("week"), match.group("day")) else: value = match.group(token) if value is None: raise ParserMatchError( f"Unable to find a match group for the specified token {token!r}." ) self._parse_token(token, value, parts) # type: ignore return self._build_datetime(parts) def _generate_pattern_re(self, fmt: str) -> Tuple[List[_FORMAT_TYPE], Pattern[str]]: # fmt is a string of tokens like 'YYYY-MM-DD' # we construct a new string by replacing each # token by its pattern: # 'YYYY-MM-DD' -> '(?P\d{4})-(?P\d{2})-(?P
\d{2})' tokens: List[_FORMAT_TYPE] = [] offset = 0 # Escape all special RegEx chars escaped_fmt = re.escape(fmt) # Extract the bracketed expressions to be reinserted later. escaped_fmt = re.sub(self._ESCAPE_RE, "#", escaped_fmt) # Any number of S is the same as one. # TODO: allow users to specify the number of digits to parse escaped_fmt = re.sub(r"S+", "S", escaped_fmt) escaped_data = re.findall(self._ESCAPE_RE, fmt) fmt_pattern = escaped_fmt for m in self._FORMAT_RE.finditer(escaped_fmt): token: _FORMAT_TYPE = cast(_FORMAT_TYPE, m.group(0)) try: input_re = self._input_re_map[token] except KeyError: raise ParserError(f"Unrecognized token {token!r}.") input_pattern = f"(?P<{token}>{input_re.pattern})" tokens.append(token) # a pattern doesn't have the same length as the token # it replaces! We keep the difference in the offset variable. # This works because the string is scanned left-to-right and matches # are returned in the order found by finditer. fmt_pattern = ( fmt_pattern[: m.start() + offset] + input_pattern + fmt_pattern[m.end() + offset :] ) offset += len(input_pattern) - (m.end() - m.start()) final_fmt_pattern = "" split_fmt = fmt_pattern.split(r"\#") # Due to the way Python splits, 'split_fmt' will always be longer for i in range(len(split_fmt)): final_fmt_pattern += split_fmt[i] if i < len(escaped_data): final_fmt_pattern += escaped_data[i][1:-1] # Wrap final_fmt_pattern in a custom word boundary to strictly # match the formatting pattern and filter out date and time formats # that include junk such as: blah1998-09-12 blah, blah 1998-09-12blah, # blah1998-09-12blah. The custom word boundary matches every character # that is not a whitespace character to allow for searching for a date # and time string in a natural language sentence. Therefore, searching # for a string of the form YYYY-MM-DD in "blah 1998-09-12 blah" will # work properly. # Certain punctuation before or after the target pattern such as # "1998-09-12," is permitted. For the full list of valid punctuation, # see the documentation. starting_word_boundary = ( r"(?\s])" # This is the list of punctuation that is ok before the # pattern (i.e. "It can't not be these characters before the pattern") r"(\b|^)" # The \b is to block cases like 1201912 but allow 201912 for pattern YYYYMM. The ^ was necessary to allow a # negative number through i.e. before epoch numbers ) ending_word_boundary = ( r"(?=[\,\.\;\:\?\!\"\'\`\[\]\{\}\(\)\<\>]?" # Positive lookahead stating that these punctuation marks # can appear after the pattern at most 1 time r"(?!\S))" # Don't allow any non-whitespace character after the punctuation ) bounded_fmt_pattern = r"{}{}{}".format( starting_word_boundary, final_fmt_pattern, ending_word_boundary ) return tokens, re.compile(bounded_fmt_pattern, flags=re.IGNORECASE) @overload def _parse_token( self, token: Literal[ "YYYY", "YY", "MM", "M", "DDDD", "DDD", "DD", "D", "Do", "HH", "hh", "h", "H", "mm", "m", "ss", "s", "x", ], value: Union[str, bytes, SupportsInt, bytearray], parts: _Parts, ) -> None: ... # pragma: no cover @overload def _parse_token( self, token: Literal["X"], value: Union[str, bytes, SupportsFloat, bytearray], parts: _Parts, ) -> None: ... # pragma: no cover @overload def _parse_token( self, token: Literal["MMMM", "MMM", "dddd", "ddd", "S"], value: Union[str, bytes, bytearray], parts: _Parts, ) -> None: ... # pragma: no cover @overload def _parse_token( self, token: Literal["a", "A", "ZZZ", "ZZ", "Z"], value: Union[str, bytes], parts: _Parts, ) -> None: ... # pragma: no cover @overload def _parse_token( self, token: Literal["W"], value: Tuple[_WEEKDATE_ELEMENT, _WEEKDATE_ELEMENT, Optional[_WEEKDATE_ELEMENT]], parts: _Parts, ) -> None: ... # pragma: no cover def _parse_token( self, token: Any, value: Any, parts: _Parts, ) -> None: if token == "YYYY": parts["year"] = int(value) elif token == "YY": value = int(value) parts["year"] = 1900 + value if value > 68 else 2000 + value elif token in ["MMMM", "MMM"]: # FIXME: month_number() is nullable parts["month"] = self.locale.month_number(value.lower()) # type: ignore elif token in ["MM", "M"]: parts["month"] = int(value) elif token in ["DDDD", "DDD"]: parts["day_of_year"] = int(value) elif token in ["DD", "D"]: parts["day"] = int(value) elif token == "Do": parts["day"] = int(value) elif token == "dddd": # locale day names are 1-indexed day_of_week = [x.lower() for x in self.locale.day_names].index( value.lower() ) parts["day_of_week"] = day_of_week - 1 elif token == "ddd": # locale day abbreviations are 1-indexed day_of_week = [x.lower() for x in self.locale.day_abbreviations].index( value.lower() ) parts["day_of_week"] = day_of_week - 1 elif token.upper() in ["HH", "H"]: parts["hour"] = int(value) elif token in ["mm", "m"]: parts["minute"] = int(value) elif token in ["ss", "s"]: parts["second"] = int(value) elif token == "S": # We have the *most significant* digits of an arbitrary-precision integer. # We want the six most significant digits as an integer, rounded. # IDEA: add nanosecond support somehow? Need datetime support for it first. value = value.ljust(7, "0") # floating-point (IEEE-754) defaults to half-to-even rounding seventh_digit = int(value[6]) if seventh_digit == 5: rounding = int(value[5]) % 2 elif seventh_digit > 5: rounding = 1 else: rounding = 0 parts["microsecond"] = int(value[:6]) + rounding elif token == "X": parts["timestamp"] = float(value) elif token == "x": parts["expanded_timestamp"] = int(value) elif token in ["ZZZ", "ZZ", "Z"]: parts["tzinfo"] = TzinfoParser.parse(value) elif token in ["a", "A"]: if value in (self.locale.meridians["am"], self.locale.meridians["AM"]): parts["am_pm"] = "am" if "hour" in parts and not 0 <= parts["hour"] <= 12: raise ParserMatchError( f"Hour token value must be between 0 and 12 inclusive for token {token!r}." ) elif value in (self.locale.meridians["pm"], self.locale.meridians["PM"]): parts["am_pm"] = "pm" elif token == "W": parts["weekdate"] = value @staticmethod def _build_datetime(parts: _Parts) -> datetime: weekdate = parts.get("weekdate") if weekdate is not None: year, week = int(weekdate[0]), int(weekdate[1]) if weekdate[2] is not None: _day = int(weekdate[2]) else: # day not given, default to 1 _day = 1 date_string = f"{year}-{week}-{_day}" # tokens for ISO 8601 weekdates dt = datetime.strptime(date_string, "%G-%V-%u") parts["year"] = dt.year parts["month"] = dt.month parts["day"] = dt.day timestamp = parts.get("timestamp") if timestamp is not None: return datetime.fromtimestamp(timestamp, tz=tz.tzutc()) expanded_timestamp = parts.get("expanded_timestamp") if expanded_timestamp is not None: return datetime.fromtimestamp( normalize_timestamp(expanded_timestamp), tz=tz.tzutc(), ) day_of_year = parts.get("day_of_year") if day_of_year is not None: _year = parts.get("year") month = parts.get("month") if _year is None: raise ParserError( "Year component is required with the DDD and DDDD tokens." ) if month is not None: raise ParserError( "Month component is not allowed with the DDD and DDDD tokens." ) date_string = f"{_year}-{day_of_year}" try: dt = datetime.strptime(date_string, "%Y-%j") except ValueError: raise ParserError( f"The provided day of year {day_of_year!r} is invalid." ) parts["year"] = dt.year parts["month"] = dt.month parts["day"] = dt.day day_of_week: Optional[int] = parts.get("day_of_week") day = parts.get("day") # If day is passed, ignore day of week if day_of_week is not None and day is None: year = parts.get("year", 1970) month = parts.get("month", 1) day = 1 # dddd => first day of week after epoch # dddd YYYY => first day of week in specified year # dddd MM YYYY => first day of week in specified year and month # dddd MM => first day after epoch in specified month next_weekday_dt = next_weekday(datetime(year, month, day), day_of_week) parts["year"] = next_weekday_dt.year parts["month"] = next_weekday_dt.month parts["day"] = next_weekday_dt.day am_pm = parts.get("am_pm") hour = parts.get("hour", 0) if am_pm == "pm" and hour < 12: hour += 12 elif am_pm == "am" and hour == 12: hour = 0 # Support for midnight at the end of day if hour == 24: if parts.get("minute", 0) != 0: raise ParserError("Midnight at the end of day must not contain minutes") if parts.get("second", 0) != 0: raise ParserError("Midnight at the end of day must not contain seconds") if parts.get("microsecond", 0) != 0: raise ParserError( "Midnight at the end of day must not contain microseconds" ) hour = 0 day_increment = 1 else: day_increment = 0 # account for rounding up to 1000000 microsecond = parts.get("microsecond", 0) if microsecond == 1000000: microsecond = 0 second_increment = 1 else: second_increment = 0 increment = timedelta(days=day_increment, seconds=second_increment) return ( datetime( year=parts.get("year", 1), month=parts.get("month", 1), day=parts.get("day", 1), hour=hour, minute=parts.get("minute", 0), second=parts.get("second", 0), microsecond=microsecond, tzinfo=parts.get("tzinfo"), ) + increment ) def _parse_multiformat(self, string: str, formats: Iterable[str]) -> datetime: _datetime: Optional[datetime] = None for fmt in formats: try: _datetime = self.parse(string, fmt) break except ParserMatchError: pass if _datetime is None: supported_formats = ", ".join(formats) raise ParserError( f"Could not match input {string!r} to any of the following formats: {supported_formats}." ) return _datetime # generates a capture group of choices separated by an OR operator @staticmethod def _generate_choice_re( choices: Iterable[str], flags: Union[int, re.RegexFlag] = 0 ) -> Pattern[str]: return re.compile(r"({})".format("|".join(choices)), flags=flags) class TzinfoParser: _TZINFO_RE: ClassVar[Pattern[str]] = re.compile( r"^([\+\-])?(\d{2})(?:\:?(\d{2}))?$" ) @classmethod def parse(cls, tzinfo_string: str) -> dt_tzinfo: tzinfo: Optional[dt_tzinfo] = None if tzinfo_string == "local": tzinfo = tz.tzlocal() elif tzinfo_string in ["utc", "UTC", "Z"]: tzinfo = tz.tzutc() else: iso_match = cls._TZINFO_RE.match(tzinfo_string) if iso_match: sign: Optional[str] hours: str minutes: Union[str, int, None] sign, hours, minutes = iso_match.groups() seconds = int(hours) * 3600 + int(minutes or 0) * 60 if sign == "-": seconds *= -1 tzinfo = tz.tzoffset(None, seconds) else: tzinfo = tz.gettz(tzinfo_string) if tzinfo is None: raise ParserError(f"Could not parse timezone expression {tzinfo_string!r}.") return tzinfo python-arrow-1.2.1/arrow/py.typed000066400000000000000000000000001415100123600167740ustar00rootroot00000000000000python-arrow-1.2.1/arrow/util.py000066400000000000000000000071371415100123600166460ustar00rootroot00000000000000"""Helpful functions used internally within arrow.""" import datetime from typing import Any, Optional, cast from dateutil.rrule import WEEKLY, rrule from arrow.constants import ( MAX_ORDINAL, MAX_TIMESTAMP, MAX_TIMESTAMP_MS, MAX_TIMESTAMP_US, MIN_ORDINAL, ) def next_weekday( start_date: Optional[datetime.date], weekday: int ) -> datetime.datetime: """Get next weekday from the specified start date. :param start_date: Datetime object representing the start date. :param weekday: Next weekday to obtain. Can be a value between 0 (Monday) and 6 (Sunday). :return: Datetime object corresponding to the next weekday after start_date. Usage:: # Get first Monday after epoch >>> next_weekday(datetime(1970, 1, 1), 0) 1970-01-05 00:00:00 # Get first Thursday after epoch >>> next_weekday(datetime(1970, 1, 1), 3) 1970-01-01 00:00:00 # Get first Sunday after epoch >>> next_weekday(datetime(1970, 1, 1), 6) 1970-01-04 00:00:00 """ if weekday < 0 or weekday > 6: raise ValueError("Weekday must be between 0 (Monday) and 6 (Sunday).") return cast( datetime.datetime, rrule(freq=WEEKLY, dtstart=start_date, byweekday=weekday, count=1)[0], ) def is_timestamp(value: Any) -> bool: """Check if value is a valid timestamp.""" if isinstance(value, bool): return False if not isinstance(value, (int, float, str)): return False try: float(value) return True except ValueError: return False def validate_ordinal(value: Any) -> None: """Raise an exception if value is an invalid Gregorian ordinal. :param value: the input to be checked """ if isinstance(value, bool) or not isinstance(value, int): raise TypeError(f"Ordinal must be an integer (got type {type(value)}).") if not (MIN_ORDINAL <= value <= MAX_ORDINAL): raise ValueError(f"Ordinal {value} is out of range.") def normalize_timestamp(timestamp: float) -> float: """Normalize millisecond and microsecond timestamps into normal timestamps.""" if timestamp > MAX_TIMESTAMP: if timestamp < MAX_TIMESTAMP_MS: timestamp /= 1000 elif timestamp < MAX_TIMESTAMP_US: timestamp /= 1_000_000 else: raise ValueError(f"The specified timestamp {timestamp!r} is too large.") return timestamp # Credit to https://stackoverflow.com/a/1700069 def iso_to_gregorian(iso_year: int, iso_week: int, iso_day: int) -> datetime.date: """Converts an ISO week date into a datetime object. :param iso_year: the year :param iso_week: the week number, each year has either 52 or 53 weeks :param iso_day: the day numbered 1 through 7, beginning with Monday """ if not 1 <= iso_week <= 53: raise ValueError("ISO Calendar week value must be between 1-53.") if not 1 <= iso_day <= 7: raise ValueError("ISO Calendar day value must be between 1-7") # The first week of the year always contains 4 Jan. fourth_jan = datetime.date(iso_year, 1, 4) delta = datetime.timedelta(fourth_jan.isoweekday() - 1) year_start = fourth_jan - delta gregorian = year_start + datetime.timedelta(days=iso_day - 1, weeks=iso_week - 1) return gregorian def validate_bounds(bounds: str) -> None: if bounds != "()" and bounds != "(]" and bounds != "[)" and bounds != "[]": raise ValueError( "Invalid bounds. Please select between '()', '(]', '[)', or '[]'." ) __all__ = ["next_weekday", "is_timestamp", "validate_ordinal", "iso_to_gregorian"] python-arrow-1.2.1/docs/000077500000000000000000000000001415100123600151055ustar00rootroot00000000000000python-arrow-1.2.1/docs/Makefile000066400000000000000000000011721415100123600165460ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) python-arrow-1.2.1/docs/conf.py000066400000000000000000000027731415100123600164150ustar00rootroot00000000000000# mypy: ignore-errors # -- Path setup -------------------------------------------------------------- import os import sys sys.path.insert(0, os.path.abspath("..")) about = {} with open("../arrow/_version.py", encoding="utf-8") as f: exec(f.read(), about) # -- Project information ----------------------------------------------------- project = "Arrow 🏹" copyright = "2021, Chris Smith" author = "Chris Smith" release = about["__version__"] # -- General configuration --------------------------------------------------- extensions = ["sphinx.ext.autodoc", "sphinx_autodoc_typehints"] templates_path = [] exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] master_doc = "index" source_suffix = ".rst" pygments_style = "sphinx" language = None # -- Options for HTML output ------------------------------------------------- html_theme = "alabaster" html_theme_path = [] html_static_path = [] html_show_sourcelink = False html_show_sphinx = False html_show_copyright = True # https://alabaster.readthedocs.io/en/latest/customization.html html_theme_options = { "description": "Arrow is a sensible and human-friendly approach to dates, times and timestamps.", "github_user": "arrow-py", "github_repo": "arrow", "github_banner": True, "show_related": False, "show_powered_by": False, "github_button": True, "github_type": "star", "github_count": "true", # must be a string } html_sidebars = { "**": ["about.html", "localtoc.html", "relations.html", "searchbox.html"] } python-arrow-1.2.1/docs/index.rst000066400000000000000000000475271415100123600167650ustar00rootroot00000000000000Arrow: Better dates & times for Python ====================================== Release v\ |release| (`Installation`_) (`Changelog `_) .. include:: ../README.rst :start-after: start-inclusion-marker-do-not-remove :end-before: end-inclusion-marker-do-not-remove User's Guide ------------ Creation ~~~~~~~~ Get 'now' easily: .. code-block:: python >>> arrow.utcnow() >>> arrow.now() >>> arrow.now('US/Pacific') Create from timestamps (:code:`int` or :code:`float`): .. code-block:: python >>> arrow.get(1367900664) >>> arrow.get(1367900664.152325) Use a naive or timezone-aware datetime, or flexibly specify a timezone: .. code-block:: python >>> arrow.get(datetime.utcnow()) >>> arrow.get(datetime(2013, 5, 5), 'US/Pacific') >>> from dateutil import tz >>> arrow.get(datetime(2013, 5, 5), tz.gettz('US/Pacific')) >>> arrow.get(datetime.now(tz.gettz('US/Pacific'))) Parse from a string: .. code-block:: python >>> arrow.get('2013-05-05 12:30:45', 'YYYY-MM-DD HH:mm:ss') Search a date in a string: .. code-block:: python >>> arrow.get('June was born in May 1980', 'MMMM YYYY') Some ISO 8601 compliant strings are recognized and parsed without a format string: >>> arrow.get('2013-09-30T15:34:00.000-07:00') Arrow objects can be instantiated directly too, with the same arguments as a datetime: .. code-block:: python >>> arrow.get(2013, 5, 5) >>> arrow.Arrow(2013, 5, 5) Properties ~~~~~~~~~~ Get a datetime or timestamp representation: .. code-block:: python >>> a = arrow.utcnow() >>> a.datetime datetime.datetime(2013, 5, 7, 4, 38, 15, 447644, tzinfo=tzutc()) Get a naive datetime, and tzinfo: .. code-block:: python >>> a.naive datetime.datetime(2013, 5, 7, 4, 38, 15, 447644) >>> a.tzinfo tzutc() Get any datetime value: .. code-block:: python >>> a.year 2013 Call datetime functions that return properties: .. code-block:: python >>> a.date() datetime.date(2013, 5, 7) >>> a.time() datetime.time(4, 38, 15, 447644) Replace & Shift ~~~~~~~~~~~~~~~ Get a new :class:`Arrow ` object, with altered attributes, just as you would with a datetime: .. code-block:: python >>> arw = arrow.utcnow() >>> arw >>> arw.replace(hour=4, minute=40) Or, get one with attributes shifted forward or backward: .. code-block:: python >>> arw.shift(weeks=+3) Even replace the timezone without altering other attributes: .. code-block:: python >>> arw.replace(tzinfo='US/Pacific') Move between the earlier and later moments of an ambiguous time: .. code-block:: python >>> paris_transition = arrow.Arrow(2019, 10, 27, 2, tzinfo="Europe/Paris", fold=0) >>> paris_transition >>> paris_transition.ambiguous True >>> paris_transition.replace(fold=1) Format ~~~~~~ .. code-block:: python >>> arrow.utcnow().format('YYYY-MM-DD HH:mm:ss ZZ') '2013-05-07 05:23:16 -00:00' Convert ~~~~~~~ Convert from UTC to other timezones by name or tzinfo: .. code-block:: python >>> utc = arrow.utcnow() >>> utc >>> utc.to('US/Pacific') >>> utc.to(tz.gettz('US/Pacific')) Or using shorthand: .. code-block:: python >>> utc.to('local') >>> utc.to('local').to('utc') Humanize ~~~~~~~~ Humanize relative to now: .. code-block:: python >>> past = arrow.utcnow().shift(hours=-1) >>> past.humanize() 'an hour ago' Or another Arrow, or datetime: .. code-block:: python >>> present = arrow.utcnow() >>> future = present.shift(hours=2) >>> future.humanize(present) 'in 2 hours' Indicate time as relative or include only the distance .. code-block:: python >>> present = arrow.utcnow() >>> future = present.shift(hours=2) >>> future.humanize(present) 'in 2 hours' >>> future.humanize(present, only_distance=True) '2 hours' Indicate a specific time granularity (or multiple): .. code-block:: python >>> present = arrow.utcnow() >>> future = present.shift(minutes=66) >>> future.humanize(present, granularity="minute") 'in 66 minutes' >>> future.humanize(present, granularity=["hour", "minute"]) 'in an hour and 6 minutes' >>> present.humanize(future, granularity=["hour", "minute"]) 'an hour and 6 minutes ago' >>> future.humanize(present, only_distance=True, granularity=["hour", "minute"]) 'an hour and 6 minutes' Support for a growing number of locales (see ``locales.py`` for supported languages): .. code-block:: python >>> future = arrow.utcnow().shift(hours=1) >>> future.humanize(a, locale='ru') 'через 2 час(а,ов)' Dehumanize ~~~~~~~~~~ Take a human readable string and use it to shift into a past time: .. code-block:: python >>> arw = arrow.utcnow() >>> arw >>> earlier = arw.dehumanize("2 days ago") >>> earlier Or use it to shift into a future time: .. code-block:: python >>> arw = arrow.utcnow() >>> arw >>> later = arw.dehumanize("in a month") >>> later Support for a growing number of locales (see ``constants.py`` for supported languages): .. code-block:: python >>> arw = arrow.utcnow() >>> arw >>> later = arw.dehumanize("एक माह बाद", locale="hi") >>> later Ranges & Spans ~~~~~~~~~~~~~~ Get the time span of any unit: .. code-block:: python >>> arrow.utcnow().span('hour') (, ) Or just get the floor and ceiling: .. code-block:: python >>> arrow.utcnow().floor('hour') >>> arrow.utcnow().ceil('hour') You can also get a range of time spans: .. code-block:: python >>> start = datetime(2013, 5, 5, 12, 30) >>> end = datetime(2013, 5, 5, 17, 15) >>> for r in arrow.Arrow.span_range('hour', start, end): ... print r ... (, ) (, ) (, ) (, ) (, ) Or just iterate over a range of time: .. code-block:: python >>> start = datetime(2013, 5, 5, 12, 30) >>> end = datetime(2013, 5, 5, 17, 15) >>> for r in arrow.Arrow.range('hour', start, end): ... print repr(r) ... .. toctree:: :maxdepth: 2 Factories ~~~~~~~~~ Use factories to harness Arrow's module API for a custom Arrow-derived type. First, derive your type: .. code-block:: python >>> class CustomArrow(arrow.Arrow): ... ... def days_till_xmas(self): ... ... xmas = arrow.Arrow(self.year, 12, 25) ... ... if self > xmas: ... xmas = xmas.shift(years=1) ... ... return (xmas - self).days Then get and use a factory for it: .. code-block:: python >>> factory = arrow.ArrowFactory(CustomArrow) >>> custom = factory.utcnow() >>> custom >>> >>> custom.days_till_xmas() >>> 211 Supported Tokens ~~~~~~~~~~~~~~~~ Use the following tokens for parsing and formatting. Note that they are **not** the same as the tokens for `strptime `_: +--------------------------------+--------------+-------------------------------------------+ | |Token |Output | +================================+==============+===========================================+ |**Year** |YYYY |2000, 2001, 2002 ... 2012, 2013 | +--------------------------------+--------------+-------------------------------------------+ | |YY |00, 01, 02 ... 12, 13 | +--------------------------------+--------------+-------------------------------------------+ |**Month** |MMMM |January, February, March ... [#t1]_ | +--------------------------------+--------------+-------------------------------------------+ | |MMM |Jan, Feb, Mar ... [#t1]_ | +--------------------------------+--------------+-------------------------------------------+ | |MM |01, 02, 03 ... 11, 12 | +--------------------------------+--------------+-------------------------------------------+ | |M |1, 2, 3 ... 11, 12 | +--------------------------------+--------------+-------------------------------------------+ |**Day of Year** |DDDD |001, 002, 003 ... 364, 365 | +--------------------------------+--------------+-------------------------------------------+ | |DDD |1, 2, 3 ... 364, 365 | +--------------------------------+--------------+-------------------------------------------+ |**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 ... [#t2]_ | +--------------------------------+--------------+-------------------------------------------+ | |ddd |Mon, Tue, Wed ... [#t2]_ | +--------------------------------+--------------+-------------------------------------------+ | |d |1, 2, 3 ... 6, 7 | +--------------------------------+--------------+-------------------------------------------+ |**ISO week date** |W |2011-W05-4, 2019-W17 | +--------------------------------+--------------+-------------------------------------------+ |**Hour** |HH |00, 01, 02 ... 23, 24 | +--------------------------------+--------------+-------------------------------------------+ | |H |0, 1, 2 ... 23, 24 | +--------------------------------+--------------+-------------------------------------------+ | |hh |01, 02, 03 ... 11, 12 | +--------------------------------+--------------+-------------------------------------------+ | |h |1, 2, 3 ... 11, 12 | +--------------------------------+--------------+-------------------------------------------+ |**AM / PM** |A |AM, PM, am, pm [#t1]_ | +--------------------------------+--------------+-------------------------------------------+ | |a |am, pm [#t1]_ | +--------------------------------+--------------+-------------------------------------------+ |**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 | +--------------------------------+--------------+-------------------------------------------+ |**Sub-second** |S... |0, 02, 003, 000006, 123123123123... [#t3]_ | +--------------------------------+--------------+-------------------------------------------+ |**Timezone** |ZZZ |Asia/Baku, Europe/Warsaw, GMT ... [#t4]_ | +--------------------------------+--------------+-------------------------------------------+ | |ZZ |-07:00, -06:00 ... +06:00, +07:00, +08, Z | +--------------------------------+--------------+-------------------------------------------+ | |Z |-0700, -0600 ... +0600, +0700, +08, Z | +--------------------------------+--------------+-------------------------------------------+ |**Seconds Timestamp** |X |1381685817, 1381685817.915482 ... [#t5]_ | +--------------------------------+--------------+-------------------------------------------+ |**ms or µs Timestamp** |x |1569980330813, 1569980330813221 | +--------------------------------+--------------+-------------------------------------------+ .. rubric:: Footnotes .. [#t1] localization support for parsing and formatting .. [#t2] localization support only for formatting .. [#t3] the result is truncated to microseconds, with `half-to-even rounding `_. .. [#t4] timezone names from `tz database `_ provided via dateutil package, note that abbreviations such as MST, PDT, BRST are unlikely to parse due to ambiguity. Use the full IANA zone name instead (Asia/Shanghai, Europe/London, America/Chicago etc). .. [#t5] this token cannot be used for parsing timestamps out of natural language strings due to compatibility reasons Built-in Formats ++++++++++++++++ There are several formatting standards that are provided as built-in tokens. .. code-block:: python >>> arw = arrow.utcnow() >>> arw.format(arrow.FORMAT_ATOM) '2020-05-27 10:30:35+00:00' >>> arw.format(arrow.FORMAT_COOKIE) 'Wednesday, 27-May-2020 10:30:35 UTC' >>> arw.format(arrow.FORMAT_RSS) 'Wed, 27 May 2020 10:30:35 +0000' >>> arw.format(arrow.FORMAT_RFC822) 'Wed, 27 May 20 10:30:35 +0000' >>> arw.format(arrow.FORMAT_RFC850) 'Wednesday, 27-May-20 10:30:35 UTC' >>> arw.format(arrow.FORMAT_RFC1036) 'Wed, 27 May 20 10:30:35 +0000' >>> arw.format(arrow.FORMAT_RFC1123) 'Wed, 27 May 2020 10:30:35 +0000' >>> arw.format(arrow.FORMAT_RFC2822) 'Wed, 27 May 2020 10:30:35 +0000' >>> arw.format(arrow.FORMAT_RFC3339) '2020-05-27 10:30:35+00:00' >>> arw.format(arrow.FORMAT_W3C) '2020-05-27 10:30:35+00:00' Escaping Formats ~~~~~~~~~~~~~~~~ Tokens, phrases, and regular expressions in a format string can be escaped when parsing and formatting by enclosing them within square brackets. Tokens & Phrases ++++++++++++++++ Any `token `_ or phrase can be escaped as follows: .. code-block:: python >>> fmt = "YYYY-MM-DD h [h] m" >>> arw = arrow.get("2018-03-09 8 h 40", fmt) >>> arw.format(fmt) '2018-03-09 8 h 40' >>> fmt = "YYYY-MM-DD h [hello] m" >>> arw = arrow.get("2018-03-09 8 hello 40", fmt) >>> arw.format(fmt) '2018-03-09 8 hello 40' >>> fmt = "YYYY-MM-DD h [hello world] m" >>> arw = arrow.get("2018-03-09 8 hello world 40", fmt) >>> arw.format(fmt) '2018-03-09 8 hello world 40' This can be useful for parsing dates in different locales such as French, in which it is common to format time strings as "8 h 40" rather than "8:40". Regular Expressions +++++++++++++++++++ You can also escape regular expressions by enclosing them within square brackets. In the following example, we are using the regular expression :code:`\s+` to match any number of whitespace characters that separate the tokens. This is useful if you do not know the number of spaces between tokens ahead of time (e.g. in log files). .. code-block:: python >>> fmt = r"ddd[\s+]MMM[\s+]DD[\s+]HH:mm:ss[\s+]YYYY" >>> arrow.get("Mon Sep 08 16:41:45 2014", fmt) >>> arrow.get("Mon \tSep 08 16:41:45 2014", fmt) >>> arrow.get("Mon Sep 08 16:41:45 2014", fmt) Punctuation ~~~~~~~~~~~ Date and time formats may be fenced on either side by one punctuation character from the following list: ``, . ; : ? ! " \` ' [ ] { } ( ) < >`` .. code-block:: python >>> arrow.get("Cool date: 2019-10-31T09:12:45.123456+04:30.", "YYYY-MM-DDTHH:mm:ss.SZZ") >>> arrow.get("Tomorrow (2019-10-31) is Halloween!", "YYYY-MM-DD") >>> arrow.get("Halloween is on 2019.10.31.", "YYYY.MM.DD") >>> arrow.get("It's Halloween tomorrow (2019-10-31)!", "YYYY-MM-DD") # Raises exception because there are multiple punctuation marks following the date Redundant Whitespace ~~~~~~~~~~~~~~~~~~~~ Redundant whitespace characters (spaces, tabs, and newlines) can be normalized automatically by passing in the ``normalize_whitespace`` flag to ``arrow.get``: .. code-block:: python >>> arrow.get('\t \n 2013-05-05T12:30:45.123456 \t \n', normalize_whitespace=True) >>> arrow.get('2013-05-05 T \n 12:30:45\t123456', 'YYYY-MM-DD T HH:mm:ss S', normalize_whitespace=True) API Guide --------- arrow.arrow ~~~~~~~~~~~ .. automodule:: arrow.arrow :members: arrow.factory ~~~~~~~~~~~~~ .. automodule:: arrow.factory :members: arrow.api ~~~~~~~~~ .. automodule:: arrow.api :members: arrow.locale ~~~~~~~~~~~~ .. automodule:: arrow.locales :members: :undoc-members: Release History --------------- .. toctree:: :maxdepth: 2 releases python-arrow-1.2.1/docs/make.bat000066400000000000000000000013701415100123600165130ustar00rootroot00000000000000@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=. set BUILDDIR=_build if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end popd python-arrow-1.2.1/docs/releases.rst000066400000000000000000000000551415100123600174420ustar00rootroot00000000000000.. _releases: .. include:: ../CHANGELOG.rst python-arrow-1.2.1/requirements-dev.txt000066400000000000000000000003251415100123600202150ustar00rootroot00000000000000dateparser==1.* pre-commit==2.* pytest==6.* pytest-cov==3.* pytest-mock==3.* python-dateutil>=2.7.0 pytz==2021.1 simplejson==3.* sphinx==4.* sphinx-autodoc-typehints==1.* typing_extensions; python_version < '3.8' python-arrow-1.2.1/setup.cfg000066400000000000000000000012111415100123600157710ustar00rootroot00000000000000[mypy] python_version = 3.6 allow_any_expr = True allow_any_decorated = True allow_any_explicit = True disallow_any_generics = True disallow_subclassing_any = True disallow_untyped_calls = True disallow_untyped_defs = True disallow_incomplete_defs = True disallow_untyped_decorators = True no_implicit_optional = True warn_redundant_casts = True warn_unused_ignores = True no_warn_no_return = True warn_return_any = True warn_unreachable = True strict_equality = True no_implicit_reexport = True allow_redefinition = True # Type annotations for testing code and migration files are not mandatory [mypy-*.tests.*,tests.*] ignore_errors = True python-arrow-1.2.1/setup.py000066400000000000000000000032021415100123600156640ustar00rootroot00000000000000# mypy: ignore-errors from pathlib import Path from setuptools import setup readme = Path("README.rst").read_text(encoding="utf-8") version = Path("arrow/_version.py").read_text(encoding="utf-8") about = {} exec(version, about) setup( name="arrow", version=about["__version__"], description="Better dates & times for Python", long_description=readme, long_description_content_type="text/x-rst", url="https://arrow.readthedocs.io", author="Chris Smith", author_email="crsmithdev@gmail.com", license="Apache 2.0", packages=["arrow"], package_data={"arrow": ["py.typed"]}, zip_safe=False, python_requires=">=3.6", install_requires=[ "python-dateutil>=2.7.0", "typing_extensions; python_version<'3.8'", ], classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Topic :: Software Development :: Libraries :: Python Modules", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", ], keywords="arrow date time datetime timestamp timezone humanize", project_urls={ "Repository": "https://github.com/arrow-py/arrow", "Bug Reports": "https://github.com/arrow-py/arrow/issues", "Documentation": "https://arrow.readthedocs.io", }, ) python-arrow-1.2.1/tests/000077500000000000000000000000001415100123600153175ustar00rootroot00000000000000python-arrow-1.2.1/tests/__init__.py000066400000000000000000000000001415100123600174160ustar00rootroot00000000000000python-arrow-1.2.1/tests/conftest.py000066400000000000000000000037531415100123600175260ustar00rootroot00000000000000from datetime import datetime import pytest from dateutil import tz as dateutil_tz from arrow import arrow, factory, formatter, locales, parser @pytest.fixture(scope="class") def time_utcnow(request): request.cls.arrow = arrow.Arrow.utcnow() @pytest.fixture(scope="class") def time_2013_01_01(request): request.cls.now = arrow.Arrow.utcnow() request.cls.arrow = arrow.Arrow(2013, 1, 1) request.cls.datetime = datetime(2013, 1, 1) @pytest.fixture(scope="class") def time_2013_02_03(request): request.cls.arrow = arrow.Arrow(2013, 2, 3, 12, 30, 45, 1) @pytest.fixture(scope="class") def time_2013_02_15(request): request.cls.datetime = datetime(2013, 2, 15, 3, 41, 22, 8923) request.cls.arrow = arrow.Arrow.fromdatetime(request.cls.datetime) @pytest.fixture(scope="class") def time_1975_12_25(request): request.cls.datetime = datetime( 1975, 12, 25, 14, 15, 16, tzinfo=dateutil_tz.gettz("America/New_York") ) request.cls.arrow = arrow.Arrow.fromdatetime(request.cls.datetime) @pytest.fixture(scope="class") def arrow_formatter(request): request.cls.formatter = formatter.DateTimeFormatter() @pytest.fixture(scope="class") def arrow_factory(request): request.cls.factory = factory.ArrowFactory() @pytest.fixture(scope="class") def lang_locales(request): request.cls.locales = locales._locale_map @pytest.fixture(scope="class") def lang_locale(request): # As locale test classes are prefixed with Test, we are dynamically getting the locale by the test class name. # TestEnglishLocale -> EnglishLocale name = request.cls.__name__[4:] request.cls.locale = locales.get_locale_by_class_name(name) @pytest.fixture(scope="class") def dt_parser(request): request.cls.parser = parser.DateTimeParser() @pytest.fixture(scope="class") def dt_parser_regex(request): request.cls.format_regex = parser.DateTimeParser._FORMAT_RE @pytest.fixture(scope="class") def tzinfo_parser(request): request.cls.parser = parser.TzinfoParser() python-arrow-1.2.1/tests/test_api.py000066400000000000000000000014021415100123600174760ustar00rootroot00000000000000import arrow class TestModule: def test_get(self, mocker): mocker.patch("arrow.api._factory.get", return_value="result") assert arrow.api.get() == "result" def test_utcnow(self, mocker): mocker.patch("arrow.api._factory.utcnow", return_value="utcnow") assert arrow.api.utcnow() == "utcnow" def test_now(self, mocker): mocker.patch("arrow.api._factory.now", tz="tz", return_value="now") assert arrow.api.now("tz") == "now" def test_factory(self): class MockCustomArrowClass(arrow.Arrow): pass result = arrow.api.factory(MockCustomArrowClass) assert isinstance(result, arrow.factory.ArrowFactory) assert isinstance(result.utcnow(), MockCustomArrowClass) python-arrow-1.2.1/tests/test_arrow.py000066400000000000000000003014041415100123600200640ustar00rootroot00000000000000import pickle import sys import time from datetime import date, datetime, timedelta import dateutil import pytest import pytz import simplejson as json from dateutil import tz from dateutil.relativedelta import FR, MO, SA, SU, TH, TU, WE from arrow import arrow, locales from .utils import assert_datetime_equality class TestTestArrowInit: def test_init_bad_input(self): with pytest.raises(TypeError): arrow.Arrow(2013) with pytest.raises(TypeError): arrow.Arrow(2013, 2) with pytest.raises(ValueError): arrow.Arrow(2013, 2, 2, 12, 30, 45, 9999999) def test_init(self): result = arrow.Arrow(2013, 2, 2) self.expected = datetime(2013, 2, 2, tzinfo=tz.tzutc()) assert result._datetime == self.expected result = arrow.Arrow(2013, 2, 2, 12) self.expected = datetime(2013, 2, 2, 12, tzinfo=tz.tzutc()) assert result._datetime == self.expected result = arrow.Arrow(2013, 2, 2, 12, 30) self.expected = datetime(2013, 2, 2, 12, 30, tzinfo=tz.tzutc()) assert result._datetime == self.expected result = arrow.Arrow(2013, 2, 2, 12, 30, 45) self.expected = datetime(2013, 2, 2, 12, 30, 45, tzinfo=tz.tzutc()) assert result._datetime == self.expected result = arrow.Arrow(2013, 2, 2, 12, 30, 45, 999999) self.expected = datetime(2013, 2, 2, 12, 30, 45, 999999, tzinfo=tz.tzutc()) assert result._datetime == self.expected result = arrow.Arrow( 2013, 2, 2, 12, 30, 45, 999999, tzinfo=tz.gettz("Europe/Paris") ) self.expected = datetime( 2013, 2, 2, 12, 30, 45, 999999, tzinfo=tz.gettz("Europe/Paris") ) assert result._datetime == self.expected # regression tests for issue #626 def test_init_pytz_timezone(self): result = arrow.Arrow( 2013, 2, 2, 12, 30, 45, 999999, tzinfo=pytz.timezone("Europe/Paris") ) self.expected = datetime( 2013, 2, 2, 12, 30, 45, 999999, tzinfo=tz.gettz("Europe/Paris") ) assert result._datetime == self.expected assert_datetime_equality(result._datetime, self.expected, 1) def test_init_with_fold(self): before = arrow.Arrow(2017, 10, 29, 2, 0, tzinfo="Europe/Stockholm") after = arrow.Arrow(2017, 10, 29, 2, 0, tzinfo="Europe/Stockholm", fold=1) assert hasattr(before, "fold") assert hasattr(after, "fold") # PEP-495 requires the comparisons below to be true assert before == after assert before.utcoffset() != after.utcoffset() class TestTestArrowFactory: def test_now(self): result = arrow.Arrow.now() assert_datetime_equality( result._datetime, datetime.now().replace(tzinfo=tz.tzlocal()) ) def test_utcnow(self): result = arrow.Arrow.utcnow() assert_datetime_equality( result._datetime, datetime.utcnow().replace(tzinfo=tz.tzutc()) ) assert result.fold == 0 def test_fromtimestamp(self): timestamp = time.time() result = arrow.Arrow.fromtimestamp(timestamp) assert_datetime_equality( result._datetime, datetime.now().replace(tzinfo=tz.tzlocal()) ) result = arrow.Arrow.fromtimestamp(timestamp, tzinfo=tz.gettz("Europe/Paris")) assert_datetime_equality( result._datetime, datetime.fromtimestamp(timestamp, tz.gettz("Europe/Paris")), ) result = arrow.Arrow.fromtimestamp(timestamp, tzinfo="Europe/Paris") assert_datetime_equality( result._datetime, datetime.fromtimestamp(timestamp, tz.gettz("Europe/Paris")), ) with pytest.raises(ValueError): arrow.Arrow.fromtimestamp("invalid timestamp") def test_utcfromtimestamp(self): timestamp = time.time() result = arrow.Arrow.utcfromtimestamp(timestamp) assert_datetime_equality( result._datetime, datetime.utcnow().replace(tzinfo=tz.tzutc()) ) with pytest.raises(ValueError): arrow.Arrow.utcfromtimestamp("invalid timestamp") def test_fromdatetime(self): dt = datetime(2013, 2, 3, 12, 30, 45, 1) result = arrow.Arrow.fromdatetime(dt) assert result._datetime == dt.replace(tzinfo=tz.tzutc()) def test_fromdatetime_dt_tzinfo(self): dt = datetime(2013, 2, 3, 12, 30, 45, 1, tzinfo=tz.gettz("US/Pacific")) result = arrow.Arrow.fromdatetime(dt) assert result._datetime == dt.replace(tzinfo=tz.gettz("US/Pacific")) def test_fromdatetime_tzinfo_arg(self): dt = datetime(2013, 2, 3, 12, 30, 45, 1) result = arrow.Arrow.fromdatetime(dt, tz.gettz("US/Pacific")) assert result._datetime == dt.replace(tzinfo=tz.gettz("US/Pacific")) def test_fromdate(self): dt = date(2013, 2, 3) result = arrow.Arrow.fromdate(dt, tz.gettz("US/Pacific")) assert result._datetime == datetime(2013, 2, 3, tzinfo=tz.gettz("US/Pacific")) def test_strptime(self): formatted = datetime(2013, 2, 3, 12, 30, 45).strftime("%Y-%m-%d %H:%M:%S") result = arrow.Arrow.strptime(formatted, "%Y-%m-%d %H:%M:%S") assert result._datetime == datetime(2013, 2, 3, 12, 30, 45, tzinfo=tz.tzutc()) result = arrow.Arrow.strptime( formatted, "%Y-%m-%d %H:%M:%S", tzinfo=tz.gettz("Europe/Paris") ) assert result._datetime == datetime( 2013, 2, 3, 12, 30, 45, tzinfo=tz.gettz("Europe/Paris") ) def test_fromordinal(self): timestamp = 1607066909.937968 with pytest.raises(TypeError): arrow.Arrow.fromordinal(timestamp) with pytest.raises(ValueError): arrow.Arrow.fromordinal(int(timestamp)) ordinal = arrow.Arrow.utcnow().toordinal() with pytest.raises(TypeError): arrow.Arrow.fromordinal(str(ordinal)) result = arrow.Arrow.fromordinal(ordinal) dt = datetime.fromordinal(ordinal) assert result.naive == dt @pytest.mark.usefixtures("time_2013_02_03") class TestTestArrowRepresentation: def test_repr(self): result = self.arrow.__repr__() assert result == f"" def test_str(self): result = self.arrow.__str__() assert result == self.arrow._datetime.isoformat() def test_hash(self): result = self.arrow.__hash__() assert result == self.arrow._datetime.__hash__() def test_format(self): result = f"{self.arrow:YYYY-MM-DD}" assert result == "2013-02-03" def test_bare_format(self): result = self.arrow.format() assert result == "2013-02-03 12:30:45+00:00" def test_format_no_format_string(self): result = f"{self.arrow}" assert result == str(self.arrow) def test_clone(self): result = self.arrow.clone() assert result is not self.arrow assert result._datetime == self.arrow._datetime @pytest.mark.usefixtures("time_2013_01_01") class TestArrowAttribute: def test_getattr_base(self): with pytest.raises(AttributeError): self.arrow.prop def test_getattr_week(self): assert self.arrow.week == 1 def test_getattr_quarter(self): # start dates q1 = arrow.Arrow(2013, 1, 1) q2 = arrow.Arrow(2013, 4, 1) q3 = arrow.Arrow(2013, 8, 1) q4 = arrow.Arrow(2013, 10, 1) assert q1.quarter == 1 assert q2.quarter == 2 assert q3.quarter == 3 assert q4.quarter == 4 # end dates q1 = arrow.Arrow(2013, 3, 31) q2 = arrow.Arrow(2013, 6, 30) q3 = arrow.Arrow(2013, 9, 30) q4 = arrow.Arrow(2013, 12, 31) assert q1.quarter == 1 assert q2.quarter == 2 assert q3.quarter == 3 assert q4.quarter == 4 def test_getattr_dt_value(self): assert self.arrow.year == 2013 def test_tzinfo(self): assert self.arrow.tzinfo == tz.tzutc() def test_naive(self): assert self.arrow.naive == self.arrow._datetime.replace(tzinfo=None) def test_timestamp(self): assert self.arrow.timestamp() == self.arrow._datetime.timestamp() def test_int_timestamp(self): assert self.arrow.int_timestamp == int(self.arrow._datetime.timestamp()) def test_float_timestamp(self): assert self.arrow.float_timestamp == self.arrow._datetime.timestamp() def test_getattr_fold(self): # UTC is always unambiguous assert self.now.fold == 0 ambiguous_dt = arrow.Arrow( 2017, 10, 29, 2, 0, tzinfo="Europe/Stockholm", fold=1 ) assert ambiguous_dt.fold == 1 with pytest.raises(AttributeError): ambiguous_dt.fold = 0 def test_getattr_ambiguous(self): assert not self.now.ambiguous ambiguous_dt = arrow.Arrow(2017, 10, 29, 2, 0, tzinfo="Europe/Stockholm") assert ambiguous_dt.ambiguous def test_getattr_imaginary(self): assert not self.now.imaginary imaginary_dt = arrow.Arrow(2013, 3, 31, 2, 30, tzinfo="Europe/Paris") assert imaginary_dt.imaginary @pytest.mark.usefixtures("time_utcnow") class TestArrowComparison: def test_eq(self): assert self.arrow == self.arrow assert self.arrow == self.arrow.datetime assert not (self.arrow == "abc") def test_ne(self): assert not (self.arrow != self.arrow) assert not (self.arrow != self.arrow.datetime) assert self.arrow != "abc" def test_gt(self): arrow_cmp = self.arrow.shift(minutes=1) assert not (self.arrow > self.arrow) assert not (self.arrow > self.arrow.datetime) with pytest.raises(TypeError): self.arrow > "abc" # noqa: B015 assert self.arrow < arrow_cmp assert self.arrow < arrow_cmp.datetime def test_ge(self): with pytest.raises(TypeError): self.arrow >= "abc" # noqa: B015 assert self.arrow >= self.arrow assert self.arrow >= self.arrow.datetime def test_lt(self): arrow_cmp = self.arrow.shift(minutes=1) assert not (self.arrow < self.arrow) assert not (self.arrow < self.arrow.datetime) with pytest.raises(TypeError): self.arrow < "abc" # noqa: B015 assert self.arrow < arrow_cmp assert self.arrow < arrow_cmp.datetime def test_le(self): with pytest.raises(TypeError): self.arrow <= "abc" # noqa: B015 assert self.arrow <= self.arrow assert self.arrow <= self.arrow.datetime @pytest.mark.usefixtures("time_2013_01_01") class TestArrowMath: def test_add_timedelta(self): result = self.arrow.__add__(timedelta(days=1)) assert result._datetime == datetime(2013, 1, 2, tzinfo=tz.tzutc()) def test_add_other(self): with pytest.raises(TypeError): self.arrow + 1 def test_radd(self): result = self.arrow.__radd__(timedelta(days=1)) assert result._datetime == datetime(2013, 1, 2, tzinfo=tz.tzutc()) def test_sub_timedelta(self): result = self.arrow.__sub__(timedelta(days=1)) assert result._datetime == datetime(2012, 12, 31, tzinfo=tz.tzutc()) def test_sub_datetime(self): result = self.arrow.__sub__(datetime(2012, 12, 21, tzinfo=tz.tzutc())) assert result == timedelta(days=11) def test_sub_arrow(self): result = self.arrow.__sub__(arrow.Arrow(2012, 12, 21, tzinfo=tz.tzutc())) assert result == timedelta(days=11) def test_sub_other(self): with pytest.raises(TypeError): self.arrow - object() def test_rsub_datetime(self): result = self.arrow.__rsub__(datetime(2012, 12, 21, tzinfo=tz.tzutc())) assert result == timedelta(days=-11) def test_rsub_other(self): with pytest.raises(TypeError): timedelta(days=1) - self.arrow @pytest.mark.usefixtures("time_utcnow") class TestArrowDatetimeInterface: def test_date(self): result = self.arrow.date() assert result == self.arrow._datetime.date() def test_time(self): result = self.arrow.time() assert result == self.arrow._datetime.time() def test_timetz(self): result = self.arrow.timetz() assert result == self.arrow._datetime.timetz() def test_astimezone(self): other_tz = tz.gettz("US/Pacific") result = self.arrow.astimezone(other_tz) assert result == self.arrow._datetime.astimezone(other_tz) def test_utcoffset(self): result = self.arrow.utcoffset() assert result == self.arrow._datetime.utcoffset() def test_dst(self): result = self.arrow.dst() assert result == self.arrow._datetime.dst() def test_timetuple(self): result = self.arrow.timetuple() assert result == self.arrow._datetime.timetuple() def test_utctimetuple(self): result = self.arrow.utctimetuple() assert result == self.arrow._datetime.utctimetuple() def test_toordinal(self): result = self.arrow.toordinal() assert result == self.arrow._datetime.toordinal() def test_weekday(self): result = self.arrow.weekday() assert result == self.arrow._datetime.weekday() def test_isoweekday(self): result = self.arrow.isoweekday() assert result == self.arrow._datetime.isoweekday() def test_isocalendar(self): result = self.arrow.isocalendar() assert result == self.arrow._datetime.isocalendar() def test_isoformat(self): result = self.arrow.isoformat() assert result == self.arrow._datetime.isoformat() def test_isoformat_timespec(self): result = self.arrow.isoformat(timespec="hours") assert result == self.arrow._datetime.isoformat(timespec="hours") result = self.arrow.isoformat(timespec="microseconds") assert result == self.arrow._datetime.isoformat() result = self.arrow.isoformat(timespec="milliseconds") assert result == self.arrow._datetime.isoformat(timespec="milliseconds") result = self.arrow.isoformat(sep="x", timespec="seconds") assert result == self.arrow._datetime.isoformat(sep="x", timespec="seconds") def test_simplejson(self): result = json.dumps({"v": self.arrow.for_json()}, for_json=True) assert json.loads(result)["v"] == self.arrow._datetime.isoformat() def test_ctime(self): result = self.arrow.ctime() assert result == self.arrow._datetime.ctime() def test_strftime(self): result = self.arrow.strftime("%Y") assert result == self.arrow._datetime.strftime("%Y") class TestArrowFalsePositiveDst: """These tests relate to issues #376 and #551. The key points in both issues are that arrow will assign a UTC timezone if none is provided and .to() will change other attributes to be correct whereas .replace() only changes the specified attribute. Issue 376 >>> arrow.get('2016-11-06').to('America/New_York').ceil('day') < Arrow [2016-11-05T23:59:59.999999-04:00] > Issue 551 >>> just_before = arrow.get('2018-11-04T01:59:59.999999') >>> just_before 2018-11-04T01:59:59.999999+00:00 >>> just_after = just_before.shift(microseconds=1) >>> just_after 2018-11-04T02:00:00+00:00 >>> just_before_eastern = just_before.replace(tzinfo='US/Eastern') >>> just_before_eastern 2018-11-04T01:59:59.999999-04:00 >>> just_after_eastern = just_after.replace(tzinfo='US/Eastern') >>> just_after_eastern 2018-11-04T02:00:00-05:00 """ def test_dst(self): self.before_1 = arrow.Arrow( 2016, 11, 6, 3, 59, tzinfo=tz.gettz("America/New_York") ) self.before_2 = arrow.Arrow(2016, 11, 6, tzinfo=tz.gettz("America/New_York")) self.after_1 = arrow.Arrow(2016, 11, 6, 4, tzinfo=tz.gettz("America/New_York")) self.after_2 = arrow.Arrow( 2016, 11, 6, 23, 59, tzinfo=tz.gettz("America/New_York") ) self.before_3 = arrow.Arrow( 2018, 11, 4, 3, 59, tzinfo=tz.gettz("America/New_York") ) self.before_4 = arrow.Arrow(2018, 11, 4, tzinfo=tz.gettz("America/New_York")) self.after_3 = arrow.Arrow(2018, 11, 4, 4, tzinfo=tz.gettz("America/New_York")) self.after_4 = arrow.Arrow( 2018, 11, 4, 23, 59, tzinfo=tz.gettz("America/New_York") ) assert self.before_1.day == self.before_2.day assert self.after_1.day == self.after_2.day assert self.before_3.day == self.before_4.day assert self.after_3.day == self.after_4.day class TestArrowConversion: def test_to(self): dt_from = datetime.now() arrow_from = arrow.Arrow.fromdatetime(dt_from, tz.gettz("US/Pacific")) self.expected = dt_from.replace(tzinfo=tz.gettz("US/Pacific")).astimezone( tz.tzutc() ) assert arrow_from.to("UTC").datetime == self.expected assert arrow_from.to(tz.tzutc()).datetime == self.expected # issue #368 def test_to_pacific_then_utc(self): result = arrow.Arrow(2018, 11, 4, 1, tzinfo="-08:00").to("US/Pacific").to("UTC") assert result == arrow.Arrow(2018, 11, 4, 9) # issue #368 def test_to_amsterdam_then_utc(self): result = arrow.Arrow(2016, 10, 30).to("Europe/Amsterdam") assert result.utcoffset() == timedelta(seconds=7200) # regression test for #690 def test_to_israel_same_offset(self): result = arrow.Arrow(2019, 10, 27, 2, 21, 1, tzinfo="+03:00").to("Israel") expected = arrow.Arrow(2019, 10, 27, 1, 21, 1, tzinfo="Israel") assert result == expected assert result.utcoffset() != expected.utcoffset() # issue 315 def test_anchorage_dst(self): before = arrow.Arrow(2016, 3, 13, 1, 59, tzinfo="America/Anchorage") after = arrow.Arrow(2016, 3, 13, 2, 1, tzinfo="America/Anchorage") assert before.utcoffset() != after.utcoffset() # issue 476 def test_chicago_fall(self): result = arrow.Arrow(2017, 11, 5, 2, 1, tzinfo="-05:00").to("America/Chicago") expected = arrow.Arrow(2017, 11, 5, 1, 1, tzinfo="America/Chicago") assert result == expected assert result.utcoffset() != expected.utcoffset() def test_toronto_gap(self): before = arrow.Arrow(2011, 3, 13, 6, 30, tzinfo="UTC").to("America/Toronto") after = arrow.Arrow(2011, 3, 13, 7, 30, tzinfo="UTC").to("America/Toronto") assert before.datetime.replace(tzinfo=None) == datetime(2011, 3, 13, 1, 30) assert after.datetime.replace(tzinfo=None) == datetime(2011, 3, 13, 3, 30) assert before.utcoffset() != after.utcoffset() def test_sydney_gap(self): before = arrow.Arrow(2012, 10, 6, 15, 30, tzinfo="UTC").to("Australia/Sydney") after = arrow.Arrow(2012, 10, 6, 16, 30, tzinfo="UTC").to("Australia/Sydney") assert before.datetime.replace(tzinfo=None) == datetime(2012, 10, 7, 1, 30) assert after.datetime.replace(tzinfo=None) == datetime(2012, 10, 7, 3, 30) assert before.utcoffset() != after.utcoffset() class TestArrowPickling: def test_pickle_and_unpickle(self): dt = arrow.Arrow.utcnow() pickled = pickle.dumps(dt) unpickled = pickle.loads(pickled) assert unpickled == dt class TestArrowReplace: def test_not_attr(self): with pytest.raises(ValueError): arrow.Arrow.utcnow().replace(abc=1) def test_replace(self): arw = arrow.Arrow(2013, 5, 5, 12, 30, 45) assert arw.replace(year=2012) == arrow.Arrow(2012, 5, 5, 12, 30, 45) assert arw.replace(month=1) == arrow.Arrow(2013, 1, 5, 12, 30, 45) assert arw.replace(day=1) == arrow.Arrow(2013, 5, 1, 12, 30, 45) assert arw.replace(hour=1) == arrow.Arrow(2013, 5, 5, 1, 30, 45) assert arw.replace(minute=1) == arrow.Arrow(2013, 5, 5, 12, 1, 45) assert arw.replace(second=1) == arrow.Arrow(2013, 5, 5, 12, 30, 1) def test_replace_tzinfo(self): arw = arrow.Arrow.utcnow().to("US/Eastern") result = arw.replace(tzinfo=tz.gettz("US/Pacific")) assert result == arw.datetime.replace(tzinfo=tz.gettz("US/Pacific")) def test_replace_fold(self): before = arrow.Arrow(2017, 11, 5, 1, tzinfo="America/New_York") after = before.replace(fold=1) assert before.fold == 0 assert after.fold == 1 assert before == after assert before.utcoffset() != after.utcoffset() def test_replace_fold_and_other(self): arw = arrow.Arrow(2013, 5, 5, 12, 30, 45) assert arw.replace(fold=1, minute=50) == arrow.Arrow(2013, 5, 5, 12, 50, 45) assert arw.replace(minute=50, fold=1) == arrow.Arrow(2013, 5, 5, 12, 50, 45) def test_replace_week(self): with pytest.raises(ValueError): arrow.Arrow.utcnow().replace(week=1) def test_replace_quarter(self): with pytest.raises(ValueError): arrow.Arrow.utcnow().replace(quarter=1) def test_replace_quarter_and_fold(self): with pytest.raises(AttributeError): arrow.utcnow().replace(fold=1, quarter=1) with pytest.raises(AttributeError): arrow.utcnow().replace(quarter=1, fold=1) def test_replace_other_kwargs(self): with pytest.raises(AttributeError): arrow.utcnow().replace(abc="def") class TestArrowShift: def test_not_attr(self): now = arrow.Arrow.utcnow() with pytest.raises(ValueError): now.shift(abc=1) with pytest.raises(ValueError): now.shift(week=1) def test_shift(self): arw = arrow.Arrow(2013, 5, 5, 12, 30, 45) assert arw.shift(years=1) == arrow.Arrow(2014, 5, 5, 12, 30, 45) assert arw.shift(quarters=1) == arrow.Arrow(2013, 8, 5, 12, 30, 45) assert arw.shift(quarters=1, months=1) == arrow.Arrow(2013, 9, 5, 12, 30, 45) assert arw.shift(months=1) == arrow.Arrow(2013, 6, 5, 12, 30, 45) assert arw.shift(weeks=1) == arrow.Arrow(2013, 5, 12, 12, 30, 45) assert arw.shift(days=1) == arrow.Arrow(2013, 5, 6, 12, 30, 45) assert arw.shift(hours=1) == arrow.Arrow(2013, 5, 5, 13, 30, 45) assert arw.shift(minutes=1) == arrow.Arrow(2013, 5, 5, 12, 31, 45) assert arw.shift(seconds=1) == arrow.Arrow(2013, 5, 5, 12, 30, 46) assert arw.shift(microseconds=1) == arrow.Arrow(2013, 5, 5, 12, 30, 45, 1) # Remember: Python's weekday 0 is Monday assert arw.shift(weekday=0) == arrow.Arrow(2013, 5, 6, 12, 30, 45) assert arw.shift(weekday=1) == arrow.Arrow(2013, 5, 7, 12, 30, 45) assert arw.shift(weekday=2) == arrow.Arrow(2013, 5, 8, 12, 30, 45) assert arw.shift(weekday=3) == arrow.Arrow(2013, 5, 9, 12, 30, 45) assert arw.shift(weekday=4) == arrow.Arrow(2013, 5, 10, 12, 30, 45) assert arw.shift(weekday=5) == arrow.Arrow(2013, 5, 11, 12, 30, 45) assert arw.shift(weekday=6) == arw with pytest.raises(IndexError): arw.shift(weekday=7) # Use dateutil.relativedelta's convenient day instances assert arw.shift(weekday=MO) == arrow.Arrow(2013, 5, 6, 12, 30, 45) assert arw.shift(weekday=MO(0)) == arrow.Arrow(2013, 5, 6, 12, 30, 45) assert arw.shift(weekday=MO(1)) == arrow.Arrow(2013, 5, 6, 12, 30, 45) assert arw.shift(weekday=MO(2)) == arrow.Arrow(2013, 5, 13, 12, 30, 45) assert arw.shift(weekday=TU) == arrow.Arrow(2013, 5, 7, 12, 30, 45) assert arw.shift(weekday=TU(0)) == arrow.Arrow(2013, 5, 7, 12, 30, 45) assert arw.shift(weekday=TU(1)) == arrow.Arrow(2013, 5, 7, 12, 30, 45) assert arw.shift(weekday=TU(2)) == arrow.Arrow(2013, 5, 14, 12, 30, 45) assert arw.shift(weekday=WE) == arrow.Arrow(2013, 5, 8, 12, 30, 45) assert arw.shift(weekday=WE(0)) == arrow.Arrow(2013, 5, 8, 12, 30, 45) assert arw.shift(weekday=WE(1)) == arrow.Arrow(2013, 5, 8, 12, 30, 45) assert arw.shift(weekday=WE(2)) == arrow.Arrow(2013, 5, 15, 12, 30, 45) assert arw.shift(weekday=TH) == arrow.Arrow(2013, 5, 9, 12, 30, 45) assert arw.shift(weekday=TH(0)) == arrow.Arrow(2013, 5, 9, 12, 30, 45) assert arw.shift(weekday=TH(1)) == arrow.Arrow(2013, 5, 9, 12, 30, 45) assert arw.shift(weekday=TH(2)) == arrow.Arrow(2013, 5, 16, 12, 30, 45) assert arw.shift(weekday=FR) == arrow.Arrow(2013, 5, 10, 12, 30, 45) assert arw.shift(weekday=FR(0)) == arrow.Arrow(2013, 5, 10, 12, 30, 45) assert arw.shift(weekday=FR(1)) == arrow.Arrow(2013, 5, 10, 12, 30, 45) assert arw.shift(weekday=FR(2)) == arrow.Arrow(2013, 5, 17, 12, 30, 45) assert arw.shift(weekday=SA) == arrow.Arrow(2013, 5, 11, 12, 30, 45) assert arw.shift(weekday=SA(0)) == arrow.Arrow(2013, 5, 11, 12, 30, 45) assert arw.shift(weekday=SA(1)) == arrow.Arrow(2013, 5, 11, 12, 30, 45) assert arw.shift(weekday=SA(2)) == arrow.Arrow(2013, 5, 18, 12, 30, 45) assert arw.shift(weekday=SU) == arw assert arw.shift(weekday=SU(0)) == arw assert arw.shift(weekday=SU(1)) == arw assert arw.shift(weekday=SU(2)) == arrow.Arrow(2013, 5, 12, 12, 30, 45) def test_shift_negative(self): arw = arrow.Arrow(2013, 5, 5, 12, 30, 45) assert arw.shift(years=-1) == arrow.Arrow(2012, 5, 5, 12, 30, 45) assert arw.shift(quarters=-1) == arrow.Arrow(2013, 2, 5, 12, 30, 45) assert arw.shift(quarters=-1, months=-1) == arrow.Arrow(2013, 1, 5, 12, 30, 45) assert arw.shift(months=-1) == arrow.Arrow(2013, 4, 5, 12, 30, 45) assert arw.shift(weeks=-1) == arrow.Arrow(2013, 4, 28, 12, 30, 45) assert arw.shift(days=-1) == arrow.Arrow(2013, 5, 4, 12, 30, 45) assert arw.shift(hours=-1) == arrow.Arrow(2013, 5, 5, 11, 30, 45) assert arw.shift(minutes=-1) == arrow.Arrow(2013, 5, 5, 12, 29, 45) assert arw.shift(seconds=-1) == arrow.Arrow(2013, 5, 5, 12, 30, 44) assert arw.shift(microseconds=-1) == arrow.Arrow(2013, 5, 5, 12, 30, 44, 999999) # Not sure how practical these negative weekdays are assert arw.shift(weekday=-1) == arw.shift(weekday=SU) assert arw.shift(weekday=-2) == arw.shift(weekday=SA) assert arw.shift(weekday=-3) == arw.shift(weekday=FR) assert arw.shift(weekday=-4) == arw.shift(weekday=TH) assert arw.shift(weekday=-5) == arw.shift(weekday=WE) assert arw.shift(weekday=-6) == arw.shift(weekday=TU) assert arw.shift(weekday=-7) == arw.shift(weekday=MO) with pytest.raises(IndexError): arw.shift(weekday=-8) assert arw.shift(weekday=MO(-1)) == arrow.Arrow(2013, 4, 29, 12, 30, 45) assert arw.shift(weekday=TU(-1)) == arrow.Arrow(2013, 4, 30, 12, 30, 45) assert arw.shift(weekday=WE(-1)) == arrow.Arrow(2013, 5, 1, 12, 30, 45) assert arw.shift(weekday=TH(-1)) == arrow.Arrow(2013, 5, 2, 12, 30, 45) assert arw.shift(weekday=FR(-1)) == arrow.Arrow(2013, 5, 3, 12, 30, 45) assert arw.shift(weekday=SA(-1)) == arrow.Arrow(2013, 5, 4, 12, 30, 45) assert arw.shift(weekday=SU(-1)) == arw assert arw.shift(weekday=SU(-2)) == arrow.Arrow(2013, 4, 28, 12, 30, 45) def test_shift_quarters_bug(self): arw = arrow.Arrow(2013, 5, 5, 12, 30, 45) # The value of the last-read argument was used instead of the ``quarters`` argument. # Recall that the keyword argument dict, like all dicts, is unordered, so only certain # combinations of arguments would exhibit this. assert arw.shift(quarters=0, years=1) == arrow.Arrow(2014, 5, 5, 12, 30, 45) assert arw.shift(quarters=0, months=1) == arrow.Arrow(2013, 6, 5, 12, 30, 45) assert arw.shift(quarters=0, weeks=1) == arrow.Arrow(2013, 5, 12, 12, 30, 45) assert arw.shift(quarters=0, days=1) == arrow.Arrow(2013, 5, 6, 12, 30, 45) assert arw.shift(quarters=0, hours=1) == arrow.Arrow(2013, 5, 5, 13, 30, 45) assert arw.shift(quarters=0, minutes=1) == arrow.Arrow(2013, 5, 5, 12, 31, 45) assert arw.shift(quarters=0, seconds=1) == arrow.Arrow(2013, 5, 5, 12, 30, 46) assert arw.shift(quarters=0, microseconds=1) == arrow.Arrow( 2013, 5, 5, 12, 30, 45, 1 ) def test_shift_positive_imaginary(self): # Avoid shifting into imaginary datetimes, take into account DST and other timezone changes. new_york = arrow.Arrow(2017, 3, 12, 1, 30, tzinfo="America/New_York") assert new_york.shift(hours=+1) == arrow.Arrow( 2017, 3, 12, 3, 30, tzinfo="America/New_York" ) # pendulum example paris = arrow.Arrow(2013, 3, 31, 1, 50, tzinfo="Europe/Paris") assert paris.shift(minutes=+20) == arrow.Arrow( 2013, 3, 31, 3, 10, tzinfo="Europe/Paris" ) canberra = arrow.Arrow(2018, 10, 7, 1, 30, tzinfo="Australia/Canberra") assert canberra.shift(hours=+1) == arrow.Arrow( 2018, 10, 7, 3, 30, tzinfo="Australia/Canberra" ) kiev = arrow.Arrow(2018, 3, 25, 2, 30, tzinfo="Europe/Kiev") assert kiev.shift(hours=+1) == arrow.Arrow( 2018, 3, 25, 4, 30, tzinfo="Europe/Kiev" ) # Edge case, the entire day of 2011-12-30 is imaginary in this zone! apia = arrow.Arrow(2011, 12, 29, 23, tzinfo="Pacific/Apia") assert apia.shift(hours=+2) == arrow.Arrow( 2011, 12, 31, 1, tzinfo="Pacific/Apia" ) def test_shift_negative_imaginary(self): new_york = arrow.Arrow(2011, 3, 13, 3, 30, tzinfo="America/New_York") assert new_york.shift(hours=-1) == arrow.Arrow( 2011, 3, 13, 3, 30, tzinfo="America/New_York" ) assert new_york.shift(hours=-2) == arrow.Arrow( 2011, 3, 13, 1, 30, tzinfo="America/New_York" ) london = arrow.Arrow(2019, 3, 31, 2, tzinfo="Europe/London") assert london.shift(hours=-1) == arrow.Arrow( 2019, 3, 31, 2, tzinfo="Europe/London" ) assert london.shift(hours=-2) == arrow.Arrow( 2019, 3, 31, 0, tzinfo="Europe/London" ) # edge case, crossing the international dateline apia = arrow.Arrow(2011, 12, 31, 1, tzinfo="Pacific/Apia") assert apia.shift(hours=-2) == arrow.Arrow( 2011, 12, 31, 23, tzinfo="Pacific/Apia" ) @pytest.mark.skipif( dateutil.__version__ < "2.7.1", reason="old tz database (2018d needed)" ) def test_shift_kiritimati(self): # corrected 2018d tz database release, will fail in earlier versions kiritimati = arrow.Arrow(1994, 12, 30, 12, 30, tzinfo="Pacific/Kiritimati") assert kiritimati.shift(days=+1) == arrow.Arrow( 1995, 1, 1, 12, 30, tzinfo="Pacific/Kiritimati" ) def shift_imaginary_seconds(self): # offset has a seconds component monrovia = arrow.Arrow(1972, 1, 6, 23, tzinfo="Africa/Monrovia") assert monrovia.shift(hours=+1, minutes=+30) == arrow.Arrow( 1972, 1, 7, 1, 14, 30, tzinfo="Africa/Monrovia" ) class TestArrowRange: def test_year(self): result = list( arrow.Arrow.range( "year", datetime(2013, 1, 2, 3, 4, 5), datetime(2016, 4, 5, 6, 7, 8) ) ) assert result == [ arrow.Arrow(2013, 1, 2, 3, 4, 5), arrow.Arrow(2014, 1, 2, 3, 4, 5), arrow.Arrow(2015, 1, 2, 3, 4, 5), arrow.Arrow(2016, 1, 2, 3, 4, 5), ] def test_quarter(self): result = list( arrow.Arrow.range( "quarter", datetime(2013, 2, 3, 4, 5, 6), datetime(2013, 5, 6, 7, 8, 9) ) ) assert result == [ arrow.Arrow(2013, 2, 3, 4, 5, 6), arrow.Arrow(2013, 5, 3, 4, 5, 6), ] def test_month(self): result = list( arrow.Arrow.range( "month", datetime(2013, 2, 3, 4, 5, 6), datetime(2013, 5, 6, 7, 8, 9) ) ) assert result == [ arrow.Arrow(2013, 2, 3, 4, 5, 6), arrow.Arrow(2013, 3, 3, 4, 5, 6), arrow.Arrow(2013, 4, 3, 4, 5, 6), arrow.Arrow(2013, 5, 3, 4, 5, 6), ] def test_week(self): result = list( arrow.Arrow.range( "week", datetime(2013, 9, 1, 2, 3, 4), datetime(2013, 10, 1, 2, 3, 4) ) ) assert result == [ arrow.Arrow(2013, 9, 1, 2, 3, 4), arrow.Arrow(2013, 9, 8, 2, 3, 4), arrow.Arrow(2013, 9, 15, 2, 3, 4), arrow.Arrow(2013, 9, 22, 2, 3, 4), arrow.Arrow(2013, 9, 29, 2, 3, 4), ] def test_day(self): result = list( arrow.Arrow.range( "day", datetime(2013, 1, 2, 3, 4, 5), datetime(2013, 1, 5, 6, 7, 8) ) ) assert result == [ arrow.Arrow(2013, 1, 2, 3, 4, 5), arrow.Arrow(2013, 1, 3, 3, 4, 5), arrow.Arrow(2013, 1, 4, 3, 4, 5), arrow.Arrow(2013, 1, 5, 3, 4, 5), ] def test_hour(self): result = list( arrow.Arrow.range( "hour", datetime(2013, 1, 2, 3, 4, 5), datetime(2013, 1, 2, 6, 7, 8) ) ) assert result == [ arrow.Arrow(2013, 1, 2, 3, 4, 5), arrow.Arrow(2013, 1, 2, 4, 4, 5), arrow.Arrow(2013, 1, 2, 5, 4, 5), arrow.Arrow(2013, 1, 2, 6, 4, 5), ] result = list( arrow.Arrow.range( "hour", datetime(2013, 1, 2, 3, 4, 5), datetime(2013, 1, 2, 3, 4, 5) ) ) assert result == [arrow.Arrow(2013, 1, 2, 3, 4, 5)] def test_minute(self): result = list( arrow.Arrow.range( "minute", datetime(2013, 1, 2, 3, 4, 5), datetime(2013, 1, 2, 3, 7, 8) ) ) assert result == [ arrow.Arrow(2013, 1, 2, 3, 4, 5), arrow.Arrow(2013, 1, 2, 3, 5, 5), arrow.Arrow(2013, 1, 2, 3, 6, 5), arrow.Arrow(2013, 1, 2, 3, 7, 5), ] def test_second(self): result = list( arrow.Arrow.range( "second", datetime(2013, 1, 2, 3, 4, 5), datetime(2013, 1, 2, 3, 4, 8) ) ) assert result == [ arrow.Arrow(2013, 1, 2, 3, 4, 5), arrow.Arrow(2013, 1, 2, 3, 4, 6), arrow.Arrow(2013, 1, 2, 3, 4, 7), arrow.Arrow(2013, 1, 2, 3, 4, 8), ] def test_arrow(self): result = list( arrow.Arrow.range( "day", arrow.Arrow(2013, 1, 2, 3, 4, 5), arrow.Arrow(2013, 1, 5, 6, 7, 8), ) ) assert result == [ arrow.Arrow(2013, 1, 2, 3, 4, 5), arrow.Arrow(2013, 1, 3, 3, 4, 5), arrow.Arrow(2013, 1, 4, 3, 4, 5), arrow.Arrow(2013, 1, 5, 3, 4, 5), ] def test_naive_tz(self): result = arrow.Arrow.range( "year", datetime(2013, 1, 2, 3), datetime(2016, 4, 5, 6), "US/Pacific" ) for r in result: assert r.tzinfo == tz.gettz("US/Pacific") def test_aware_same_tz(self): result = arrow.Arrow.range( "day", arrow.Arrow(2013, 1, 1, tzinfo=tz.gettz("US/Pacific")), arrow.Arrow(2013, 1, 3, tzinfo=tz.gettz("US/Pacific")), ) for r in result: assert r.tzinfo == tz.gettz("US/Pacific") def test_aware_different_tz(self): result = arrow.Arrow.range( "day", datetime(2013, 1, 1, tzinfo=tz.gettz("US/Eastern")), datetime(2013, 1, 3, tzinfo=tz.gettz("US/Pacific")), ) for r in result: assert r.tzinfo == tz.gettz("US/Eastern") def test_aware_tz(self): result = arrow.Arrow.range( "day", datetime(2013, 1, 1, tzinfo=tz.gettz("US/Eastern")), datetime(2013, 1, 3, tzinfo=tz.gettz("US/Pacific")), tz=tz.gettz("US/Central"), ) for r in result: assert r.tzinfo == tz.gettz("US/Central") def test_imaginary(self): # issue #72, avoid duplication in utc column before = arrow.Arrow(2018, 3, 10, 23, tzinfo="US/Pacific") after = arrow.Arrow(2018, 3, 11, 4, tzinfo="US/Pacific") pacific_range = [t for t in arrow.Arrow.range("hour", before, after)] utc_range = [t.to("utc") for t in arrow.Arrow.range("hour", before, after)] assert len(pacific_range) == len(set(pacific_range)) assert len(utc_range) == len(set(utc_range)) def test_unsupported(self): with pytest.raises(ValueError): next(arrow.Arrow.range("abc", datetime.utcnow(), datetime.utcnow())) def test_range_over_months_ending_on_different_days(self): # regression test for issue #842 result = list(arrow.Arrow.range("month", datetime(2015, 1, 31), limit=4)) assert result == [ arrow.Arrow(2015, 1, 31), arrow.Arrow(2015, 2, 28), arrow.Arrow(2015, 3, 31), arrow.Arrow(2015, 4, 30), ] result = list(arrow.Arrow.range("month", datetime(2015, 1, 30), limit=3)) assert result == [ arrow.Arrow(2015, 1, 30), arrow.Arrow(2015, 2, 28), arrow.Arrow(2015, 3, 30), ] result = list(arrow.Arrow.range("month", datetime(2015, 2, 28), limit=3)) assert result == [ arrow.Arrow(2015, 2, 28), arrow.Arrow(2015, 3, 28), arrow.Arrow(2015, 4, 28), ] result = list(arrow.Arrow.range("month", datetime(2015, 3, 31), limit=3)) assert result == [ arrow.Arrow(2015, 3, 31), arrow.Arrow(2015, 4, 30), arrow.Arrow(2015, 5, 31), ] def test_range_over_quarter_months_ending_on_different_days(self): result = list(arrow.Arrow.range("quarter", datetime(2014, 11, 30), limit=3)) assert result == [ arrow.Arrow(2014, 11, 30), arrow.Arrow(2015, 2, 28), arrow.Arrow(2015, 5, 30), ] def test_range_over_year_maintains_end_date_across_leap_year(self): result = list(arrow.Arrow.range("year", datetime(2012, 2, 29), limit=5)) assert result == [ arrow.Arrow(2012, 2, 29), arrow.Arrow(2013, 2, 28), arrow.Arrow(2014, 2, 28), arrow.Arrow(2015, 2, 28), arrow.Arrow(2016, 2, 29), ] class TestArrowSpanRange: def test_year(self): result = list( arrow.Arrow.span_range("year", datetime(2013, 2, 1), datetime(2016, 3, 31)) ) assert result == [ ( arrow.Arrow(2013, 1, 1), arrow.Arrow(2013, 12, 31, 23, 59, 59, 999999), ), ( arrow.Arrow(2014, 1, 1), arrow.Arrow(2014, 12, 31, 23, 59, 59, 999999), ), ( arrow.Arrow(2015, 1, 1), arrow.Arrow(2015, 12, 31, 23, 59, 59, 999999), ), ( arrow.Arrow(2016, 1, 1), arrow.Arrow(2016, 12, 31, 23, 59, 59, 999999), ), ] def test_quarter(self): result = list( arrow.Arrow.span_range( "quarter", datetime(2013, 2, 2), datetime(2013, 5, 15) ) ) assert result == [ (arrow.Arrow(2013, 1, 1), arrow.Arrow(2013, 3, 31, 23, 59, 59, 999999)), (arrow.Arrow(2013, 4, 1), arrow.Arrow(2013, 6, 30, 23, 59, 59, 999999)), ] def test_month(self): result = list( arrow.Arrow.span_range("month", datetime(2013, 1, 2), datetime(2013, 4, 15)) ) assert result == [ (arrow.Arrow(2013, 1, 1), arrow.Arrow(2013, 1, 31, 23, 59, 59, 999999)), (arrow.Arrow(2013, 2, 1), arrow.Arrow(2013, 2, 28, 23, 59, 59, 999999)), (arrow.Arrow(2013, 3, 1), arrow.Arrow(2013, 3, 31, 23, 59, 59, 999999)), (arrow.Arrow(2013, 4, 1), arrow.Arrow(2013, 4, 30, 23, 59, 59, 999999)), ] def test_week(self): result = list( arrow.Arrow.span_range("week", datetime(2013, 2, 2), datetime(2013, 2, 28)) ) assert result == [ (arrow.Arrow(2013, 1, 28), arrow.Arrow(2013, 2, 3, 23, 59, 59, 999999)), (arrow.Arrow(2013, 2, 4), arrow.Arrow(2013, 2, 10, 23, 59, 59, 999999)), ( arrow.Arrow(2013, 2, 11), arrow.Arrow(2013, 2, 17, 23, 59, 59, 999999), ), ( arrow.Arrow(2013, 2, 18), arrow.Arrow(2013, 2, 24, 23, 59, 59, 999999), ), (arrow.Arrow(2013, 2, 25), arrow.Arrow(2013, 3, 3, 23, 59, 59, 999999)), ] def test_day(self): result = list( arrow.Arrow.span_range( "day", datetime(2013, 1, 1, 12), datetime(2013, 1, 4, 12) ) ) assert result == [ ( arrow.Arrow(2013, 1, 1, 0), arrow.Arrow(2013, 1, 1, 23, 59, 59, 999999), ), ( arrow.Arrow(2013, 1, 2, 0), arrow.Arrow(2013, 1, 2, 23, 59, 59, 999999), ), ( arrow.Arrow(2013, 1, 3, 0), arrow.Arrow(2013, 1, 3, 23, 59, 59, 999999), ), ( arrow.Arrow(2013, 1, 4, 0), arrow.Arrow(2013, 1, 4, 23, 59, 59, 999999), ), ] def test_days(self): result = list( arrow.Arrow.span_range( "days", datetime(2013, 1, 1, 12), datetime(2013, 1, 4, 12) ) ) assert result == [ ( arrow.Arrow(2013, 1, 1, 0), arrow.Arrow(2013, 1, 1, 23, 59, 59, 999999), ), ( arrow.Arrow(2013, 1, 2, 0), arrow.Arrow(2013, 1, 2, 23, 59, 59, 999999), ), ( arrow.Arrow(2013, 1, 3, 0), arrow.Arrow(2013, 1, 3, 23, 59, 59, 999999), ), ( arrow.Arrow(2013, 1, 4, 0), arrow.Arrow(2013, 1, 4, 23, 59, 59, 999999), ), ] def test_hour(self): result = list( arrow.Arrow.span_range( "hour", datetime(2013, 1, 1, 0, 30), datetime(2013, 1, 1, 3, 30) ) ) assert result == [ ( arrow.Arrow(2013, 1, 1, 0), arrow.Arrow(2013, 1, 1, 0, 59, 59, 999999), ), ( arrow.Arrow(2013, 1, 1, 1), arrow.Arrow(2013, 1, 1, 1, 59, 59, 999999), ), ( arrow.Arrow(2013, 1, 1, 2), arrow.Arrow(2013, 1, 1, 2, 59, 59, 999999), ), ( arrow.Arrow(2013, 1, 1, 3), arrow.Arrow(2013, 1, 1, 3, 59, 59, 999999), ), ] result = list( arrow.Arrow.span_range( "hour", datetime(2013, 1, 1, 3, 30), datetime(2013, 1, 1, 3, 30) ) ) assert result == [ (arrow.Arrow(2013, 1, 1, 3), arrow.Arrow(2013, 1, 1, 3, 59, 59, 999999)) ] def test_minute(self): result = list( arrow.Arrow.span_range( "minute", datetime(2013, 1, 1, 0, 0, 30), datetime(2013, 1, 1, 0, 3, 30) ) ) assert result == [ ( arrow.Arrow(2013, 1, 1, 0, 0), arrow.Arrow(2013, 1, 1, 0, 0, 59, 999999), ), ( arrow.Arrow(2013, 1, 1, 0, 1), arrow.Arrow(2013, 1, 1, 0, 1, 59, 999999), ), ( arrow.Arrow(2013, 1, 1, 0, 2), arrow.Arrow(2013, 1, 1, 0, 2, 59, 999999), ), ( arrow.Arrow(2013, 1, 1, 0, 3), arrow.Arrow(2013, 1, 1, 0, 3, 59, 999999), ), ] def test_second(self): result = list( arrow.Arrow.span_range( "second", datetime(2013, 1, 1), datetime(2013, 1, 1, 0, 0, 3) ) ) assert result == [ ( arrow.Arrow(2013, 1, 1, 0, 0, 0), arrow.Arrow(2013, 1, 1, 0, 0, 0, 999999), ), ( arrow.Arrow(2013, 1, 1, 0, 0, 1), arrow.Arrow(2013, 1, 1, 0, 0, 1, 999999), ), ( arrow.Arrow(2013, 1, 1, 0, 0, 2), arrow.Arrow(2013, 1, 1, 0, 0, 2, 999999), ), ( arrow.Arrow(2013, 1, 1, 0, 0, 3), arrow.Arrow(2013, 1, 1, 0, 0, 3, 999999), ), ] def test_naive_tz(self): tzinfo = tz.gettz("US/Pacific") result = arrow.Arrow.span_range( "hour", datetime(2013, 1, 1, 0), datetime(2013, 1, 1, 3, 59), "US/Pacific" ) for f, c in result: assert f.tzinfo == tzinfo assert c.tzinfo == tzinfo def test_aware_same_tz(self): tzinfo = tz.gettz("US/Pacific") result = arrow.Arrow.span_range( "hour", datetime(2013, 1, 1, 0, tzinfo=tzinfo), datetime(2013, 1, 1, 2, 59, tzinfo=tzinfo), ) for f, c in result: assert f.tzinfo == tzinfo assert c.tzinfo == tzinfo def test_aware_different_tz(self): tzinfo1 = tz.gettz("US/Pacific") tzinfo2 = tz.gettz("US/Eastern") result = arrow.Arrow.span_range( "hour", datetime(2013, 1, 1, 0, tzinfo=tzinfo1), datetime(2013, 1, 1, 2, 59, tzinfo=tzinfo2), ) for f, c in result: assert f.tzinfo == tzinfo1 assert c.tzinfo == tzinfo1 def test_aware_tz(self): result = arrow.Arrow.span_range( "hour", datetime(2013, 1, 1, 0, tzinfo=tz.gettz("US/Eastern")), datetime(2013, 1, 1, 2, 59, tzinfo=tz.gettz("US/Eastern")), tz="US/Central", ) for f, c in result: assert f.tzinfo == tz.gettz("US/Central") assert c.tzinfo == tz.gettz("US/Central") def test_bounds_param_is_passed(self): result = list( arrow.Arrow.span_range( "quarter", datetime(2013, 2, 2), datetime(2013, 5, 15), bounds="[]" ) ) assert result == [ (arrow.Arrow(2013, 1, 1), arrow.Arrow(2013, 4, 1)), (arrow.Arrow(2013, 4, 1), arrow.Arrow(2013, 7, 1)), ] def test_exact_bound_exclude(self): result = list( arrow.Arrow.span_range( "hour", datetime(2013, 5, 5, 12, 30), datetime(2013, 5, 5, 17, 15), bounds="[)", exact=True, ) ) expected = [ ( arrow.Arrow(2013, 5, 5, 12, 30), arrow.Arrow(2013, 5, 5, 13, 29, 59, 999999), ), ( arrow.Arrow(2013, 5, 5, 13, 30), arrow.Arrow(2013, 5, 5, 14, 29, 59, 999999), ), ( arrow.Arrow(2013, 5, 5, 14, 30), arrow.Arrow(2013, 5, 5, 15, 29, 59, 999999), ), ( arrow.Arrow(2013, 5, 5, 15, 30), arrow.Arrow(2013, 5, 5, 16, 29, 59, 999999), ), ( arrow.Arrow(2013, 5, 5, 16, 30), arrow.Arrow(2013, 5, 5, 17, 14, 59, 999999), ), ] assert result == expected def test_exact_floor_equals_end(self): result = list( arrow.Arrow.span_range( "minute", datetime(2013, 5, 5, 12, 30), datetime(2013, 5, 5, 12, 40), exact=True, ) ) expected = [ ( arrow.Arrow(2013, 5, 5, 12, 30), arrow.Arrow(2013, 5, 5, 12, 30, 59, 999999), ), ( arrow.Arrow(2013, 5, 5, 12, 31), arrow.Arrow(2013, 5, 5, 12, 31, 59, 999999), ), ( arrow.Arrow(2013, 5, 5, 12, 32), arrow.Arrow(2013, 5, 5, 12, 32, 59, 999999), ), ( arrow.Arrow(2013, 5, 5, 12, 33), arrow.Arrow(2013, 5, 5, 12, 33, 59, 999999), ), ( arrow.Arrow(2013, 5, 5, 12, 34), arrow.Arrow(2013, 5, 5, 12, 34, 59, 999999), ), ( arrow.Arrow(2013, 5, 5, 12, 35), arrow.Arrow(2013, 5, 5, 12, 35, 59, 999999), ), ( arrow.Arrow(2013, 5, 5, 12, 36), arrow.Arrow(2013, 5, 5, 12, 36, 59, 999999), ), ( arrow.Arrow(2013, 5, 5, 12, 37), arrow.Arrow(2013, 5, 5, 12, 37, 59, 999999), ), ( arrow.Arrow(2013, 5, 5, 12, 38), arrow.Arrow(2013, 5, 5, 12, 38, 59, 999999), ), ( arrow.Arrow(2013, 5, 5, 12, 39), arrow.Arrow(2013, 5, 5, 12, 39, 59, 999999), ), ] assert result == expected def test_exact_bound_include(self): result = list( arrow.Arrow.span_range( "hour", datetime(2013, 5, 5, 2, 30), datetime(2013, 5, 5, 6, 00), bounds="(]", exact=True, ) ) expected = [ ( arrow.Arrow(2013, 5, 5, 2, 30, 00, 1), arrow.Arrow(2013, 5, 5, 3, 30, 00, 0), ), ( arrow.Arrow(2013, 5, 5, 3, 30, 00, 1), arrow.Arrow(2013, 5, 5, 4, 30, 00, 0), ), ( arrow.Arrow(2013, 5, 5, 4, 30, 00, 1), arrow.Arrow(2013, 5, 5, 5, 30, 00, 0), ), ( arrow.Arrow(2013, 5, 5, 5, 30, 00, 1), arrow.Arrow(2013, 5, 5, 6, 00), ), ] assert result == expected def test_small_interval_exact_open_bounds(self): result = list( arrow.Arrow.span_range( "minute", datetime(2013, 5, 5, 2, 30), datetime(2013, 5, 5, 2, 31), bounds="()", exact=True, ) ) expected = [ ( arrow.Arrow(2013, 5, 5, 2, 30, 00, 1), arrow.Arrow(2013, 5, 5, 2, 30, 59, 999999), ), ] assert result == expected class TestArrowInterval: def test_incorrect_input(self): with pytest.raises(ValueError): list( arrow.Arrow.interval( "month", datetime(2013, 1, 2), datetime(2013, 4, 15), 0 ) ) def test_correct(self): result = list( arrow.Arrow.interval( "hour", datetime(2013, 5, 5, 12, 30), datetime(2013, 5, 5, 17, 15), 2 ) ) assert result == [ ( arrow.Arrow(2013, 5, 5, 12), arrow.Arrow(2013, 5, 5, 13, 59, 59, 999999), ), ( arrow.Arrow(2013, 5, 5, 14), arrow.Arrow(2013, 5, 5, 15, 59, 59, 999999), ), ( arrow.Arrow(2013, 5, 5, 16), arrow.Arrow(2013, 5, 5, 17, 59, 59, 999999), ), ] def test_bounds_param_is_passed(self): result = list( arrow.Arrow.interval( "hour", datetime(2013, 5, 5, 12, 30), datetime(2013, 5, 5, 17, 15), 2, bounds="[]", ) ) assert result == [ (arrow.Arrow(2013, 5, 5, 12), arrow.Arrow(2013, 5, 5, 14)), (arrow.Arrow(2013, 5, 5, 14), arrow.Arrow(2013, 5, 5, 16)), (arrow.Arrow(2013, 5, 5, 16), arrow.Arrow(2013, 5, 5, 18)), ] def test_exact(self): result = list( arrow.Arrow.interval( "hour", datetime(2013, 5, 5, 12, 30), datetime(2013, 5, 5, 17, 15), 4, exact=True, ) ) expected = [ ( arrow.Arrow(2013, 5, 5, 12, 30), arrow.Arrow(2013, 5, 5, 16, 29, 59, 999999), ), ( arrow.Arrow(2013, 5, 5, 16, 30), arrow.Arrow(2013, 5, 5, 17, 14, 59, 999999), ), ] assert result == expected @pytest.mark.usefixtures("time_2013_02_15") class TestArrowSpan: def test_span_attribute(self): with pytest.raises(ValueError): self.arrow.span("span") def test_span_year(self): floor, ceil = self.arrow.span("year") assert floor == datetime(2013, 1, 1, tzinfo=tz.tzutc()) assert ceil == datetime(2013, 12, 31, 23, 59, 59, 999999, tzinfo=tz.tzutc()) def test_span_quarter(self): floor, ceil = self.arrow.span("quarter") assert floor == datetime(2013, 1, 1, tzinfo=tz.tzutc()) assert ceil == datetime(2013, 3, 31, 23, 59, 59, 999999, tzinfo=tz.tzutc()) def test_span_quarter_count(self): floor, ceil = self.arrow.span("quarter", 2) assert floor == datetime(2013, 1, 1, tzinfo=tz.tzutc()) assert ceil == datetime(2013, 6, 30, 23, 59, 59, 999999, tzinfo=tz.tzutc()) def test_span_year_count(self): floor, ceil = self.arrow.span("year", 2) assert floor == datetime(2013, 1, 1, tzinfo=tz.tzutc()) assert ceil == datetime(2014, 12, 31, 23, 59, 59, 999999, tzinfo=tz.tzutc()) def test_span_month(self): floor, ceil = self.arrow.span("month") assert floor == datetime(2013, 2, 1, tzinfo=tz.tzutc()) assert ceil == datetime(2013, 2, 28, 23, 59, 59, 999999, tzinfo=tz.tzutc()) def test_span_week(self): """ >>> self.arrow.format("YYYY-MM-DD") == "2013-02-15" >>> self.arrow.isoweekday() == 5 # a Friday """ # span week from Monday to Sunday floor, ceil = self.arrow.span("week") assert floor == datetime(2013, 2, 11, tzinfo=tz.tzutc()) assert ceil == datetime(2013, 2, 17, 23, 59, 59, 999999, tzinfo=tz.tzutc()) # span week from Tuesday to Monday floor, ceil = self.arrow.span("week", week_start=2) assert floor == datetime(2013, 2, 12, tzinfo=tz.tzutc()) assert ceil == datetime(2013, 2, 18, 23, 59, 59, 999999, tzinfo=tz.tzutc()) # span week from Saturday to Friday floor, ceil = self.arrow.span("week", week_start=6) assert floor == datetime(2013, 2, 9, tzinfo=tz.tzutc()) assert ceil == datetime(2013, 2, 15, 23, 59, 59, 999999, tzinfo=tz.tzutc()) # span week from Sunday to Saturday floor, ceil = self.arrow.span("week", week_start=7) assert floor == datetime(2013, 2, 10, tzinfo=tz.tzutc()) assert ceil == datetime(2013, 2, 16, 23, 59, 59, 999999, tzinfo=tz.tzutc()) def test_span_day(self): floor, ceil = self.arrow.span("day") assert floor == datetime(2013, 2, 15, tzinfo=tz.tzutc()) assert ceil == datetime(2013, 2, 15, 23, 59, 59, 999999, tzinfo=tz.tzutc()) def test_span_hour(self): floor, ceil = self.arrow.span("hour") assert floor == datetime(2013, 2, 15, 3, tzinfo=tz.tzutc()) assert ceil == datetime(2013, 2, 15, 3, 59, 59, 999999, tzinfo=tz.tzutc()) def test_span_minute(self): floor, ceil = self.arrow.span("minute") assert floor == datetime(2013, 2, 15, 3, 41, tzinfo=tz.tzutc()) assert ceil == datetime(2013, 2, 15, 3, 41, 59, 999999, tzinfo=tz.tzutc()) def test_span_second(self): floor, ceil = self.arrow.span("second") assert floor == datetime(2013, 2, 15, 3, 41, 22, tzinfo=tz.tzutc()) assert ceil == datetime(2013, 2, 15, 3, 41, 22, 999999, tzinfo=tz.tzutc()) def test_span_microsecond(self): floor, ceil = self.arrow.span("microsecond") assert floor == datetime(2013, 2, 15, 3, 41, 22, 8923, tzinfo=tz.tzutc()) assert ceil == datetime(2013, 2, 15, 3, 41, 22, 8923, tzinfo=tz.tzutc()) def test_floor(self): floor, ceil = self.arrow.span("month") assert floor == self.arrow.floor("month") assert ceil == self.arrow.ceil("month") def test_span_inclusive_inclusive(self): floor, ceil = self.arrow.span("hour", bounds="[]") assert floor == datetime(2013, 2, 15, 3, tzinfo=tz.tzutc()) assert ceil == datetime(2013, 2, 15, 4, tzinfo=tz.tzutc()) def test_span_exclusive_inclusive(self): floor, ceil = self.arrow.span("hour", bounds="(]") assert floor == datetime(2013, 2, 15, 3, 0, 0, 1, tzinfo=tz.tzutc()) assert ceil == datetime(2013, 2, 15, 4, tzinfo=tz.tzutc()) def test_span_exclusive_exclusive(self): floor, ceil = self.arrow.span("hour", bounds="()") assert floor == datetime(2013, 2, 15, 3, 0, 0, 1, tzinfo=tz.tzutc()) assert ceil == datetime(2013, 2, 15, 3, 59, 59, 999999, tzinfo=tz.tzutc()) def test_bounds_are_validated(self): with pytest.raises(ValueError): floor, ceil = self.arrow.span("hour", bounds="][") def test_exact(self): result_floor, result_ceil = self.arrow.span("hour", exact=True) expected_floor = datetime(2013, 2, 15, 3, 41, 22, 8923, tzinfo=tz.tzutc()) expected_ceil = datetime(2013, 2, 15, 4, 41, 22, 8922, tzinfo=tz.tzutc()) assert result_floor == expected_floor assert result_ceil == expected_ceil def test_exact_inclusive_inclusive(self): floor, ceil = self.arrow.span("minute", bounds="[]", exact=True) assert floor == datetime(2013, 2, 15, 3, 41, 22, 8923, tzinfo=tz.tzutc()) assert ceil == datetime(2013, 2, 15, 3, 42, 22, 8923, tzinfo=tz.tzutc()) def test_exact_exclusive_inclusive(self): floor, ceil = self.arrow.span("day", bounds="(]", exact=True) assert floor == datetime(2013, 2, 15, 3, 41, 22, 8924, tzinfo=tz.tzutc()) assert ceil == datetime(2013, 2, 16, 3, 41, 22, 8923, tzinfo=tz.tzutc()) def test_exact_exclusive_exclusive(self): floor, ceil = self.arrow.span("second", bounds="()", exact=True) assert floor == datetime(2013, 2, 15, 3, 41, 22, 8924, tzinfo=tz.tzutc()) assert ceil == datetime(2013, 2, 15, 3, 41, 23, 8922, tzinfo=tz.tzutc()) def test_all_parameters_specified(self): floor, ceil = self.arrow.span("week", bounds="()", exact=True, count=2) assert floor == datetime(2013, 2, 15, 3, 41, 22, 8924, tzinfo=tz.tzutc()) assert ceil == datetime(2013, 3, 1, 3, 41, 22, 8922, tzinfo=tz.tzutc()) @pytest.mark.usefixtures("time_2013_01_01") class TestArrowHumanize: def test_granularity(self): assert self.now.humanize(granularity="second") == "just now" later1 = self.now.shift(seconds=1) assert self.now.humanize(later1, granularity="second") == "just now" assert later1.humanize(self.now, granularity="second") == "just now" assert self.now.humanize(later1, granularity="minute") == "0 minutes ago" assert later1.humanize(self.now, granularity="minute") == "in 0 minutes" later100 = self.now.shift(seconds=100) assert self.now.humanize(later100, granularity="second") == "100 seconds ago" assert later100.humanize(self.now, granularity="second") == "in 100 seconds" assert self.now.humanize(later100, granularity="minute") == "a minute ago" assert later100.humanize(self.now, granularity="minute") == "in a minute" assert self.now.humanize(later100, granularity="hour") == "0 hours ago" assert later100.humanize(self.now, granularity="hour") == "in 0 hours" later4000 = self.now.shift(seconds=4000) assert self.now.humanize(later4000, granularity="minute") == "66 minutes ago" assert later4000.humanize(self.now, granularity="minute") == "in 66 minutes" assert self.now.humanize(later4000, granularity="hour") == "an hour ago" assert later4000.humanize(self.now, granularity="hour") == "in an hour" assert self.now.humanize(later4000, granularity="day") == "0 days ago" assert later4000.humanize(self.now, granularity="day") == "in 0 days" later105 = self.now.shift(seconds=10 ** 5) assert self.now.humanize(later105, granularity="hour") == "27 hours ago" assert later105.humanize(self.now, granularity="hour") == "in 27 hours" assert self.now.humanize(later105, granularity="day") == "a day ago" assert later105.humanize(self.now, granularity="day") == "in a day" assert self.now.humanize(later105, granularity="week") == "0 weeks ago" assert later105.humanize(self.now, granularity="week") == "in 0 weeks" assert self.now.humanize(later105, granularity="month") == "0 months ago" assert later105.humanize(self.now, granularity="month") == "in 0 months" assert self.now.humanize(later105, granularity=["month"]) == "0 months ago" assert later105.humanize(self.now, granularity=["month"]) == "in 0 months" later106 = self.now.shift(seconds=3 * 10 ** 6) assert self.now.humanize(later106, granularity="day") == "34 days ago" assert later106.humanize(self.now, granularity="day") == "in 34 days" assert self.now.humanize(later106, granularity="week") == "4 weeks ago" assert later106.humanize(self.now, granularity="week") == "in 4 weeks" assert self.now.humanize(later106, granularity="month") == "a month ago" assert later106.humanize(self.now, granularity="month") == "in a month" assert self.now.humanize(later106, granularity="year") == "0 years ago" assert later106.humanize(self.now, granularity="year") == "in 0 years" later506 = self.now.shift(seconds=50 * 10 ** 6) assert self.now.humanize(later506, granularity="week") == "82 weeks ago" assert later506.humanize(self.now, granularity="week") == "in 82 weeks" assert self.now.humanize(later506, granularity="month") == "18 months ago" assert later506.humanize(self.now, granularity="month") == "in 18 months" assert self.now.humanize(later506, granularity="quarter") == "6 quarters ago" assert later506.humanize(self.now, granularity="quarter") == "in 6 quarters" assert self.now.humanize(later506, granularity="year") == "a year ago" assert later506.humanize(self.now, granularity="year") == "in a year" assert self.now.humanize(later1, granularity="quarter") == "0 quarters ago" assert later1.humanize(self.now, granularity="quarter") == "in 0 quarters" later107 = self.now.shift(seconds=10 ** 7) assert self.now.humanize(later107, granularity="quarter") == "a quarter ago" assert later107.humanize(self.now, granularity="quarter") == "in a quarter" later207 = self.now.shift(seconds=2 * 10 ** 7) assert self.now.humanize(later207, granularity="quarter") == "2 quarters ago" assert later207.humanize(self.now, granularity="quarter") == "in 2 quarters" later307 = self.now.shift(seconds=3 * 10 ** 7) assert self.now.humanize(later307, granularity="quarter") == "3 quarters ago" assert later307.humanize(self.now, granularity="quarter") == "in 3 quarters" later377 = self.now.shift(seconds=3.7 * 10 ** 7) assert self.now.humanize(later377, granularity="quarter") == "4 quarters ago" assert later377.humanize(self.now, granularity="quarter") == "in 4 quarters" later407 = self.now.shift(seconds=4 * 10 ** 7) assert self.now.humanize(later407, granularity="quarter") == "5 quarters ago" assert later407.humanize(self.now, granularity="quarter") == "in 5 quarters" later108 = self.now.shift(seconds=10 ** 8) assert self.now.humanize(later108, granularity="year") == "3 years ago" assert later108.humanize(self.now, granularity="year") == "in 3 years" later108onlydistance = self.now.shift(seconds=10 ** 8) assert ( self.now.humanize( later108onlydistance, only_distance=True, granularity="year" ) == "3 years" ) assert ( later108onlydistance.humanize( self.now, only_distance=True, granularity="year" ) == "3 years" ) with pytest.raises(ValueError): self.now.humanize(later108, granularity="years") def test_multiple_granularity(self): assert self.now.humanize(granularity="second") == "just now" assert self.now.humanize(granularity=["second"]) == "just now" assert ( self.now.humanize(granularity=["year", "month", "day", "hour", "second"]) == "in 0 years 0 months 0 days 0 hours and 0 seconds" ) later4000 = self.now.shift(seconds=4000) assert ( later4000.humanize(self.now, granularity=["hour", "minute"]) == "in an hour and 6 minutes" ) assert ( self.now.humanize(later4000, granularity=["hour", "minute"]) == "an hour and 6 minutes ago" ) assert ( later4000.humanize( self.now, granularity=["hour", "minute"], only_distance=True ) == "an hour and 6 minutes" ) assert ( later4000.humanize(self.now, granularity=["day", "hour", "minute"]) == "in 0 days an hour and 6 minutes" ) assert ( self.now.humanize(later4000, granularity=["day", "hour", "minute"]) == "0 days an hour and 6 minutes ago" ) later105 = self.now.shift(seconds=10 ** 5) assert ( self.now.humanize(later105, granularity=["hour", "day", "minute"]) == "a day 3 hours and 46 minutes ago" ) with pytest.raises(ValueError): self.now.humanize(later105, granularity=["error", "second"]) later108onlydistance = self.now.shift(seconds=10 ** 8) assert ( self.now.humanize( later108onlydistance, only_distance=True, granularity=["year"] ) == "3 years" ) assert ( self.now.humanize( later108onlydistance, only_distance=True, granularity=["month", "week"] ) == "37 months and 4 weeks" ) # this will change when leap years are implemented assert ( self.now.humanize( later108onlydistance, only_distance=True, granularity=["year", "second"] ) == "3 years and 5392000 seconds" ) one_min_one_sec_ago = self.now.shift(minutes=-1, seconds=-1) assert ( one_min_one_sec_ago.humanize(self.now, granularity=["minute", "second"]) == "a minute and a second ago" ) one_min_two_secs_ago = self.now.shift(minutes=-1, seconds=-2) assert ( one_min_two_secs_ago.humanize(self.now, granularity=["minute", "second"]) == "a minute and 2 seconds ago" ) def test_seconds(self): later = self.now.shift(seconds=10) # regression test for issue #727 assert self.now.humanize(later) == "10 seconds ago" assert later.humanize(self.now) == "in 10 seconds" assert self.now.humanize(later, only_distance=True) == "10 seconds" assert later.humanize(self.now, only_distance=True) == "10 seconds" def test_minute(self): later = self.now.shift(minutes=1) assert self.now.humanize(later) == "a minute ago" assert later.humanize(self.now) == "in a minute" assert self.now.humanize(later, only_distance=True) == "a minute" assert later.humanize(self.now, only_distance=True) == "a minute" def test_minutes(self): later = self.now.shift(minutes=2) assert self.now.humanize(later) == "2 minutes ago" assert later.humanize(self.now) == "in 2 minutes" assert self.now.humanize(later, only_distance=True) == "2 minutes" assert later.humanize(self.now, only_distance=True) == "2 minutes" def test_hour(self): later = self.now.shift(hours=1) assert self.now.humanize(later) == "an hour ago" assert later.humanize(self.now) == "in an hour" assert self.now.humanize(later, only_distance=True) == "an hour" assert later.humanize(self.now, only_distance=True) == "an hour" def test_hours(self): later = self.now.shift(hours=2) assert self.now.humanize(later) == "2 hours ago" assert later.humanize(self.now) == "in 2 hours" assert self.now.humanize(later, only_distance=True) == "2 hours" assert later.humanize(self.now, only_distance=True) == "2 hours" def test_day(self): later = self.now.shift(days=1) assert self.now.humanize(later) == "a day ago" assert later.humanize(self.now) == "in a day" # regression test for issue #697 less_than_48_hours = self.now.shift( days=1, hours=23, seconds=59, microseconds=999999 ) assert self.now.humanize(less_than_48_hours) == "a day ago" assert less_than_48_hours.humanize(self.now) == "in a day" less_than_48_hours_date = less_than_48_hours._datetime.date() with pytest.raises(TypeError): # humanize other argument does not take raw datetime.date objects self.now.humanize(less_than_48_hours_date) assert self.now.humanize(later, only_distance=True) == "a day" assert later.humanize(self.now, only_distance=True) == "a day" def test_days(self): later = self.now.shift(days=2) assert self.now.humanize(later) == "2 days ago" assert later.humanize(self.now) == "in 2 days" assert self.now.humanize(later, only_distance=True) == "2 days" assert later.humanize(self.now, only_distance=True) == "2 days" # Regression tests for humanize bug referenced in issue 541 later = self.now.shift(days=3) assert later.humanize(self.now) == "in 3 days" later = self.now.shift(days=3, seconds=1) assert later.humanize(self.now) == "in 3 days" later = self.now.shift(days=4) assert later.humanize(self.now) == "in 4 days" def test_week(self): later = self.now.shift(weeks=1) assert self.now.humanize(later) == "a week ago" assert later.humanize(self.now) == "in a week" assert self.now.humanize(later, only_distance=True) == "a week" assert later.humanize(self.now, only_distance=True) == "a week" def test_weeks(self): later = self.now.shift(weeks=2) assert self.now.humanize(later) == "2 weeks ago" assert later.humanize(self.now) == "in 2 weeks" assert self.now.humanize(later, only_distance=True) == "2 weeks" assert later.humanize(self.now, only_distance=True) == "2 weeks" @pytest.mark.xfail(reason="known issue with humanize month limits") def test_month(self): later = self.now.shift(months=1) # TODO this test now returns "4 weeks ago", we need to fix this to be correct on a per month basis assert self.now.humanize(later) == "a month ago" assert later.humanize(self.now) == "in a month" assert self.now.humanize(later, only_distance=True) == "a month" assert later.humanize(self.now, only_distance=True) == "a month" def test_month_plus_4_days(self): # TODO needed for coverage, remove when month limits are fixed later = self.now.shift(months=1, days=4) assert self.now.humanize(later) == "a month ago" assert later.humanize(self.now) == "in a month" @pytest.mark.xfail(reason="known issue with humanize month limits") def test_months(self): later = self.now.shift(months=2) earlier = self.now.shift(months=-2) assert earlier.humanize(self.now) == "2 months ago" assert later.humanize(self.now) == "in 2 months" assert self.now.humanize(later, only_distance=True) == "2 months" assert later.humanize(self.now, only_distance=True) == "2 months" def test_year(self): later = self.now.shift(years=1) assert self.now.humanize(later) == "a year ago" assert later.humanize(self.now) == "in a year" assert self.now.humanize(later, only_distance=True) == "a year" assert later.humanize(self.now, only_distance=True) == "a year" def test_years(self): later = self.now.shift(years=2) assert self.now.humanize(later) == "2 years ago" assert later.humanize(self.now) == "in 2 years" assert self.now.humanize(later, only_distance=True) == "2 years" assert later.humanize(self.now, only_distance=True) == "2 years" arw = arrow.Arrow(2014, 7, 2) result = arw.humanize(self.datetime) assert result == "in a year" def test_arrow(self): arw = arrow.Arrow.fromdatetime(self.datetime) result = arw.humanize(arrow.Arrow.fromdatetime(self.datetime)) assert result == "just now" def test_datetime_tzinfo(self): arw = arrow.Arrow.fromdatetime(self.datetime) result = arw.humanize(self.datetime.replace(tzinfo=tz.tzutc())) assert result == "just now" def test_other(self): arw = arrow.Arrow.fromdatetime(self.datetime) with pytest.raises(TypeError): arw.humanize(object()) def test_invalid_locale(self): arw = arrow.Arrow.fromdatetime(self.datetime) with pytest.raises(ValueError): arw.humanize(locale="klingon") def test_none(self): arw = arrow.Arrow.utcnow() result = arw.humanize() assert result == "just now" result = arw.humanize(None) assert result == "just now" def test_week_limit(self): # regression test for issue #848 arw = arrow.Arrow.utcnow() later = arw.shift(weeks=+1) result = arw.humanize(later) assert result == "a week ago" def test_untranslated_granularity(self, mocker): arw = arrow.Arrow.utcnow() later = arw.shift(weeks=1) # simulate an untranslated timeframe key mocker.patch.dict("arrow.locales.EnglishLocale.timeframes") del arrow.locales.EnglishLocale.timeframes["week"] with pytest.raises(ValueError): arw.humanize(later, granularity="week") def test_empty_granularity_list(self): arw = arrow.Arrow(2013, 1, 1, 0, 0, 0) later = arw.shift(seconds=55000) with pytest.raises(ValueError): arw.humanize(later, granularity=[]) # Bulgarian is an example of a language that overrides _format_timeframe # Applicabale to all locales. Note: Contributors need to make sure # that if they override describe or describe_mutli, that delta # is truncated on call def test_no_floats(self): arw = arrow.Arrow(2013, 1, 1, 0, 0, 0) later = arw.shift(seconds=55000) humanize_string = arw.humanize(later, locale="bg", granularity="minute") assert humanize_string == "916 минути назад" def test_no_floats_multi_gran(self): arw = arrow.Arrow(2013, 1, 1, 0, 0, 0) later = arw.shift(seconds=55000) humanize_string = arw.humanize( later, locale="bg", granularity=["second", "minute"] ) assert humanize_string == "916 минути 40 няколко секунди назад" @pytest.mark.usefixtures("time_2013_01_01") class TestArrowHumanizeTestsWithLocale: def test_now(self): arw = arrow.Arrow(2013, 1, 1, 0, 0, 0) result = arw.humanize(self.datetime, locale="ru") assert result == "сейчас" def test_seconds(self): arw = arrow.Arrow(2013, 1, 1, 0, 0, 44) result = arw.humanize(self.datetime, locale="ru") assert result == "через 44 несколько секунд" def test_years(self): arw = arrow.Arrow(2011, 7, 2) result = arw.humanize(self.datetime, locale="ru") assert result == "год назад" # Fixtures for Dehumanize @pytest.fixture(scope="class") def locale_list_no_weeks(): tested_langs = [ "en", "en-us", "en-gb", "en-au", "en-be", "en-jp", "en-za", "en-ca", "en-ph", "fr", "fr-fr", "fr-ca", "it", "it-it", "es", "es-es", "el", "el-gr", "ja", "ja-jp", "sv", "sv-se", "fi", "fi-fi", "zh", "zh-cn", "zh-tw", "zh-hk", "nl", "nl-nl", "af", "de", "de-de", "de-ch", "de-at", "nb", "nb-no", "nn", "nn-no", "pt", "pt-pt", "pt_br", "tl", "tl-ph", "vi", "vi-vn", "tr", "tr-tr", "az", "az-az", "da", "da-dk", "ml", "hi", "fa", "fa-ir", "mr", "ca", "ca-es", "ca-ad", "ca-fr", "ca-it", "eo", "eo-xx", "bn", "bn-bd", "bn-in", "rm", "rm-ch", "ro", "ro-ro", "sl", "sl-si", "id", "id-id", "ne", "ne-np", "ee", "et", "sw", "sw-ke", "sw-tz", "la", "la-va", "lt", "lt-lt", "ms", "ms-my", "ms-bn", "or", "or-in", "se", "se-fi", "se-no", "se-se", "lb", "lb-lu", "zu", "zu-za", "sq", "sq-al", "ta", "ta-in", "ta-lk", "ur", "ur-pk", ] return tested_langs @pytest.fixture(scope="class") def locale_list_with_weeks(): tested_langs = [ "en", "en-us", "en-gb", "en-au", "en-be", "en-jp", "en-za", "en-ca", "en-ph", "fr", "fr-fr", "fr-ca", "it", "it-it", "es", "es-es", "ja", "ja-jp", "sv", "sv-se", "zh", "zh-cn", "zh-tw", "zh-hk", "nl", "nl-nl", "de", "de-de", "de-ch", "de-at", "pt", "pt-pt", "pt-br", "tl", "tl-ph", "vi", "vi-vn", "sw", "sw-ke", "sw-tz", "la", "la-va", "lt", "lt-lt", "ms", "ms-my", "ms-bn", "lb", "lb-lu", "zu", "zu-za", "ta", "ta-in", "ta-lk", ] return tested_langs class TestArrowDehumanize: def test_now(self, locale_list_no_weeks): for lang in locale_list_no_weeks: arw = arrow.Arrow(2000, 6, 18, 5, 55, 0) second_ago = arw.shift(seconds=-1) second_future = arw.shift(seconds=1) second_ago_string = second_ago.humanize( arw, locale=lang, granularity=["second"] ) second_future_string = second_future.humanize( arw, locale=lang, granularity=["second"] ) assert arw.dehumanize(second_ago_string, locale=lang) == arw assert arw.dehumanize(second_future_string, locale=lang) == arw def test_seconds(self, locale_list_no_weeks): for lang in locale_list_no_weeks: arw = arrow.Arrow(2000, 6, 18, 5, 55, 0) second_ago = arw.shift(seconds=-5) second_future = arw.shift(seconds=5) second_ago_string = second_ago.humanize( arw, locale=lang, granularity=["second"] ) second_future_string = second_future.humanize( arw, locale=lang, granularity=["second"] ) assert arw.dehumanize(second_ago_string, locale=lang) == second_ago assert arw.dehumanize(second_future_string, locale=lang) == second_future def test_minute(self, locale_list_no_weeks): for lang in locale_list_no_weeks: arw = arrow.Arrow(2001, 6, 18, 5, 55, 0) minute_ago = arw.shift(minutes=-1) minute_future = arw.shift(minutes=1) minute_ago_string = minute_ago.humanize( arw, locale=lang, granularity=["minute"] ) minute_future_string = minute_future.humanize( arw, locale=lang, granularity=["minute"] ) assert arw.dehumanize(minute_ago_string, locale=lang) == minute_ago assert arw.dehumanize(minute_future_string, locale=lang) == minute_future def test_minutes(self, locale_list_no_weeks): for lang in locale_list_no_weeks: arw = arrow.Arrow(2007, 1, 10, 5, 55, 0) minute_ago = arw.shift(minutes=-5) minute_future = arw.shift(minutes=5) minute_ago_string = minute_ago.humanize( arw, locale=lang, granularity=["minute"] ) minute_future_string = minute_future.humanize( arw, locale=lang, granularity=["minute"] ) assert arw.dehumanize(minute_ago_string, locale=lang) == minute_ago assert arw.dehumanize(minute_future_string, locale=lang) == minute_future def test_hour(self, locale_list_no_weeks): for lang in locale_list_no_weeks: arw = arrow.Arrow(2009, 4, 20, 5, 55, 0) hour_ago = arw.shift(hours=-1) hour_future = arw.shift(hours=1) hour_ago_string = hour_ago.humanize(arw, locale=lang, granularity=["hour"]) hour_future_string = hour_future.humanize( arw, locale=lang, granularity=["hour"] ) assert arw.dehumanize(hour_ago_string, locale=lang) == hour_ago assert arw.dehumanize(hour_future_string, locale=lang) == hour_future def test_hours(self, locale_list_no_weeks): for lang in locale_list_no_weeks: arw = arrow.Arrow(2010, 2, 16, 7, 55, 0) hour_ago = arw.shift(hours=-3) hour_future = arw.shift(hours=3) hour_ago_string = hour_ago.humanize(arw, locale=lang, granularity=["hour"]) hour_future_string = hour_future.humanize( arw, locale=lang, granularity=["hour"] ) assert arw.dehumanize(hour_ago_string, locale=lang) == hour_ago assert arw.dehumanize(hour_future_string, locale=lang) == hour_future def test_week(self, locale_list_with_weeks): for lang in locale_list_with_weeks: arw = arrow.Arrow(2012, 2, 18, 1, 52, 0) week_ago = arw.shift(weeks=-1) week_future = arw.shift(weeks=1) week_ago_string = week_ago.humanize(arw, locale=lang, granularity=["week"]) week_future_string = week_future.humanize( arw, locale=lang, granularity=["week"] ) assert arw.dehumanize(week_ago_string, locale=lang) == week_ago assert arw.dehumanize(week_future_string, locale=lang) == week_future def test_weeks(self, locale_list_with_weeks): for lang in locale_list_with_weeks: arw = arrow.Arrow(2020, 3, 18, 5, 3, 0) week_ago = arw.shift(weeks=-7) week_future = arw.shift(weeks=7) week_ago_string = week_ago.humanize(arw, locale=lang, granularity=["week"]) week_future_string = week_future.humanize( arw, locale=lang, granularity=["week"] ) assert arw.dehumanize(week_ago_string, locale=lang) == week_ago assert arw.dehumanize(week_future_string, locale=lang) == week_future def test_year(self, locale_list_no_weeks): for lang in locale_list_no_weeks: arw = arrow.Arrow(2000, 1, 10, 5, 55, 0) year_ago = arw.shift(years=-1) year_future = arw.shift(years=1) year_ago_string = year_ago.humanize(arw, locale=lang, granularity=["year"]) year_future_string = year_future.humanize( arw, locale=lang, granularity=["year"] ) assert arw.dehumanize(year_ago_string, locale=lang) == year_ago assert arw.dehumanize(year_future_string, locale=lang) == year_future def test_years(self, locale_list_no_weeks): for lang in locale_list_no_weeks: arw = arrow.Arrow(2000, 1, 10, 5, 55, 0) year_ago = arw.shift(years=-10) year_future = arw.shift(years=10) year_ago_string = year_ago.humanize(arw, locale=lang, granularity=["year"]) year_future_string = year_future.humanize( arw, locale=lang, granularity=["year"] ) assert arw.dehumanize(year_ago_string, locale=lang) == year_ago assert arw.dehumanize(year_future_string, locale=lang) == year_future def test_gt_than_10_years(self, locale_list_no_weeks): for lang in locale_list_no_weeks: arw = arrow.Arrow(2000, 1, 10, 5, 55, 0) year_ago = arw.shift(years=-25) year_future = arw.shift(years=25) year_ago_string = year_ago.humanize(arw, locale=lang, granularity=["year"]) year_future_string = year_future.humanize( arw, locale=lang, granularity=["year"] ) assert arw.dehumanize(year_ago_string, locale=lang) == year_ago assert arw.dehumanize(year_future_string, locale=lang) == year_future def test_mixed_granularity(self, locale_list_no_weeks): for lang in locale_list_no_weeks: arw = arrow.Arrow(2000, 1, 10, 5, 55, 0) past = arw.shift(hours=-1, minutes=-1, seconds=-1) future = arw.shift(hours=1, minutes=1, seconds=1) past_string = past.humanize( arw, locale=lang, granularity=["hour", "minute", "second"] ) future_string = future.humanize( arw, locale=lang, granularity=["hour", "minute", "second"] ) assert arw.dehumanize(past_string, locale=lang) == past assert arw.dehumanize(future_string, locale=lang) == future def test_mixed_granularity_hours(self, locale_list_no_weeks): for lang in locale_list_no_weeks: arw = arrow.Arrow(2000, 1, 10, 5, 55, 0) past = arw.shift(hours=-3, minutes=-1, seconds=-15) future = arw.shift(hours=3, minutes=1, seconds=15) past_string = past.humanize( arw, locale=lang, granularity=["hour", "minute", "second"] ) future_string = future.humanize( arw, locale=lang, granularity=["hour", "minute", "second"] ) assert arw.dehumanize(past_string, locale=lang) == past assert arw.dehumanize(future_string, locale=lang) == future def test_mixed_granularity_day(self, locale_list_no_weeks): for lang in locale_list_no_weeks: arw = arrow.Arrow(2000, 1, 10, 5, 55, 0) past = arw.shift(days=-3, minutes=-1, seconds=-15) future = arw.shift(days=3, minutes=1, seconds=15) past_string = past.humanize( arw, locale=lang, granularity=["day", "minute", "second"] ) future_string = future.humanize( arw, locale=lang, granularity=["day", "minute", "second"] ) assert arw.dehumanize(past_string, locale=lang) == past assert arw.dehumanize(future_string, locale=lang) == future def test_mixed_granularity_day_hour(self, locale_list_no_weeks): for lang in locale_list_no_weeks: arw = arrow.Arrow(2000, 1, 10, 5, 55, 0) past = arw.shift(days=-3, hours=-23, seconds=-15) future = arw.shift(days=3, hours=23, seconds=15) past_string = past.humanize( arw, locale=lang, granularity=["day", "hour", "second"] ) future_string = future.humanize( arw, locale=lang, granularity=["day", "hour", "second"] ) assert arw.dehumanize(past_string, locale=lang) == past assert arw.dehumanize(future_string, locale=lang) == future # Test to make sure unsupported locales error out def test_unsupported_locale(self): arw = arrow.Arrow(2000, 6, 18, 5, 55, 0) second_ago = arw.shift(seconds=-5) second_future = arw.shift(seconds=5) second_ago_string = second_ago.humanize( arw, locale="ko", granularity=["second"] ) second_future_string = second_future.humanize( arw, locale="ko", granularity=["second"] ) # ko is an example of many unsupported locales currently with pytest.raises(ValueError): arw.dehumanize(second_ago_string, locale="ko") with pytest.raises(ValueError): arw.dehumanize(second_future_string, locale="ko") # Test to ensure old style locale strings are supported def test_normalized_locale(self): arw = arrow.Arrow(2000, 6, 18, 5, 55, 0) second_ago = arw.shift(seconds=-5) second_future = arw.shift(seconds=5) second_ago_string = second_ago.humanize( arw, locale="zh_hk", granularity=["second"] ) second_future_string = second_future.humanize( arw, locale="zh_hk", granularity=["second"] ) assert arw.dehumanize(second_ago_string, locale="zh_hk") == second_ago assert arw.dehumanize(second_future_string, locale="zh_hk") == second_future # Ensures relative units are required in string def test_require_relative_unit(self, locale_list_no_weeks): for lang in locale_list_no_weeks: arw = arrow.Arrow(2000, 6, 18, 5, 55, 0) second_ago = arw.shift(seconds=-5) second_future = arw.shift(seconds=5) second_ago_string = second_ago.humanize( arw, locale=lang, granularity=["second"], only_distance=True ) second_future_string = second_future.humanize( arw, locale=lang, granularity=["second"], only_distance=True ) with pytest.raises(ValueError): arw.dehumanize(second_ago_string, locale=lang) with pytest.raises(ValueError): arw.dehumanize(second_future_string, locale=lang) # Test for scrambled input def test_scrambled_input(self, locale_list_no_weeks): for lang in locale_list_no_weeks: arw = arrow.Arrow(2000, 6, 18, 5, 55, 0) second_ago = arw.shift(seconds=-5) second_future = arw.shift(seconds=5) second_ago_string = second_ago.humanize( arw, locale=lang, granularity=["second"], only_distance=True ) second_future_string = second_future.humanize( arw, locale=lang, granularity=["second"], only_distance=True ) # Scrambles input by sorting strings second_ago_presort = sorted(second_ago_string) second_ago_string = "".join(second_ago_presort) second_future_presort = sorted(second_future_string) second_future_string = "".join(second_future_presort) with pytest.raises(ValueError): arw.dehumanize(second_ago_string, locale=lang) with pytest.raises(ValueError): arw.dehumanize(second_future_string, locale=lang) def test_no_units_modified(self, locale_list_no_weeks): for lang in locale_list_no_weeks: arw = arrow.Arrow(2000, 6, 18, 5, 55, 0) # Ensures we pass the first stage of checking whether relative units exist locale_obj = locales.get_locale(lang) empty_past_string = locale_obj.past empty_future_string = locale_obj.future with pytest.raises(ValueError): arw.dehumanize(empty_past_string, locale=lang) with pytest.raises(ValueError): arw.dehumanize(empty_future_string, locale=lang) class TestArrowIsBetween: def test_start_before_end(self): target = arrow.Arrow.fromdatetime(datetime(2013, 5, 7)) start = arrow.Arrow.fromdatetime(datetime(2013, 5, 8)) end = arrow.Arrow.fromdatetime(datetime(2013, 5, 5)) assert not target.is_between(start, end) def test_exclusive_exclusive_bounds(self): target = arrow.Arrow.fromdatetime(datetime(2013, 5, 5, 12, 30, 27)) start = arrow.Arrow.fromdatetime(datetime(2013, 5, 5, 12, 30, 10)) end = arrow.Arrow.fromdatetime(datetime(2013, 5, 5, 12, 30, 36)) assert target.is_between(start, end, "()") def test_exclusive_exclusive_bounds_same_date(self): target = arrow.Arrow.fromdatetime(datetime(2013, 5, 7)) start = arrow.Arrow.fromdatetime(datetime(2013, 5, 7)) end = arrow.Arrow.fromdatetime(datetime(2013, 5, 7)) assert not target.is_between(start, end, "()") def test_inclusive_exclusive_bounds(self): target = arrow.Arrow.fromdatetime(datetime(2013, 5, 6)) start = arrow.Arrow.fromdatetime(datetime(2013, 5, 4)) end = arrow.Arrow.fromdatetime(datetime(2013, 5, 6)) assert not target.is_between(start, end, "[)") def test_exclusive_inclusive_bounds(self): target = arrow.Arrow.fromdatetime(datetime(2013, 5, 7)) start = arrow.Arrow.fromdatetime(datetime(2013, 5, 5)) end = arrow.Arrow.fromdatetime(datetime(2013, 5, 7)) assert target.is_between(start, end, "(]") def test_inclusive_inclusive_bounds_same_date(self): target = arrow.Arrow.fromdatetime(datetime(2013, 5, 7)) start = arrow.Arrow.fromdatetime(datetime(2013, 5, 7)) end = arrow.Arrow.fromdatetime(datetime(2013, 5, 7)) assert target.is_between(start, end, "[]") def test_inclusive_inclusive_bounds_target_before_start(self): target = arrow.Arrow.fromdatetime(datetime(2020, 12, 24)) start = arrow.Arrow.fromdatetime(datetime(2020, 12, 25)) end = arrow.Arrow.fromdatetime(datetime(2020, 12, 26)) assert not target.is_between(start, end, "[]") def test_type_error_exception(self): with pytest.raises(TypeError): target = arrow.Arrow.fromdatetime(datetime(2013, 5, 7)) start = datetime(2013, 5, 5) end = arrow.Arrow.fromdatetime(datetime(2013, 5, 8)) target.is_between(start, end) with pytest.raises(TypeError): target = arrow.Arrow.fromdatetime(datetime(2013, 5, 7)) start = arrow.Arrow.fromdatetime(datetime(2013, 5, 5)) end = datetime(2013, 5, 8) target.is_between(start, end) with pytest.raises(TypeError): target.is_between(None, None) def test_value_error_exception(self): target = arrow.Arrow.fromdatetime(datetime(2013, 5, 7)) start = arrow.Arrow.fromdatetime(datetime(2013, 5, 5)) end = arrow.Arrow.fromdatetime(datetime(2013, 5, 8)) with pytest.raises(ValueError): target.is_between(start, end, "][") with pytest.raises(ValueError): target.is_between(start, end, "") with pytest.raises(ValueError): target.is_between(start, end, "]") with pytest.raises(ValueError): target.is_between(start, end, "[") with pytest.raises(ValueError): target.is_between(start, end, "hello") with pytest.raises(ValueError): target.span("week", week_start=55) class TestArrowUtil: def test_get_datetime(self): get_datetime = arrow.Arrow._get_datetime arw = arrow.Arrow.utcnow() dt = datetime.utcnow() timestamp = time.time() assert get_datetime(arw) == arw.datetime assert get_datetime(dt) == dt assert ( get_datetime(timestamp) == arrow.Arrow.utcfromtimestamp(timestamp).datetime ) with pytest.raises(ValueError) as raise_ctx: get_datetime("abc") assert "not recognized as a datetime or timestamp" in str(raise_ctx.value) def test_get_tzinfo(self): get_tzinfo = arrow.Arrow._get_tzinfo with pytest.raises(ValueError) as raise_ctx: get_tzinfo("abc") assert "not recognized as a timezone" in str(raise_ctx.value) def test_get_iteration_params(self): assert arrow.Arrow._get_iteration_params("end", None) == ("end", sys.maxsize) assert arrow.Arrow._get_iteration_params(None, 100) == (arrow.Arrow.max, 100) assert arrow.Arrow._get_iteration_params(100, 120) == (100, 120) with pytest.raises(ValueError): arrow.Arrow._get_iteration_params(None, None) python-arrow-1.2.1/tests/test_factory.py000066400000000000000000000315441415100123600204060ustar00rootroot00000000000000import time from datetime import date, datetime from decimal import Decimal import pytest from dateutil import tz from arrow import Arrow from arrow.parser import ParserError from .utils import assert_datetime_equality @pytest.mark.usefixtures("arrow_factory") class TestGet: def test_no_args(self): assert_datetime_equality( self.factory.get(), datetime.utcnow().replace(tzinfo=tz.tzutc()) ) def test_timestamp_one_arg_no_arg(self): no_arg = self.factory.get(1406430900).timestamp() one_arg = self.factory.get("1406430900", "X").timestamp() assert no_arg == one_arg def test_one_arg_none(self): with pytest.raises(TypeError): self.factory.get(None) def test_struct_time(self): assert_datetime_equality( self.factory.get(time.gmtime()), datetime.utcnow().replace(tzinfo=tz.tzutc()), ) def test_one_arg_timestamp(self): int_timestamp = int(time.time()) timestamp_dt = datetime.utcfromtimestamp(int_timestamp).replace( tzinfo=tz.tzutc() ) assert self.factory.get(int_timestamp) == timestamp_dt with pytest.raises(ParserError): self.factory.get(str(int_timestamp)) float_timestamp = time.time() timestamp_dt = datetime.utcfromtimestamp(float_timestamp).replace( tzinfo=tz.tzutc() ) assert self.factory.get(float_timestamp) == timestamp_dt with pytest.raises(ParserError): self.factory.get(str(float_timestamp)) # Regression test for issue #216 # Python 3 raises OverflowError, Python 2 raises ValueError timestamp = 99999999999999999999999999.99999999999999999999999999 with pytest.raises((OverflowError, ValueError)): self.factory.get(timestamp) def test_one_arg_expanded_timestamp(self): millisecond_timestamp = 1591328104308 microsecond_timestamp = 1591328104308505 # Regression test for issue #796 assert self.factory.get(millisecond_timestamp) == datetime.utcfromtimestamp( 1591328104.308 ).replace(tzinfo=tz.tzutc()) assert self.factory.get(microsecond_timestamp) == datetime.utcfromtimestamp( 1591328104.308505 ).replace(tzinfo=tz.tzutc()) def test_one_arg_timestamp_with_tzinfo(self): timestamp = time.time() timestamp_dt = datetime.fromtimestamp(timestamp, tz=tz.tzutc()).astimezone( tz.gettz("US/Pacific") ) timezone = tz.gettz("US/Pacific") assert_datetime_equality( self.factory.get(timestamp, tzinfo=timezone), timestamp_dt ) def test_one_arg_arrow(self): arw = self.factory.utcnow() result = self.factory.get(arw) assert arw == result def test_one_arg_datetime(self): dt = datetime.utcnow().replace(tzinfo=tz.tzutc()) assert self.factory.get(dt) == dt def test_one_arg_date(self): d = date.today() dt = datetime(d.year, d.month, d.day, tzinfo=tz.tzutc()) assert self.factory.get(d) == dt def test_one_arg_tzinfo(self): self.expected = ( datetime.utcnow() .replace(tzinfo=tz.tzutc()) .astimezone(tz.gettz("US/Pacific")) ) assert_datetime_equality( self.factory.get(tz.gettz("US/Pacific")), self.expected ) # regression test for issue #658 def test_one_arg_dateparser_datetime(self): dateparser = pytest.importorskip("dateparser") expected = datetime(1990, 1, 1).replace(tzinfo=tz.tzutc()) # dateparser outputs: datetime.datetime(1990, 1, 1, 0, 0, tzinfo=) parsed_date = dateparser.parse("1990-01-01T00:00:00+00:00") dt_output = self.factory.get(parsed_date)._datetime.replace(tzinfo=tz.tzutc()) assert dt_output == expected def test_kwarg_tzinfo(self): self.expected = ( datetime.utcnow() .replace(tzinfo=tz.tzutc()) .astimezone(tz.gettz("US/Pacific")) ) assert_datetime_equality( self.factory.get(tzinfo=tz.gettz("US/Pacific")), self.expected ) def test_kwarg_tzinfo_string(self): self.expected = ( datetime.utcnow() .replace(tzinfo=tz.tzutc()) .astimezone(tz.gettz("US/Pacific")) ) assert_datetime_equality(self.factory.get(tzinfo="US/Pacific"), self.expected) with pytest.raises(ParserError): self.factory.get(tzinfo="US/PacificInvalidTzinfo") def test_kwarg_normalize_whitespace(self): result = self.factory.get( "Jun 1 2005 1:33PM", "MMM D YYYY H:mmA", tzinfo=tz.tzutc(), normalize_whitespace=True, ) assert result._datetime == datetime(2005, 6, 1, 13, 33, tzinfo=tz.tzutc()) result = self.factory.get( "\t 2013-05-05T12:30:45.123456 \t \n", tzinfo=tz.tzutc(), normalize_whitespace=True, ) assert result._datetime == datetime( 2013, 5, 5, 12, 30, 45, 123456, tzinfo=tz.tzutc() ) # regression test for #944 def test_one_arg_datetime_tzinfo_kwarg(self): dt = datetime(2021, 4, 29, 6) result = self.factory.get(dt, tzinfo="America/Chicago") expected = datetime(2021, 4, 29, 6, tzinfo=tz.gettz("America/Chicago")) assert_datetime_equality(result._datetime, expected) def test_one_arg_arrow_tzinfo_kwarg(self): arw = Arrow(2021, 4, 29, 6) result = self.factory.get(arw, tzinfo="America/Chicago") expected = datetime(2021, 4, 29, 6, tzinfo=tz.gettz("America/Chicago")) assert_datetime_equality(result._datetime, expected) def test_one_arg_date_tzinfo_kwarg(self): da = date(2021, 4, 29) result = self.factory.get(da, tzinfo="America/Chicago") expected = Arrow(2021, 4, 29, tzinfo=tz.gettz("America/Chicago")) assert result.date() == expected.date() assert result.tzinfo == expected.tzinfo def test_one_arg_iso_calendar_tzinfo_kwarg(self): result = self.factory.get((2004, 1, 7), tzinfo="America/Chicago") expected = Arrow(2004, 1, 4, tzinfo="America/Chicago") assert_datetime_equality(result, expected) def test_one_arg_iso_str(self): dt = datetime.utcnow() assert_datetime_equality( self.factory.get(dt.isoformat()), dt.replace(tzinfo=tz.tzutc()) ) def test_one_arg_iso_calendar(self): pairs = [ (datetime(2004, 1, 4), (2004, 1, 7)), (datetime(2008, 12, 30), (2009, 1, 2)), (datetime(2010, 1, 2), (2009, 53, 6)), (datetime(2000, 2, 29), (2000, 9, 2)), (datetime(2005, 1, 1), (2004, 53, 6)), (datetime(2010, 1, 4), (2010, 1, 1)), (datetime(2010, 1, 3), (2009, 53, 7)), (datetime(2003, 12, 29), (2004, 1, 1)), ] for pair in pairs: dt, iso = pair assert self.factory.get(iso) == self.factory.get(dt) with pytest.raises(TypeError): self.factory.get((2014, 7, 1, 4)) with pytest.raises(TypeError): self.factory.get((2014, 7)) with pytest.raises(ValueError): self.factory.get((2014, 70, 1)) with pytest.raises(ValueError): self.factory.get((2014, 7, 10)) def test_one_arg_other(self): with pytest.raises(TypeError): self.factory.get(object()) def test_one_arg_bool(self): with pytest.raises(TypeError): self.factory.get(False) with pytest.raises(TypeError): self.factory.get(True) def test_one_arg_decimal(self): result = self.factory.get(Decimal(1577836800.26843)) assert result._datetime == datetime( 2020, 1, 1, 0, 0, 0, 268430, tzinfo=tz.tzutc() ) def test_two_args_datetime_tzinfo(self): result = self.factory.get(datetime(2013, 1, 1), tz.gettz("US/Pacific")) assert result._datetime == datetime(2013, 1, 1, tzinfo=tz.gettz("US/Pacific")) def test_two_args_datetime_tz_str(self): result = self.factory.get(datetime(2013, 1, 1), "US/Pacific") assert result._datetime == datetime(2013, 1, 1, tzinfo=tz.gettz("US/Pacific")) def test_two_args_date_tzinfo(self): result = self.factory.get(date(2013, 1, 1), tz.gettz("US/Pacific")) assert result._datetime == datetime(2013, 1, 1, tzinfo=tz.gettz("US/Pacific")) def test_two_args_date_tz_str(self): result = self.factory.get(date(2013, 1, 1), "US/Pacific") assert result._datetime == datetime(2013, 1, 1, tzinfo=tz.gettz("US/Pacific")) def test_two_args_datetime_other(self): with pytest.raises(TypeError): self.factory.get(datetime.utcnow(), object()) def test_two_args_date_other(self): with pytest.raises(TypeError): self.factory.get(date.today(), object()) def test_two_args_str_str(self): result = self.factory.get("2013-01-01", "YYYY-MM-DD") assert result._datetime == datetime(2013, 1, 1, tzinfo=tz.tzutc()) def test_two_args_str_tzinfo(self): result = self.factory.get("2013-01-01", tzinfo=tz.gettz("US/Pacific")) assert_datetime_equality( result._datetime, datetime(2013, 1, 1, tzinfo=tz.gettz("US/Pacific")) ) def test_two_args_twitter_format(self): # format returned by twitter API for created_at: twitter_date = "Fri Apr 08 21:08:54 +0000 2016" result = self.factory.get(twitter_date, "ddd MMM DD HH:mm:ss Z YYYY") assert result._datetime == datetime(2016, 4, 8, 21, 8, 54, tzinfo=tz.tzutc()) def test_two_args_str_list(self): result = self.factory.get("2013-01-01", ["MM/DD/YYYY", "YYYY-MM-DD"]) assert result._datetime == datetime(2013, 1, 1, tzinfo=tz.tzutc()) def test_two_args_unicode_unicode(self): result = self.factory.get("2013-01-01", "YYYY-MM-DD") assert result._datetime == datetime(2013, 1, 1, tzinfo=tz.tzutc()) def test_two_args_other(self): with pytest.raises(TypeError): self.factory.get(object(), object()) def test_three_args_with_tzinfo(self): timefmt = "YYYYMMDD" d = "20150514" assert self.factory.get(d, timefmt, tzinfo=tz.tzlocal()) == datetime( 2015, 5, 14, tzinfo=tz.tzlocal() ) def test_three_args(self): assert self.factory.get(2013, 1, 1) == datetime(2013, 1, 1, tzinfo=tz.tzutc()) def test_full_kwargs(self): assert ( self.factory.get( year=2016, month=7, day=14, hour=7, minute=16, second=45, microsecond=631092, ) == datetime(2016, 7, 14, 7, 16, 45, 631092, tzinfo=tz.tzutc()) ) def test_three_kwargs(self): assert self.factory.get(year=2016, month=7, day=14) == datetime( 2016, 7, 14, 0, 0, tzinfo=tz.tzutc() ) def test_tzinfo_string_kwargs(self): result = self.factory.get("2019072807", "YYYYMMDDHH", tzinfo="UTC") assert result._datetime == datetime(2019, 7, 28, 7, 0, 0, 0, tzinfo=tz.tzutc()) def test_insufficient_kwargs(self): with pytest.raises(TypeError): self.factory.get(year=2016) with pytest.raises(TypeError): self.factory.get(year=2016, month=7) def test_locale(self): result = self.factory.get("2010", "YYYY", locale="ja") assert result._datetime == datetime(2010, 1, 1, 0, 0, 0, 0, tzinfo=tz.tzutc()) # regression test for issue #701 result = self.factory.get( "Montag, 9. September 2019, 16:15-20:00", "dddd, D. MMMM YYYY", locale="de" ) assert result._datetime == datetime(2019, 9, 9, 0, 0, 0, 0, tzinfo=tz.tzutc()) def test_locale_kwarg_only(self): res = self.factory.get(locale="ja") assert res.tzinfo == tz.tzutc() def test_locale_with_tzinfo(self): res = self.factory.get(locale="ja", tzinfo=tz.gettz("Asia/Tokyo")) assert res.tzinfo == tz.gettz("Asia/Tokyo") @pytest.mark.usefixtures("arrow_factory") class TestUtcNow: def test_utcnow(self): assert_datetime_equality( self.factory.utcnow()._datetime, datetime.utcnow().replace(tzinfo=tz.tzutc()), ) @pytest.mark.usefixtures("arrow_factory") class TestNow: def test_no_tz(self): assert_datetime_equality(self.factory.now(), datetime.now(tz.tzlocal())) def test_tzinfo(self): assert_datetime_equality( self.factory.now(tz.gettz("EST")), datetime.now(tz.gettz("EST")) ) def test_tz_str(self): assert_datetime_equality(self.factory.now("EST"), datetime.now(tz.gettz("EST"))) python-arrow-1.2.1/tests/test_formatter.py000066400000000000000000000220741415100123600207400ustar00rootroot00000000000000from datetime import datetime import pytest import pytz from dateutil import tz as dateutil_tz from arrow import ( FORMAT_ATOM, FORMAT_COOKIE, FORMAT_RFC822, FORMAT_RFC850, FORMAT_RFC1036, FORMAT_RFC1123, FORMAT_RFC2822, FORMAT_RFC3339, FORMAT_RSS, FORMAT_W3C, ) from .utils import make_full_tz_list @pytest.mark.usefixtures("arrow_formatter") class TestFormatterFormatToken: def test_format(self): dt = datetime(2013, 2, 5, 12, 32, 51) result = self.formatter.format(dt, "MM-DD-YYYY hh:mm:ss a") assert result == "02-05-2013 12:32:51 pm" def test_year(self): dt = datetime(2013, 1, 1) assert self.formatter._format_token(dt, "YYYY") == "2013" assert self.formatter._format_token(dt, "YY") == "13" def test_month(self): dt = datetime(2013, 1, 1) assert self.formatter._format_token(dt, "MMMM") == "January" assert self.formatter._format_token(dt, "MMM") == "Jan" assert self.formatter._format_token(dt, "MM") == "01" assert self.formatter._format_token(dt, "M") == "1" def test_day(self): dt = datetime(2013, 2, 1) assert self.formatter._format_token(dt, "DDDD") == "032" assert self.formatter._format_token(dt, "DDD") == "32" assert self.formatter._format_token(dt, "DD") == "01" assert self.formatter._format_token(dt, "D") == "1" assert self.formatter._format_token(dt, "Do") == "1st" assert self.formatter._format_token(dt, "dddd") == "Friday" assert self.formatter._format_token(dt, "ddd") == "Fri" assert self.formatter._format_token(dt, "d") == "5" def test_hour(self): dt = datetime(2013, 1, 1, 2) assert self.formatter._format_token(dt, "HH") == "02" assert self.formatter._format_token(dt, "H") == "2" dt = datetime(2013, 1, 1, 13) assert self.formatter._format_token(dt, "HH") == "13" assert self.formatter._format_token(dt, "H") == "13" dt = datetime(2013, 1, 1, 2) assert self.formatter._format_token(dt, "hh") == "02" assert self.formatter._format_token(dt, "h") == "2" dt = datetime(2013, 1, 1, 13) assert self.formatter._format_token(dt, "hh") == "01" assert self.formatter._format_token(dt, "h") == "1" # test that 12-hour time converts to '12' at midnight dt = datetime(2013, 1, 1, 0) assert self.formatter._format_token(dt, "hh") == "12" assert self.formatter._format_token(dt, "h") == "12" def test_minute(self): dt = datetime(2013, 1, 1, 0, 1) assert self.formatter._format_token(dt, "mm") == "01" assert self.formatter._format_token(dt, "m") == "1" def test_second(self): dt = datetime(2013, 1, 1, 0, 0, 1) assert self.formatter._format_token(dt, "ss") == "01" assert self.formatter._format_token(dt, "s") == "1" def test_sub_second(self): dt = datetime(2013, 1, 1, 0, 0, 0, 123456) assert self.formatter._format_token(dt, "SSSSSS") == "123456" assert self.formatter._format_token(dt, "SSSSS") == "12345" assert self.formatter._format_token(dt, "SSSS") == "1234" assert self.formatter._format_token(dt, "SSS") == "123" assert self.formatter._format_token(dt, "SS") == "12" assert self.formatter._format_token(dt, "S") == "1" dt = datetime(2013, 1, 1, 0, 0, 0, 2000) assert self.formatter._format_token(dt, "SSSSSS") == "002000" assert self.formatter._format_token(dt, "SSSSS") == "00200" assert self.formatter._format_token(dt, "SSSS") == "0020" assert self.formatter._format_token(dt, "SSS") == "002" assert self.formatter._format_token(dt, "SS") == "00" assert self.formatter._format_token(dt, "S") == "0" def test_timestamp(self): dt = datetime.now(tz=dateutil_tz.UTC) expected = str(dt.timestamp()) assert self.formatter._format_token(dt, "X") == expected # Must round because time.time() may return a float with greater # than 6 digits of precision expected = str(int(dt.timestamp() * 1000000)) assert self.formatter._format_token(dt, "x") == expected def test_timezone(self): dt = datetime.utcnow().replace(tzinfo=dateutil_tz.gettz("US/Pacific")) result = self.formatter._format_token(dt, "ZZ") assert result == "-07:00" or result == "-08:00" result = self.formatter._format_token(dt, "Z") assert result == "-0700" or result == "-0800" @pytest.mark.parametrize("full_tz_name", make_full_tz_list()) def test_timezone_formatter(self, full_tz_name): # This test will fail if we use "now" as date as soon as we change from/to DST dt = datetime(1986, 2, 14, tzinfo=pytz.timezone("UTC")).replace( tzinfo=dateutil_tz.gettz(full_tz_name) ) abbreviation = dt.tzname() result = self.formatter._format_token(dt, "ZZZ") assert result == abbreviation def test_am_pm(self): dt = datetime(2012, 1, 1, 11) assert self.formatter._format_token(dt, "a") == "am" assert self.formatter._format_token(dt, "A") == "AM" dt = datetime(2012, 1, 1, 13) assert self.formatter._format_token(dt, "a") == "pm" assert self.formatter._format_token(dt, "A") == "PM" def test_week(self): dt = datetime(2017, 5, 19) assert self.formatter._format_token(dt, "W") == "2017-W20-5" # make sure week is zero padded when needed dt_early = datetime(2011, 1, 20) assert self.formatter._format_token(dt_early, "W") == "2011-W03-4" def test_nonsense(self): dt = datetime(2012, 1, 1, 11) assert self.formatter._format_token(dt, None) is None assert self.formatter._format_token(dt, "NONSENSE") is None def test_escape(self): assert ( self.formatter.format( datetime(2015, 12, 10, 17, 9), "MMMM D, YYYY [at] h:mma" ) == "December 10, 2015 at 5:09pm" ) assert ( self.formatter.format( datetime(2015, 12, 10, 17, 9), "[MMMM] M D, YYYY [at] h:mma" ) == "MMMM 12 10, 2015 at 5:09pm" ) assert ( self.formatter.format( datetime(1990, 11, 25), "[It happened on] MMMM Do [in the year] YYYY [a long time ago]", ) == "It happened on November 25th in the year 1990 a long time ago" ) assert ( self.formatter.format( datetime(1990, 11, 25), "[It happened on] MMMM Do [in the][ year] YYYY [a long time ago]", ) == "It happened on November 25th in the year 1990 a long time ago" ) assert ( self.formatter.format( datetime(1, 1, 1), "[I'm][ entirely][ escaped,][ weee!]" ) == "I'm entirely escaped, weee!" ) # Special RegEx characters assert ( self.formatter.format( datetime(2017, 12, 31, 2, 0), "MMM DD, YYYY |^${}().*+?<>-& h:mm A" ) == "Dec 31, 2017 |^${}().*+?<>-& 2:00 AM" ) # Escaping is atomic: brackets inside brackets are treated literally assert self.formatter.format(datetime(1, 1, 1), "[[[ ]]") == "[[ ]" @pytest.mark.usefixtures("arrow_formatter", "time_1975_12_25") class TestFormatterBuiltinFormats: def test_atom(self): assert ( self.formatter.format(self.datetime, FORMAT_ATOM) == "1975-12-25 14:15:16-05:00" ) def test_cookie(self): assert ( self.formatter.format(self.datetime, FORMAT_COOKIE) == "Thursday, 25-Dec-1975 14:15:16 EST" ) def test_rfc_822(self): assert ( self.formatter.format(self.datetime, FORMAT_RFC822) == "Thu, 25 Dec 75 14:15:16 -0500" ) def test_rfc_850(self): assert ( self.formatter.format(self.datetime, FORMAT_RFC850) == "Thursday, 25-Dec-75 14:15:16 EST" ) def test_rfc_1036(self): assert ( self.formatter.format(self.datetime, FORMAT_RFC1036) == "Thu, 25 Dec 75 14:15:16 -0500" ) def test_rfc_1123(self): assert ( self.formatter.format(self.datetime, FORMAT_RFC1123) == "Thu, 25 Dec 1975 14:15:16 -0500" ) def test_rfc_2822(self): assert ( self.formatter.format(self.datetime, FORMAT_RFC2822) == "Thu, 25 Dec 1975 14:15:16 -0500" ) def test_rfc3339(self): assert ( self.formatter.format(self.datetime, FORMAT_RFC3339) == "1975-12-25 14:15:16-05:00" ) def test_rss(self): assert ( self.formatter.format(self.datetime, FORMAT_RSS) == "Thu, 25 Dec 1975 14:15:16 -0500" ) def test_w3c(self): assert ( self.formatter.format(self.datetime, FORMAT_W3C) == "1975-12-25 14:15:16-05:00" ) python-arrow-1.2.1/tests/test_locales.py000066400000000000000000003267221415100123600203660ustar00rootroot00000000000000import pytest from arrow import arrow, locales @pytest.mark.usefixtures("lang_locales") class TestLocaleValidation: """Validate locales to ensure that translations are valid and complete""" def test_locale_validation(self): for locale_cls in self.locales.values(): # 7 days + 1 spacer to allow for 1-indexing of months assert len(locale_cls.day_names) == 8 assert locale_cls.day_names[0] == "" # ensure that all string from index 1 onward are valid (not blank or None) assert all(locale_cls.day_names[1:]) assert len(locale_cls.day_abbreviations) == 8 assert locale_cls.day_abbreviations[0] == "" assert all(locale_cls.day_abbreviations[1:]) # 12 months + 1 spacer to allow for 1-indexing of months assert len(locale_cls.month_names) == 13 assert locale_cls.month_names[0] == "" assert all(locale_cls.month_names[1:]) assert len(locale_cls.month_abbreviations) == 13 assert locale_cls.month_abbreviations[0] == "" assert all(locale_cls.month_abbreviations[1:]) assert len(locale_cls.names) > 0 assert locale_cls.past is not None assert locale_cls.future is not None def test_locale_name_validation(self): for locale_cls in self.locales.values(): for locale_name in locale_cls.names: assert len(locale_name) == 2 or len(locale_name) == 5 assert locale_name.islower() # Not a two-letter code if len(locale_name) > 2: assert "-" in locale_name assert locale_name.count("-") == 1 def test_duplicated_locale_name(self): with pytest.raises(LookupError): class Locale1(locales.Locale): names = ["en-us"] class TestModule: def test_get_locale(self, mocker): mock_locale = mocker.Mock() mock_locale_cls = mocker.Mock() mock_locale_cls.return_value = mock_locale with pytest.raises(ValueError): arrow.locales.get_locale("locale-name") cls_dict = arrow.locales._locale_map mocker.patch.dict(cls_dict, {"locale-name": mock_locale_cls}) result = arrow.locales.get_locale("locale_name") assert result == mock_locale # Capitalization and hyphenation should still yield the same locale result = arrow.locales.get_locale("locale-name") assert result == mock_locale result = arrow.locales.get_locale("locale-NAME") assert result == mock_locale def test_get_locale_by_class_name(self, mocker): mock_locale_cls = mocker.Mock() mock_locale_obj = mock_locale_cls.return_value = mocker.Mock() globals_fn = mocker.Mock() globals_fn.return_value = {"NonExistentLocale": mock_locale_cls} with pytest.raises(ValueError): arrow.locales.get_locale_by_class_name("NonExistentLocale") mocker.patch.object(locales, "globals", globals_fn) result = arrow.locales.get_locale_by_class_name("NonExistentLocale") mock_locale_cls.assert_called_once_with() assert result == mock_locale_obj def test_locales(self): assert len(locales._locale_map) > 0 class TestCustomLocale: def test_custom_locale_subclass(self): class CustomLocale1(locales.Locale): names = ["foo", "foo-BAR"] assert locales.get_locale("foo") is not None assert locales.get_locale("foo-BAR") is not None assert locales.get_locale("foo_bar") is not None class CustomLocale2(locales.Locale): names = ["underscores_ok"] assert locales.get_locale("underscores_ok") is not None @pytest.mark.usefixtures("lang_locale") class TestEnglishLocale: def test_describe(self): assert self.locale.describe("now", only_distance=True) == "instantly" assert self.locale.describe("now", only_distance=False) == "just now" def test_format_timeframe(self): assert self.locale._format_timeframe("hours", 2) == "2 hours" assert self.locale._format_timeframe("hour", 0) == "an hour" def test_format_relative_now(self): result = self.locale._format_relative("just now", "now", 0) assert result == "just now" def test_format_relative_past(self): result = self.locale._format_relative("an hour", "hour", 1) assert result == "in an hour" def test_format_relative_future(self): result = self.locale._format_relative("an hour", "hour", -1) assert result == "an hour ago" def test_ordinal_number(self): assert self.locale.ordinal_number(0) == "0th" assert self.locale.ordinal_number(1) == "1st" assert self.locale.ordinal_number(2) == "2nd" assert self.locale.ordinal_number(3) == "3rd" assert self.locale.ordinal_number(4) == "4th" assert self.locale.ordinal_number(10) == "10th" assert self.locale.ordinal_number(11) == "11th" assert self.locale.ordinal_number(12) == "12th" assert self.locale.ordinal_number(13) == "13th" assert self.locale.ordinal_number(14) == "14th" assert self.locale.ordinal_number(21) == "21st" assert self.locale.ordinal_number(22) == "22nd" assert self.locale.ordinal_number(23) == "23rd" assert self.locale.ordinal_number(24) == "24th" assert self.locale.ordinal_number(100) == "100th" assert self.locale.ordinal_number(101) == "101st" assert self.locale.ordinal_number(102) == "102nd" assert self.locale.ordinal_number(103) == "103rd" assert self.locale.ordinal_number(104) == "104th" assert self.locale.ordinal_number(110) == "110th" assert self.locale.ordinal_number(111) == "111th" assert self.locale.ordinal_number(112) == "112th" assert self.locale.ordinal_number(113) == "113th" assert self.locale.ordinal_number(114) == "114th" assert self.locale.ordinal_number(121) == "121st" assert self.locale.ordinal_number(122) == "122nd" assert self.locale.ordinal_number(123) == "123rd" assert self.locale.ordinal_number(124) == "124th" def test_meridian_invalid_token(self): assert self.locale.meridian(7, None) is None assert self.locale.meridian(7, "B") is None assert self.locale.meridian(7, "NONSENSE") is None @pytest.mark.usefixtures("lang_locale") class TestItalianLocale: def test_ordinal_number(self): assert self.locale.ordinal_number(1) == "1º" @pytest.mark.usefixtures("lang_locale") class TestSpanishLocale: def test_ordinal_number(self): assert self.locale.ordinal_number(1) == "1º" def test_format_timeframe(self): assert self.locale._format_timeframe("now", 0) == "ahora" assert self.locale._format_timeframe("seconds", 1) == "1 segundos" assert self.locale._format_timeframe("seconds", 3) == "3 segundos" assert self.locale._format_timeframe("seconds", 30) == "30 segundos" assert self.locale._format_timeframe("minute", 1) == "un minuto" assert self.locale._format_timeframe("minutes", 4) == "4 minutos" assert self.locale._format_timeframe("minutes", 40) == "40 minutos" assert self.locale._format_timeframe("hour", 1) == "una hora" assert self.locale._format_timeframe("hours", 5) == "5 horas" assert self.locale._format_timeframe("hours", 23) == "23 horas" assert self.locale._format_timeframe("day", 1) == "un día" assert self.locale._format_timeframe("days", 6) == "6 días" assert self.locale._format_timeframe("days", 12) == "12 días" assert self.locale._format_timeframe("week", 1) == "una semana" assert self.locale._format_timeframe("weeks", 2) == "2 semanas" assert self.locale._format_timeframe("weeks", 3) == "3 semanas" assert self.locale._format_timeframe("month", 1) == "un mes" assert self.locale._format_timeframe("months", 7) == "7 meses" assert self.locale._format_timeframe("months", 11) == "11 meses" assert self.locale._format_timeframe("year", 1) == "un año" assert self.locale._format_timeframe("years", 8) == "8 años" assert self.locale._format_timeframe("years", 12) == "12 años" assert self.locale._format_timeframe("now", 0) == "ahora" assert self.locale._format_timeframe("seconds", -1) == "1 segundos" assert self.locale._format_timeframe("seconds", -9) == "9 segundos" assert self.locale._format_timeframe("seconds", -12) == "12 segundos" assert self.locale._format_timeframe("minute", -1) == "un minuto" assert self.locale._format_timeframe("minutes", -2) == "2 minutos" assert self.locale._format_timeframe("minutes", -10) == "10 minutos" assert self.locale._format_timeframe("hour", -1) == "una hora" assert self.locale._format_timeframe("hours", -3) == "3 horas" assert self.locale._format_timeframe("hours", -11) == "11 horas" assert self.locale._format_timeframe("day", -1) == "un día" assert self.locale._format_timeframe("days", -2) == "2 días" assert self.locale._format_timeframe("days", -12) == "12 días" assert self.locale._format_timeframe("week", -1) == "una semana" assert self.locale._format_timeframe("weeks", -2) == "2 semanas" assert self.locale._format_timeframe("weeks", -3) == "3 semanas" assert self.locale._format_timeframe("month", -1) == "un mes" assert self.locale._format_timeframe("months", -3) == "3 meses" assert self.locale._format_timeframe("months", -13) == "13 meses" assert self.locale._format_timeframe("year", -1) == "un año" assert self.locale._format_timeframe("years", -4) == "4 años" assert self.locale._format_timeframe("years", -14) == "14 años" @pytest.mark.usefixtures("lang_locale") class TestFrenchLocale: def test_ordinal_number(self): assert self.locale.ordinal_number(1) == "1er" assert self.locale.ordinal_number(2) == "2e" def test_month_abbreviation(self): assert "juil" in self.locale.month_abbreviations @pytest.mark.usefixtures("lang_locale") class TestFrenchCanadianLocale: def test_month_abbreviation(self): assert "juill" in self.locale.month_abbreviations @pytest.mark.usefixtures("lang_locale") class TestRussianLocale: def test_plurals2(self): assert self.locale._format_timeframe("hours", 0) == "0 часов" assert self.locale._format_timeframe("hours", 1) == "1 час" assert self.locale._format_timeframe("hours", 2) == "2 часа" assert self.locale._format_timeframe("hours", 4) == "4 часа" assert self.locale._format_timeframe("hours", 5) == "5 часов" assert self.locale._format_timeframe("hours", 21) == "21 час" assert self.locale._format_timeframe("hours", 22) == "22 часа" assert self.locale._format_timeframe("hours", 25) == "25 часов" # feminine grammatical gender should be tested separately assert self.locale._format_timeframe("minutes", 0) == "0 минут" assert self.locale._format_timeframe("minutes", 1) == "1 минуту" assert self.locale._format_timeframe("minutes", 2) == "2 минуты" assert self.locale._format_timeframe("minutes", 4) == "4 минуты" assert self.locale._format_timeframe("minutes", 5) == "5 минут" assert self.locale._format_timeframe("minutes", 21) == "21 минуту" assert self.locale._format_timeframe("minutes", 22) == "22 минуты" assert self.locale._format_timeframe("minutes", 25) == "25 минут" @pytest.mark.usefixtures("lang_locale") class TestPolishLocale: def test_plurals(self): assert self.locale._format_timeframe("seconds", 0) == "0 sekund" assert self.locale._format_timeframe("second", 1) == "sekundę" assert self.locale._format_timeframe("seconds", 2) == "2 sekundy" assert self.locale._format_timeframe("seconds", 5) == "5 sekund" assert self.locale._format_timeframe("seconds", 21) == "21 sekund" assert self.locale._format_timeframe("seconds", 22) == "22 sekundy" assert self.locale._format_timeframe("seconds", 25) == "25 sekund" assert self.locale._format_timeframe("minutes", 0) == "0 minut" assert self.locale._format_timeframe("minute", 1) == "minutę" assert self.locale._format_timeframe("minutes", 2) == "2 minuty" assert self.locale._format_timeframe("minutes", 5) == "5 minut" assert self.locale._format_timeframe("minutes", 21) == "21 minut" assert self.locale._format_timeframe("minutes", 22) == "22 minuty" assert self.locale._format_timeframe("minutes", 25) == "25 minut" assert self.locale._format_timeframe("hours", 0) == "0 godzin" assert self.locale._format_timeframe("hour", 1) == "godzinę" assert self.locale._format_timeframe("hours", 2) == "2 godziny" assert self.locale._format_timeframe("hours", 5) == "5 godzin" assert self.locale._format_timeframe("hours", 21) == "21 godzin" assert self.locale._format_timeframe("hours", 22) == "22 godziny" assert self.locale._format_timeframe("hours", 25) == "25 godzin" assert self.locale._format_timeframe("weeks", 0) == "0 tygodni" assert self.locale._format_timeframe("week", 1) == "tydzień" assert self.locale._format_timeframe("weeks", 2) == "2 tygodnie" assert self.locale._format_timeframe("weeks", 5) == "5 tygodni" assert self.locale._format_timeframe("weeks", 21) == "21 tygodni" assert self.locale._format_timeframe("weeks", 22) == "22 tygodnie" assert self.locale._format_timeframe("weeks", 25) == "25 tygodni" assert self.locale._format_timeframe("months", 0) == "0 miesięcy" assert self.locale._format_timeframe("month", 1) == "miesiąc" assert self.locale._format_timeframe("months", 2) == "2 miesiące" assert self.locale._format_timeframe("months", 5) == "5 miesięcy" assert self.locale._format_timeframe("months", 21) == "21 miesięcy" assert self.locale._format_timeframe("months", 22) == "22 miesiące" assert self.locale._format_timeframe("months", 25) == "25 miesięcy" assert self.locale._format_timeframe("years", 0) == "0 lat" assert self.locale._format_timeframe("year", 1) == "rok" assert self.locale._format_timeframe("years", 2) == "2 lata" assert self.locale._format_timeframe("years", 5) == "5 lat" assert self.locale._format_timeframe("years", 21) == "21 lat" assert self.locale._format_timeframe("years", 22) == "22 lata" assert self.locale._format_timeframe("years", 25) == "25 lat" @pytest.mark.usefixtures("lang_locale") class TestIcelandicLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("now", 0) == "rétt í þessu" assert self.locale._format_timeframe("second", -1) == "sekúndu" assert self.locale._format_timeframe("second", 1) == "sekúndu" assert self.locale._format_timeframe("minute", -1) == "einni mínútu" assert self.locale._format_timeframe("minute", 1) == "eina mínútu" assert self.locale._format_timeframe("minutes", -2) == "2 mínútum" assert self.locale._format_timeframe("minutes", 2) == "2 mínútur" assert self.locale._format_timeframe("hour", -1) == "einum tíma" assert self.locale._format_timeframe("hour", 1) == "einn tíma" assert self.locale._format_timeframe("hours", -2) == "2 tímum" assert self.locale._format_timeframe("hours", 2) == "2 tíma" assert self.locale._format_timeframe("day", -1) == "einum degi" assert self.locale._format_timeframe("day", 1) == "einn dag" assert self.locale._format_timeframe("days", -2) == "2 dögum" assert self.locale._format_timeframe("days", 2) == "2 daga" assert self.locale._format_timeframe("month", -1) == "einum mánuði" assert self.locale._format_timeframe("month", 1) == "einn mánuð" assert self.locale._format_timeframe("months", -2) == "2 mánuðum" assert self.locale._format_timeframe("months", 2) == "2 mánuði" assert self.locale._format_timeframe("year", -1) == "einu ári" assert self.locale._format_timeframe("year", 1) == "eitt ár" assert self.locale._format_timeframe("years", -2) == "2 árum" assert self.locale._format_timeframe("years", 2) == "2 ár" with pytest.raises(ValueError): self.locale._format_timeframe("years", 0) @pytest.mark.usefixtures("lang_locale") class TestMalayalamLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("hours", 2) == "2 മണിക്കൂർ" assert self.locale._format_timeframe("hour", 0) == "ഒരു മണിക്കൂർ" def test_format_relative_now(self): result = self.locale._format_relative("ഇപ്പോൾ", "now", 0) assert result == "ഇപ്പോൾ" def test_format_relative_past(self): result = self.locale._format_relative("ഒരു മണിക്കൂർ", "hour", 1) assert result == "ഒരു മണിക്കൂർ ശേഷം" def test_format_relative_future(self): result = self.locale._format_relative("ഒരു മണിക്കൂർ", "hour", -1) assert result == "ഒരു മണിക്കൂർ മുമ്പ്" @pytest.mark.usefixtures("lang_locale") class TestMalteseLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("now", 0) == "issa" assert self.locale._format_timeframe("second", 1) == "sekonda" assert self.locale._format_timeframe("seconds", 30) == "30 sekondi" assert self.locale._format_timeframe("minute", 1) == "minuta" assert self.locale._format_timeframe("minutes", 4) == "4 minuti" assert self.locale._format_timeframe("hour", 1) == "siegħa" assert self.locale._format_timeframe("hours", 2) == "2 sagħtejn" assert self.locale._format_timeframe("hours", 4) == "4 sigħat" assert self.locale._format_timeframe("day", 1) == "jum" assert self.locale._format_timeframe("days", 2) == "2 jumejn" assert self.locale._format_timeframe("days", 5) == "5 ijiem" assert self.locale._format_timeframe("month", 1) == "xahar" assert self.locale._format_timeframe("months", 2) == "2 xahrejn" assert self.locale._format_timeframe("months", 7) == "7 xhur" assert self.locale._format_timeframe("year", 1) == "sena" assert self.locale._format_timeframe("years", 2) == "2 sentejn" assert self.locale._format_timeframe("years", 8) == "8 snin" def test_weekday(self): dt = arrow.Arrow(2015, 4, 11, 17, 30, 00) assert self.locale.day_name(dt.isoweekday()) == "Is-Sibt" assert self.locale.day_abbreviation(dt.isoweekday()) == "S" @pytest.mark.usefixtures("lang_locale") class TestHindiLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("hours", 2) == "2 घंटे" assert self.locale._format_timeframe("hour", 0) == "एक घंटा" def test_format_relative_now(self): result = self.locale._format_relative("अभी", "now", 0) assert result == "अभी" def test_format_relative_past(self): result = self.locale._format_relative("एक घंटा", "hour", 1) assert result == "एक घंटा बाद" def test_format_relative_future(self): result = self.locale._format_relative("एक घंटा", "hour", -1) assert result == "एक घंटा पहले" @pytest.mark.usefixtures("lang_locale") class TestCzechLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("hours", 2) == "2 hodiny" assert self.locale._format_timeframe("hours", 5) == "5 hodin" assert self.locale._format_timeframe("hour", 0) == "0 hodin" assert self.locale._format_timeframe("hours", -2) == "2 hodinami" assert self.locale._format_timeframe("hours", -5) == "5 hodinami" assert self.locale._format_timeframe("hour", 1) == "hodinu" assert self.locale._format_timeframe("now", 0) == "Teď" assert self.locale._format_timeframe("weeks", 2) == "2 týdny" assert self.locale._format_timeframe("weeks", 5) == "5 týdnů" assert self.locale._format_timeframe("week", 0) == "0 týdnů" assert self.locale._format_timeframe("weeks", -2) == "2 týdny" assert self.locale._format_timeframe("weeks", -5) == "5 týdny" def test_format_relative_now(self): result = self.locale._format_relative("Teď", "now", 0) assert result == "Teď" def test_format_relative_future(self): result = self.locale._format_relative("hodinu", "hour", 1) assert result == "Za hodinu" def test_format_relative_past(self): result = self.locale._format_relative("hodinou", "hour", -1) assert result == "Před hodinou" @pytest.mark.usefixtures("lang_locale") class TestSlovakLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("seconds", -5) == "5 sekundami" assert self.locale._format_timeframe("seconds", -2) == "2 sekundami" assert self.locale._format_timeframe("second", -1) == "sekundou" assert self.locale._format_timeframe("second", 0) == "0 sekúnd" assert self.locale._format_timeframe("second", 1) == "sekundu" assert self.locale._format_timeframe("seconds", 2) == "2 sekundy" assert self.locale._format_timeframe("seconds", 5) == "5 sekúnd" assert self.locale._format_timeframe("minutes", -5) == "5 minútami" assert self.locale._format_timeframe("minutes", -2) == "2 minútami" assert self.locale._format_timeframe("minute", -1) == "minútou" assert self.locale._format_timeframe("minute", 0) == "0 minút" assert self.locale._format_timeframe("minute", 1) == "minútu" assert self.locale._format_timeframe("minutes", 2) == "2 minúty" assert self.locale._format_timeframe("minutes", 5) == "5 minút" assert self.locale._format_timeframe("hours", -5) == "5 hodinami" assert self.locale._format_timeframe("hours", -2) == "2 hodinami" assert self.locale._format_timeframe("hour", -1) == "hodinou" assert self.locale._format_timeframe("hour", 0) == "0 hodín" assert self.locale._format_timeframe("hour", 1) == "hodinu" assert self.locale._format_timeframe("hours", 2) == "2 hodiny" assert self.locale._format_timeframe("hours", 5) == "5 hodín" assert self.locale._format_timeframe("days", -5) == "5 dňami" assert self.locale._format_timeframe("days", -2) == "2 dňami" assert self.locale._format_timeframe("day", -1) == "dňom" assert self.locale._format_timeframe("day", 0) == "0 dní" assert self.locale._format_timeframe("day", 1) == "deň" assert self.locale._format_timeframe("days", 2) == "2 dni" assert self.locale._format_timeframe("days", 5) == "5 dní" assert self.locale._format_timeframe("weeks", -5) == "5 týždňami" assert self.locale._format_timeframe("weeks", -2) == "2 týždňami" assert self.locale._format_timeframe("week", -1) == "týždňom" assert self.locale._format_timeframe("week", 0) == "0 týždňov" assert self.locale._format_timeframe("week", 1) == "týždeň" assert self.locale._format_timeframe("weeks", 2) == "2 týždne" assert self.locale._format_timeframe("weeks", 5) == "5 týždňov" assert self.locale._format_timeframe("months", -5) == "5 mesiacmi" assert self.locale._format_timeframe("months", -2) == "2 mesiacmi" assert self.locale._format_timeframe("month", -1) == "mesiacom" assert self.locale._format_timeframe("month", 0) == "0 mesiacov" assert self.locale._format_timeframe("month", 1) == "mesiac" assert self.locale._format_timeframe("months", 2) == "2 mesiace" assert self.locale._format_timeframe("months", 5) == "5 mesiacov" assert self.locale._format_timeframe("years", -5) == "5 rokmi" assert self.locale._format_timeframe("years", -2) == "2 rokmi" assert self.locale._format_timeframe("year", -1) == "rokom" assert self.locale._format_timeframe("year", 0) == "0 rokov" assert self.locale._format_timeframe("year", 1) == "rok" assert self.locale._format_timeframe("years", 2) == "2 roky" assert self.locale._format_timeframe("years", 5) == "5 rokov" assert self.locale._format_timeframe("now", 0) == "Teraz" def test_format_relative_now(self): result = self.locale._format_relative("Teraz", "now", 0) assert result == "Teraz" def test_format_relative_future(self): result = self.locale._format_relative("hodinu", "hour", 1) assert result == "O hodinu" def test_format_relative_past(self): result = self.locale._format_relative("hodinou", "hour", -1) assert result == "Pred hodinou" @pytest.mark.usefixtures("lang_locale") class TestBulgarianLocale: def test_plurals2(self): assert self.locale._format_timeframe("hours", 0) == "0 часа" assert self.locale._format_timeframe("hours", 1) == "1 час" assert self.locale._format_timeframe("hours", 2) == "2 часа" assert self.locale._format_timeframe("hours", 4) == "4 часа" assert self.locale._format_timeframe("hours", 5) == "5 часа" assert self.locale._format_timeframe("hours", 21) == "21 час" assert self.locale._format_timeframe("hours", 22) == "22 часа" assert self.locale._format_timeframe("hours", 25) == "25 часа" # feminine grammatical gender should be tested separately assert self.locale._format_timeframe("minutes", 0) == "0 минути" assert self.locale._format_timeframe("minutes", 1) == "1 минута" assert self.locale._format_timeframe("minutes", 2) == "2 минути" assert self.locale._format_timeframe("minutes", 4) == "4 минути" assert self.locale._format_timeframe("minutes", 5) == "5 минути" assert self.locale._format_timeframe("minutes", 21) == "21 минута" assert self.locale._format_timeframe("minutes", 22) == "22 минути" assert self.locale._format_timeframe("minutes", 25) == "25 минути" @pytest.mark.usefixtures("lang_locale") class TestMacedonianLocale: def test_singles_mk(self): assert self.locale._format_timeframe("second", 1) == "една секунда" assert self.locale._format_timeframe("minute", 1) == "една минута" assert self.locale._format_timeframe("hour", 1) == "еден саат" assert self.locale._format_timeframe("day", 1) == "еден ден" assert self.locale._format_timeframe("week", 1) == "една недела" assert self.locale._format_timeframe("month", 1) == "еден месец" assert self.locale._format_timeframe("year", 1) == "една година" def test_meridians_mk(self): assert self.locale.meridian(7, "A") == "претпладне" assert self.locale.meridian(18, "A") == "попладне" assert self.locale.meridian(10, "a") == "дп" assert self.locale.meridian(22, "a") == "пп" def test_describe_mk(self): assert self.locale.describe("second", only_distance=True) == "една секунда" assert self.locale.describe("second", only_distance=False) == "за една секунда" assert self.locale.describe("minute", only_distance=True) == "една минута" assert self.locale.describe("minute", only_distance=False) == "за една минута" assert self.locale.describe("hour", only_distance=True) == "еден саат" assert self.locale.describe("hour", only_distance=False) == "за еден саат" assert self.locale.describe("day", only_distance=True) == "еден ден" assert self.locale.describe("day", only_distance=False) == "за еден ден" assert self.locale.describe("week", only_distance=True) == "една недела" assert self.locale.describe("week", only_distance=False) == "за една недела" assert self.locale.describe("month", only_distance=True) == "еден месец" assert self.locale.describe("month", only_distance=False) == "за еден месец" assert self.locale.describe("year", only_distance=True) == "една година" assert self.locale.describe("year", only_distance=False) == "за една година" def test_relative_mk(self): # time assert self.locale._format_relative("сега", "now", 0) == "сега" assert self.locale._format_relative("1 секунда", "seconds", 1) == "за 1 секунда" assert self.locale._format_relative("1 минута", "minutes", 1) == "за 1 минута" assert self.locale._format_relative("1 саат", "hours", 1) == "за 1 саат" assert self.locale._format_relative("1 ден", "days", 1) == "за 1 ден" assert self.locale._format_relative("1 недела", "weeks", 1) == "за 1 недела" assert self.locale._format_relative("1 месец", "months", 1) == "за 1 месец" assert self.locale._format_relative("1 година", "years", 1) == "за 1 година" assert ( self.locale._format_relative("1 секунда", "seconds", -1) == "пред 1 секунда" ) assert ( self.locale._format_relative("1 минута", "minutes", -1) == "пред 1 минута" ) assert self.locale._format_relative("1 саат", "hours", -1) == "пред 1 саат" assert self.locale._format_relative("1 ден", "days", -1) == "пред 1 ден" assert self.locale._format_relative("1 недела", "weeks", -1) == "пред 1 недела" assert self.locale._format_relative("1 месец", "months", -1) == "пред 1 месец" assert self.locale._format_relative("1 година", "years", -1) == "пред 1 година" def test_plurals_mk(self): # Seconds assert self.locale._format_timeframe("seconds", 0) == "0 секунди" assert self.locale._format_timeframe("seconds", 1) == "1 секунда" assert self.locale._format_timeframe("seconds", 2) == "2 секунди" assert self.locale._format_timeframe("seconds", 4) == "4 секунди" assert self.locale._format_timeframe("seconds", 5) == "5 секунди" assert self.locale._format_timeframe("seconds", 21) == "21 секунда" assert self.locale._format_timeframe("seconds", 22) == "22 секунди" assert self.locale._format_timeframe("seconds", 25) == "25 секунди" # Minutes assert self.locale._format_timeframe("minutes", 0) == "0 минути" assert self.locale._format_timeframe("minutes", 1) == "1 минута" assert self.locale._format_timeframe("minutes", 2) == "2 минути" assert self.locale._format_timeframe("minutes", 4) == "4 минути" assert self.locale._format_timeframe("minutes", 5) == "5 минути" assert self.locale._format_timeframe("minutes", 21) == "21 минута" assert self.locale._format_timeframe("minutes", 22) == "22 минути" assert self.locale._format_timeframe("minutes", 25) == "25 минути" # Hours assert self.locale._format_timeframe("hours", 0) == "0 саати" assert self.locale._format_timeframe("hours", 1) == "1 саат" assert self.locale._format_timeframe("hours", 2) == "2 саати" assert self.locale._format_timeframe("hours", 4) == "4 саати" assert self.locale._format_timeframe("hours", 5) == "5 саати" assert self.locale._format_timeframe("hours", 21) == "21 саат" assert self.locale._format_timeframe("hours", 22) == "22 саати" assert self.locale._format_timeframe("hours", 25) == "25 саати" # Days assert self.locale._format_timeframe("days", 0) == "0 дена" assert self.locale._format_timeframe("days", 1) == "1 ден" assert self.locale._format_timeframe("days", 2) == "2 дена" assert self.locale._format_timeframe("days", 3) == "3 дена" assert self.locale._format_timeframe("days", 21) == "21 ден" # Weeks assert self.locale._format_timeframe("weeks", 0) == "0 недели" assert self.locale._format_timeframe("weeks", 1) == "1 недела" assert self.locale._format_timeframe("weeks", 2) == "2 недели" assert self.locale._format_timeframe("weeks", 4) == "4 недели" assert self.locale._format_timeframe("weeks", 5) == "5 недели" assert self.locale._format_timeframe("weeks", 21) == "21 недела" assert self.locale._format_timeframe("weeks", 22) == "22 недели" assert self.locale._format_timeframe("weeks", 25) == "25 недели" # Months assert self.locale._format_timeframe("months", 0) == "0 месеци" assert self.locale._format_timeframe("months", 1) == "1 месец" assert self.locale._format_timeframe("months", 2) == "2 месеци" assert self.locale._format_timeframe("months", 4) == "4 месеци" assert self.locale._format_timeframe("months", 5) == "5 месеци" assert self.locale._format_timeframe("months", 21) == "21 месец" assert self.locale._format_timeframe("months", 22) == "22 месеци" assert self.locale._format_timeframe("months", 25) == "25 месеци" # Years assert self.locale._format_timeframe("years", 1) == "1 година" assert self.locale._format_timeframe("years", 2) == "2 години" assert self.locale._format_timeframe("years", 5) == "5 години" def test_multi_describe_mk(self): describe = self.locale.describe_multi fulltest = [("years", 5), ("weeks", 1), ("hours", 1), ("minutes", 6)] assert describe(fulltest) == "за 5 години 1 недела 1 саат 6 минути" seconds4000_0days = [("days", 0), ("hours", 1), ("minutes", 6)] assert describe(seconds4000_0days) == "за 0 дена 1 саат 6 минути" seconds4000 = [("hours", 1), ("minutes", 6)] assert describe(seconds4000) == "за 1 саат 6 минути" assert describe(seconds4000, only_distance=True) == "1 саат 6 минути" seconds3700 = [("hours", 1), ("minutes", 1)] assert describe(seconds3700) == "за 1 саат 1 минута" seconds300_0hours = [("hours", 0), ("minutes", 5)] assert describe(seconds300_0hours) == "за 0 саати 5 минути" seconds300 = [("minutes", 5)] assert describe(seconds300) == "за 5 минути" seconds60 = [("minutes", 1)] assert describe(seconds60) == "за 1 минута" assert describe(seconds60, only_distance=True) == "1 минута" seconds60 = [("seconds", 1)] assert describe(seconds60) == "за 1 секунда" assert describe(seconds60, only_distance=True) == "1 секунда" @pytest.mark.usefixtures("time_2013_01_01") @pytest.mark.usefixtures("lang_locale") class TestHebrewLocale: def test_couple_of_timeframe(self): assert self.locale._format_timeframe("day", 1) == "יום" assert self.locale._format_timeframe("days", 2) == "יומיים" assert self.locale._format_timeframe("days", 3) == "3 ימים" assert self.locale._format_timeframe("days", 80) == "80 יום" assert self.locale._format_timeframe("hour", 1) == "שעה" assert self.locale._format_timeframe("hours", 2) == "שעתיים" assert self.locale._format_timeframe("hours", 3) == "3 שעות" assert self.locale._format_timeframe("week", 1) == "שבוע" assert self.locale._format_timeframe("weeks", 2) == "שבועיים" assert self.locale._format_timeframe("weeks", 3) == "3 שבועות" assert self.locale._format_timeframe("month", 1) == "חודש" assert self.locale._format_timeframe("months", 2) == "חודשיים" assert self.locale._format_timeframe("months", 4) == "4 חודשים" assert self.locale._format_timeframe("year", 1) == "שנה" assert self.locale._format_timeframe("years", 2) == "שנתיים" assert self.locale._format_timeframe("years", 5) == "5 שנים" assert self.locale._format_timeframe("years", 15) == "15 שנה" def test_describe_multi(self): describe = self.locale.describe_multi fulltest = [("years", 5), ("week", 1), ("hour", 1), ("minutes", 6)] assert describe(fulltest) == "בעוד 5 שנים, שבוע, שעה ו־6 דקות" seconds4000_0days = [("days", 0), ("hour", 1), ("minutes", 6)] assert describe(seconds4000_0days) == "בעוד 0 ימים, שעה ו־6 דקות" seconds4000 = [("hour", 1), ("minutes", 6)] assert describe(seconds4000) == "בעוד שעה ו־6 דקות" assert describe(seconds4000, only_distance=True) == "שעה ו־6 דקות" seconds3700 = [("hour", 1), ("minute", 1)] assert describe(seconds3700) == "בעוד שעה ודקה" seconds300_0hours = [("hours", 0), ("minutes", 5)] assert describe(seconds300_0hours) == "בעוד 0 שעות ו־5 דקות" seconds300 = [("minutes", 5)] assert describe(seconds300) == "בעוד 5 דקות" seconds60 = [("minute", 1)] assert describe(seconds60) == "בעוד דקה" assert describe(seconds60, only_distance=True) == "דקה" @pytest.mark.usefixtures("lang_locale") class TestAzerbaijaniLocale: def test_singles_mk(self): assert self.locale._format_timeframe("second", 1) == "bir saniyə" assert self.locale._format_timeframe("minute", 1) == "bir dəqiqə" assert self.locale._format_timeframe("hour", 1) == "bir saat" assert self.locale._format_timeframe("day", 1) == "bir gün" assert self.locale._format_timeframe("week", 1) == "bir həftə" assert self.locale._format_timeframe("month", 1) == "bir ay" assert self.locale._format_timeframe("year", 1) == "bir il" def test_describe_mk(self): assert self.locale.describe("second", only_distance=True) == "bir saniyə" assert self.locale.describe("second", only_distance=False) == "bir saniyə sonra" assert self.locale.describe("minute", only_distance=True) == "bir dəqiqə" assert self.locale.describe("minute", only_distance=False) == "bir dəqiqə sonra" assert self.locale.describe("hour", only_distance=True) == "bir saat" assert self.locale.describe("hour", only_distance=False) == "bir saat sonra" assert self.locale.describe("day", only_distance=True) == "bir gün" assert self.locale.describe("day", only_distance=False) == "bir gün sonra" assert self.locale.describe("week", only_distance=True) == "bir həftə" assert self.locale.describe("week", only_distance=False) == "bir həftə sonra" assert self.locale.describe("month", only_distance=True) == "bir ay" assert self.locale.describe("month", only_distance=False) == "bir ay sonra" assert self.locale.describe("year", only_distance=True) == "bir il" assert self.locale.describe("year", only_distance=False) == "bir il sonra" def test_relative_mk(self): assert self.locale._format_relative("indi", "now", 0) == "indi" assert ( self.locale._format_relative("1 saniyə", "seconds", 1) == "1 saniyə sonra" ) assert ( self.locale._format_relative("1 saniyə", "seconds", -1) == "1 saniyə əvvəl" ) assert ( self.locale._format_relative("1 dəqiqə", "minutes", 1) == "1 dəqiqə sonra" ) assert ( self.locale._format_relative("1 dəqiqə", "minutes", -1) == "1 dəqiqə əvvəl" ) assert self.locale._format_relative("1 saat", "hours", 1) == "1 saat sonra" assert self.locale._format_relative("1 saat", "hours", -1) == "1 saat əvvəl" assert self.locale._format_relative("1 gün", "days", 1) == "1 gün sonra" assert self.locale._format_relative("1 gün", "days", -1) == "1 gün əvvəl" assert self.locale._format_relative("1 hafta", "weeks", 1) == "1 hafta sonra" assert self.locale._format_relative("1 hafta", "weeks", -1) == "1 hafta əvvəl" assert self.locale._format_relative("1 ay", "months", 1) == "1 ay sonra" assert self.locale._format_relative("1 ay", "months", -1) == "1 ay əvvəl" assert self.locale._format_relative("1 il", "years", 1) == "1 il sonra" assert self.locale._format_relative("1 il", "years", -1) == "1 il əvvəl" def test_plurals_mk(self): assert self.locale._format_timeframe("now", 0) == "indi" assert self.locale._format_timeframe("second", 1) == "bir saniyə" assert self.locale._format_timeframe("seconds", 30) == "30 saniyə" assert self.locale._format_timeframe("minute", 1) == "bir dəqiqə" assert self.locale._format_timeframe("minutes", 40) == "40 dəqiqə" assert self.locale._format_timeframe("hour", 1) == "bir saat" assert self.locale._format_timeframe("hours", 23) == "23 saat" assert self.locale._format_timeframe("day", 1) == "bir gün" assert self.locale._format_timeframe("days", 12) == "12 gün" assert self.locale._format_timeframe("week", 1) == "bir həftə" assert self.locale._format_timeframe("weeks", 38) == "38 həftə" assert self.locale._format_timeframe("month", 1) == "bir ay" assert self.locale._format_timeframe("months", 11) == "11 ay" assert self.locale._format_timeframe("year", 1) == "bir il" assert self.locale._format_timeframe("years", 12) == "12 il" @pytest.mark.usefixtures("lang_locale") class TestMarathiLocale: def test_dateCoreFunctionality(self): dt = arrow.Arrow(2015, 4, 11, 17, 30, 00) assert self.locale.month_name(dt.month) == "एप्रिल" assert self.locale.month_abbreviation(dt.month) == "एप्रि" assert self.locale.day_name(dt.isoweekday()) == "शनिवार" assert self.locale.day_abbreviation(dt.isoweekday()) == "शनि" def test_format_timeframe(self): assert self.locale._format_timeframe("hours", 2) == "2 तास" assert self.locale._format_timeframe("hour", 0) == "एक तास" def test_format_relative_now(self): result = self.locale._format_relative("सद्य", "now", 0) assert result == "सद्य" def test_format_relative_past(self): result = self.locale._format_relative("एक तास", "hour", 1) assert result == "एक तास नंतर" def test_format_relative_future(self): result = self.locale._format_relative("एक तास", "hour", -1) assert result == "एक तास आधी" # Not currently implemented def test_ordinal_number(self): assert self.locale.ordinal_number(1) == "1" @pytest.mark.usefixtures("lang_locale") class TestFinnishLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("hours", -2) == "2 tuntia" assert self.locale._format_timeframe("hours", 2) == "2 tunnin" assert self.locale._format_timeframe("hour", -1) == "tunti" assert self.locale._format_timeframe("hour", 1) == "tunnin" assert self.locale._format_timeframe("now", 1) == "juuri nyt" def test_format_relative_now(self): result = self.locale._format_relative("juuri nyt", "now", 0) assert result == "juuri nyt" def test_format_relative_past(self): result = self.locale._format_relative("tunnin", "hour", 1) assert result == "tunnin kuluttua" def test_format_relative_future(self): result = self.locale._format_relative("tunti", "hour", -1) assert result == "tunti sitten" def test_ordinal_number(self): assert self.locale.ordinal_number(1) == "1." @pytest.mark.usefixtures("lang_locale") class TestGermanLocale: def test_ordinal_number(self): assert self.locale.ordinal_number(1) == "1." def test_define(self): assert self.locale.describe("minute", only_distance=True) == "eine Minute" assert self.locale.describe("minute", only_distance=False) == "in einer Minute" assert self.locale.describe("hour", only_distance=True) == "eine Stunde" assert self.locale.describe("hour", only_distance=False) == "in einer Stunde" assert self.locale.describe("day", only_distance=True) == "ein Tag" assert self.locale.describe("day", only_distance=False) == "in einem Tag" assert self.locale.describe("week", only_distance=True) == "eine Woche" assert self.locale.describe("week", only_distance=False) == "in einer Woche" assert self.locale.describe("month", only_distance=True) == "ein Monat" assert self.locale.describe("month", only_distance=False) == "in einem Monat" assert self.locale.describe("year", only_distance=True) == "ein Jahr" assert self.locale.describe("year", only_distance=False) == "in einem Jahr" def test_weekday(self): dt = arrow.Arrow(2015, 4, 11, 17, 30, 00) assert self.locale.day_name(dt.isoweekday()) == "Samstag" assert self.locale.day_abbreviation(dt.isoweekday()) == "Sa" @pytest.mark.usefixtures("lang_locale") class TestHungarianLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("hours", 2) == "2 óra" assert self.locale._format_timeframe("hour", 0) == "egy órával" assert self.locale._format_timeframe("hours", -2) == "2 órával" assert self.locale._format_timeframe("now", 0) == "éppen most" @pytest.mark.usefixtures("lang_locale") class TestEsperantoLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("hours", 2) == "2 horoj" assert self.locale._format_timeframe("hour", 0) == "un horo" assert self.locale._format_timeframe("hours", -2) == "2 horoj" assert self.locale._format_timeframe("now", 0) == "nun" def test_ordinal_number(self): assert self.locale.ordinal_number(1) == "1a" @pytest.mark.usefixtures("lang_locale") class TestThaiLocale: def test_year_full(self): assert self.locale.year_full(2015) == "2558" def test_year_abbreviation(self): assert self.locale.year_abbreviation(2015) == "58" def test_format_relative_now(self): result = self.locale._format_relative("ขณะนี้", "now", 0) assert result == "ขณะนี้" def test_format_relative_past(self): result = self.locale._format_relative("1 ชั่วโมง", "hour", 1) assert result == "ในอีก 1 ชั่วโมง" result = self.locale._format_relative("{0} ชั่วโมง", "hours", 2) assert result == "ในอีก {0} ชั่วโมง" result = self.locale._format_relative("ไม่กี่วินาที", "seconds", 42) assert result == "ในอีกไม่กี่วินาที" def test_format_relative_future(self): result = self.locale._format_relative("1 ชั่วโมง", "hour", -1) assert result == "1 ชั่วโมง ที่ผ่านมา" @pytest.mark.usefixtures("lang_locale") class TestBengaliLocale: def test_ordinal_number(self): assert self.locale._ordinal_number(0) == "0তম" assert self.locale._ordinal_number(1) == "1ম" assert self.locale._ordinal_number(3) == "3য়" assert self.locale._ordinal_number(4) == "4র্থ" assert self.locale._ordinal_number(5) == "5ম" assert self.locale._ordinal_number(6) == "6ষ্ঠ" assert self.locale._ordinal_number(10) == "10ম" assert self.locale._ordinal_number(11) == "11তম" assert self.locale._ordinal_number(42) == "42তম" assert self.locale._ordinal_number(-1) is None @pytest.mark.usefixtures("lang_locale") class TestRomanianLocale: def test_timeframes(self): assert self.locale._format_timeframe("hours", 2) == "2 ore" assert self.locale._format_timeframe("months", 2) == "2 luni" assert self.locale._format_timeframe("days", 2) == "2 zile" assert self.locale._format_timeframe("years", 2) == "2 ani" assert self.locale._format_timeframe("hours", 3) == "3 ore" assert self.locale._format_timeframe("months", 4) == "4 luni" assert self.locale._format_timeframe("days", 3) == "3 zile" assert self.locale._format_timeframe("years", 5) == "5 ani" def test_relative_timeframes(self): assert self.locale._format_relative("acum", "now", 0) == "acum" assert self.locale._format_relative("o oră", "hour", 1) == "peste o oră" assert self.locale._format_relative("o oră", "hour", -1) == "o oră în urmă" assert self.locale._format_relative("un minut", "minute", 1) == "peste un minut" assert ( self.locale._format_relative("un minut", "minute", -1) == "un minut în urmă" ) assert ( self.locale._format_relative("câteva secunde", "seconds", -1) == "câteva secunde în urmă" ) assert ( self.locale._format_relative("câteva secunde", "seconds", 1) == "peste câteva secunde" ) assert self.locale._format_relative("o zi", "day", -1) == "o zi în urmă" assert self.locale._format_relative("o zi", "day", 1) == "peste o zi" @pytest.mark.usefixtures("lang_locale") class TestArabicLocale: def test_timeframes(self): # single assert self.locale._format_timeframe("minute", 1) == "دقيقة" assert self.locale._format_timeframe("hour", 1) == "ساعة" assert self.locale._format_timeframe("day", 1) == "يوم" assert self.locale._format_timeframe("month", 1) == "شهر" assert self.locale._format_timeframe("year", 1) == "سنة" # double assert self.locale._format_timeframe("minutes", 2) == "دقيقتين" assert self.locale._format_timeframe("hours", 2) == "ساعتين" assert self.locale._format_timeframe("days", 2) == "يومين" assert self.locale._format_timeframe("months", 2) == "شهرين" assert self.locale._format_timeframe("years", 2) == "سنتين" # up to ten assert self.locale._format_timeframe("minutes", 3) == "3 دقائق" assert self.locale._format_timeframe("hours", 4) == "4 ساعات" assert self.locale._format_timeframe("days", 5) == "5 أيام" assert self.locale._format_timeframe("months", 6) == "6 أشهر" assert self.locale._format_timeframe("years", 10) == "10 سنوات" # more than ten assert self.locale._format_timeframe("minutes", 11) == "11 دقيقة" assert self.locale._format_timeframe("hours", 19) == "19 ساعة" assert self.locale._format_timeframe("months", 24) == "24 شهر" assert self.locale._format_timeframe("days", 50) == "50 يوم" assert self.locale._format_timeframe("years", 115) == "115 سنة" @pytest.mark.usefixtures("lang_locale") class TestNepaliLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("hours", 3) == "3 घण्टा" assert self.locale._format_timeframe("hour", 0) == "एक घण्टा" def test_format_relative_now(self): result = self.locale._format_relative("अहिले", "now", 0) assert result == "अहिले" def test_format_relative_future(self): result = self.locale._format_relative("एक घण्टा", "hour", 1) assert result == "एक घण्टा पछी" def test_format_relative_past(self): result = self.locale._format_relative("एक घण्टा", "hour", -1) assert result == "एक घण्टा पहिले" @pytest.mark.usefixtures("lang_locale") class TestIndonesianLocale: def test_timeframes(self): assert self.locale._format_timeframe("hours", 2) == "2 jam" assert self.locale._format_timeframe("months", 2) == "2 bulan" assert self.locale._format_timeframe("days", 2) == "2 hari" assert self.locale._format_timeframe("years", 2) == "2 tahun" assert self.locale._format_timeframe("hours", 3) == "3 jam" assert self.locale._format_timeframe("months", 4) == "4 bulan" assert self.locale._format_timeframe("days", 3) == "3 hari" assert self.locale._format_timeframe("years", 5) == "5 tahun" def test_format_relative_now(self): assert self.locale._format_relative("baru saja", "now", 0) == "baru saja" def test_format_relative_past(self): assert self.locale._format_relative("1 jam", "hour", 1) == "dalam 1 jam" assert self.locale._format_relative("1 detik", "seconds", 1) == "dalam 1 detik" def test_format_relative_future(self): assert self.locale._format_relative("1 jam", "hour", -1) == "1 jam yang lalu" @pytest.mark.usefixtures("lang_locale") class TestTagalogLocale: def test_singles_tl(self): assert self.locale._format_timeframe("second", 1) == "isang segundo" assert self.locale._format_timeframe("minute", 1) == "isang minuto" assert self.locale._format_timeframe("hour", 1) == "isang oras" assert self.locale._format_timeframe("day", 1) == "isang araw" assert self.locale._format_timeframe("week", 1) == "isang linggo" assert self.locale._format_timeframe("month", 1) == "isang buwan" assert self.locale._format_timeframe("year", 1) == "isang taon" def test_meridians_tl(self): assert self.locale.meridian(7, "A") == "ng umaga" assert self.locale.meridian(18, "A") == "ng hapon" assert self.locale.meridian(10, "a") == "nu" assert self.locale.meridian(22, "a") == "nh" def test_describe_tl(self): assert self.locale.describe("second", only_distance=True) == "isang segundo" assert ( self.locale.describe("second", only_distance=False) == "isang segundo mula ngayon" ) assert self.locale.describe("minute", only_distance=True) == "isang minuto" assert ( self.locale.describe("minute", only_distance=False) == "isang minuto mula ngayon" ) assert self.locale.describe("hour", only_distance=True) == "isang oras" assert ( self.locale.describe("hour", only_distance=False) == "isang oras mula ngayon" ) assert self.locale.describe("day", only_distance=True) == "isang araw" assert ( self.locale.describe("day", only_distance=False) == "isang araw mula ngayon" ) assert self.locale.describe("week", only_distance=True) == "isang linggo" assert ( self.locale.describe("week", only_distance=False) == "isang linggo mula ngayon" ) assert self.locale.describe("month", only_distance=True) == "isang buwan" assert ( self.locale.describe("month", only_distance=False) == "isang buwan mula ngayon" ) assert self.locale.describe("year", only_distance=True) == "isang taon" assert ( self.locale.describe("year", only_distance=False) == "isang taon mula ngayon" ) def test_relative_tl(self): # time assert self.locale._format_relative("ngayon", "now", 0) == "ngayon" assert ( self.locale._format_relative("1 segundo", "seconds", 1) == "1 segundo mula ngayon" ) assert ( self.locale._format_relative("1 minuto", "minutes", 1) == "1 minuto mula ngayon" ) assert ( self.locale._format_relative("1 oras", "hours", 1) == "1 oras mula ngayon" ) assert self.locale._format_relative("1 araw", "days", 1) == "1 araw mula ngayon" assert ( self.locale._format_relative("1 linggo", "weeks", 1) == "1 linggo mula ngayon" ) assert ( self.locale._format_relative("1 buwan", "months", 1) == "1 buwan mula ngayon" ) assert ( self.locale._format_relative("1 taon", "years", 1) == "1 taon mula ngayon" ) assert ( self.locale._format_relative("1 segundo", "seconds", -1) == "nakaraang 1 segundo" ) assert ( self.locale._format_relative("1 minuto", "minutes", -1) == "nakaraang 1 minuto" ) assert self.locale._format_relative("1 oras", "hours", -1) == "nakaraang 1 oras" assert self.locale._format_relative("1 araw", "days", -1) == "nakaraang 1 araw" assert ( self.locale._format_relative("1 linggo", "weeks", -1) == "nakaraang 1 linggo" ) assert ( self.locale._format_relative("1 buwan", "months", -1) == "nakaraang 1 buwan" ) assert self.locale._format_relative("1 taon", "years", -1) == "nakaraang 1 taon" def test_plurals_tl(self): # Seconds assert self.locale._format_timeframe("seconds", 0) == "0 segundo" assert self.locale._format_timeframe("seconds", 1) == "1 segundo" assert self.locale._format_timeframe("seconds", 2) == "2 segundo" assert self.locale._format_timeframe("seconds", 4) == "4 segundo" assert self.locale._format_timeframe("seconds", 5) == "5 segundo" assert self.locale._format_timeframe("seconds", 21) == "21 segundo" assert self.locale._format_timeframe("seconds", 22) == "22 segundo" assert self.locale._format_timeframe("seconds", 25) == "25 segundo" # Minutes assert self.locale._format_timeframe("minutes", 0) == "0 minuto" assert self.locale._format_timeframe("minutes", 1) == "1 minuto" assert self.locale._format_timeframe("minutes", 2) == "2 minuto" assert self.locale._format_timeframe("minutes", 4) == "4 minuto" assert self.locale._format_timeframe("minutes", 5) == "5 minuto" assert self.locale._format_timeframe("minutes", 21) == "21 minuto" assert self.locale._format_timeframe("minutes", 22) == "22 minuto" assert self.locale._format_timeframe("minutes", 25) == "25 minuto" # Hours assert self.locale._format_timeframe("hours", 0) == "0 oras" assert self.locale._format_timeframe("hours", 1) == "1 oras" assert self.locale._format_timeframe("hours", 2) == "2 oras" assert self.locale._format_timeframe("hours", 4) == "4 oras" assert self.locale._format_timeframe("hours", 5) == "5 oras" assert self.locale._format_timeframe("hours", 21) == "21 oras" assert self.locale._format_timeframe("hours", 22) == "22 oras" assert self.locale._format_timeframe("hours", 25) == "25 oras" # Days assert self.locale._format_timeframe("days", 0) == "0 araw" assert self.locale._format_timeframe("days", 1) == "1 araw" assert self.locale._format_timeframe("days", 2) == "2 araw" assert self.locale._format_timeframe("days", 3) == "3 araw" assert self.locale._format_timeframe("days", 21) == "21 araw" # Weeks assert self.locale._format_timeframe("weeks", 0) == "0 linggo" assert self.locale._format_timeframe("weeks", 1) == "1 linggo" assert self.locale._format_timeframe("weeks", 2) == "2 linggo" assert self.locale._format_timeframe("weeks", 4) == "4 linggo" assert self.locale._format_timeframe("weeks", 5) == "5 linggo" assert self.locale._format_timeframe("weeks", 21) == "21 linggo" assert self.locale._format_timeframe("weeks", 22) == "22 linggo" assert self.locale._format_timeframe("weeks", 25) == "25 linggo" # Months assert self.locale._format_timeframe("months", 0) == "0 buwan" assert self.locale._format_timeframe("months", 1) == "1 buwan" assert self.locale._format_timeframe("months", 2) == "2 buwan" assert self.locale._format_timeframe("months", 4) == "4 buwan" assert self.locale._format_timeframe("months", 5) == "5 buwan" assert self.locale._format_timeframe("months", 21) == "21 buwan" assert self.locale._format_timeframe("months", 22) == "22 buwan" assert self.locale._format_timeframe("months", 25) == "25 buwan" # Years assert self.locale._format_timeframe("years", 1) == "1 taon" assert self.locale._format_timeframe("years", 2) == "2 taon" assert self.locale._format_timeframe("years", 5) == "5 taon" def test_multi_describe_tl(self): describe = self.locale.describe_multi fulltest = [("years", 5), ("weeks", 1), ("hours", 1), ("minutes", 6)] assert describe(fulltest) == "5 taon 1 linggo 1 oras 6 minuto mula ngayon" seconds4000_0days = [("days", 0), ("hours", 1), ("minutes", 6)] assert describe(seconds4000_0days) == "0 araw 1 oras 6 minuto mula ngayon" seconds4000 = [("hours", 1), ("minutes", 6)] assert describe(seconds4000) == "1 oras 6 minuto mula ngayon" assert describe(seconds4000, only_distance=True) == "1 oras 6 minuto" seconds3700 = [("hours", 1), ("minutes", 1)] assert describe(seconds3700) == "1 oras 1 minuto mula ngayon" seconds300_0hours = [("hours", 0), ("minutes", 5)] assert describe(seconds300_0hours) == "0 oras 5 minuto mula ngayon" seconds300 = [("minutes", 5)] assert describe(seconds300) == "5 minuto mula ngayon" seconds60 = [("minutes", 1)] assert describe(seconds60) == "1 minuto mula ngayon" assert describe(seconds60, only_distance=True) == "1 minuto" seconds60 = [("seconds", 1)] assert describe(seconds60) == "1 segundo mula ngayon" assert describe(seconds60, only_distance=True) == "1 segundo" def test_ordinal_number_tl(self): assert self.locale.ordinal_number(0) == "ika-0" assert self.locale.ordinal_number(1) == "ika-1" assert self.locale.ordinal_number(2) == "ika-2" assert self.locale.ordinal_number(3) == "ika-3" assert self.locale.ordinal_number(10) == "ika-10" assert self.locale.ordinal_number(23) == "ika-23" assert self.locale.ordinal_number(100) == "ika-100" assert self.locale.ordinal_number(103) == "ika-103" assert self.locale.ordinal_number(114) == "ika-114" @pytest.mark.usefixtures("lang_locale") class TestCroatianLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("now", 0) == "upravo sad" assert self.locale._format_timeframe("second", 1) == "sekundu" assert self.locale._format_timeframe("seconds", 3) == "3 sekunde" assert self.locale._format_timeframe("seconds", 30) == "30 sekundi" assert self.locale._format_timeframe("minute", 1) == "minutu" assert self.locale._format_timeframe("minutes", 4) == "4 minute" assert self.locale._format_timeframe("minutes", 40) == "40 minuta" assert self.locale._format_timeframe("hour", 1) == "sat" assert self.locale._format_timeframe("hours", 23) == "23 sati" assert self.locale._format_timeframe("day", 1) == "jedan dan" assert self.locale._format_timeframe("days", 12) == "12 dana" assert self.locale._format_timeframe("month", 1) == "mjesec" assert self.locale._format_timeframe("months", 2) == "2 mjeseca" assert self.locale._format_timeframe("months", 11) == "11 mjeseci" assert self.locale._format_timeframe("year", 1) == "godinu" assert self.locale._format_timeframe("years", 2) == "2 godine" assert self.locale._format_timeframe("years", 12) == "12 godina" def test_weekday(self): dt = arrow.Arrow(2015, 4, 11, 17, 30, 00) assert self.locale.day_name(dt.isoweekday()) == "subota" assert self.locale.day_abbreviation(dt.isoweekday()) == "su" @pytest.mark.usefixtures("lang_locale") class TestSerbianLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("now", 0) == "sada" assert self.locale._format_timeframe("second", 1) == "sekundu" assert self.locale._format_timeframe("seconds", 3) == "3 sekunde" assert self.locale._format_timeframe("seconds", 30) == "30 sekundi" assert self.locale._format_timeframe("minute", 1) == "minutu" assert self.locale._format_timeframe("minutes", 4) == "4 minute" assert self.locale._format_timeframe("minutes", 40) == "40 minuta" assert self.locale._format_timeframe("hour", 1) == "sat" assert self.locale._format_timeframe("hours", 3) == "3 sata" assert self.locale._format_timeframe("hours", 23) == "23 sati" assert self.locale._format_timeframe("day", 1) == "dan" assert self.locale._format_timeframe("days", 12) == "12 dana" assert self.locale._format_timeframe("week", 1) == "nedelju" assert self.locale._format_timeframe("weeks", 2) == "2 nedelje" assert self.locale._format_timeframe("weeks", 11) == "11 nedelja" assert self.locale._format_timeframe("month", 1) == "mesec" assert self.locale._format_timeframe("months", 2) == "2 meseca" assert self.locale._format_timeframe("months", 11) == "11 meseci" assert self.locale._format_timeframe("year", 1) == "godinu" assert self.locale._format_timeframe("years", 2) == "2 godine" assert self.locale._format_timeframe("years", 12) == "12 godina" def test_weekday(self): dt = arrow.Arrow(2015, 4, 11, 17, 30, 00) assert self.locale.day_name(dt.isoweekday()) == "subota" assert self.locale.day_abbreviation(dt.isoweekday()) == "su" @pytest.mark.usefixtures("lang_locale") class TestLatinLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("now", 0) == "nunc" assert self.locale._format_timeframe("second", 1) == "secundum" assert self.locale._format_timeframe("seconds", 3) == "3 secundis" assert self.locale._format_timeframe("minute", 1) == "minutam" assert self.locale._format_timeframe("minutes", 4) == "4 minutis" assert self.locale._format_timeframe("hour", 1) == "horam" assert self.locale._format_timeframe("hours", 23) == "23 horas" assert self.locale._format_timeframe("day", 1) == "diem" assert self.locale._format_timeframe("days", 12) == "12 dies" assert self.locale._format_timeframe("month", 1) == "mensem" assert self.locale._format_timeframe("months", 11) == "11 mensis" assert self.locale._format_timeframe("year", 1) == "annum" assert self.locale._format_timeframe("years", 2) == "2 annos" def test_weekday(self): dt = arrow.Arrow(2015, 4, 11, 17, 30, 00) assert self.locale.day_name(dt.isoweekday()) == "dies Saturni" @pytest.mark.usefixtures("lang_locale") class TestLithuanianLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("now", 0) == "dabar" assert self.locale._format_timeframe("second", 1) == "sekundės" assert self.locale._format_timeframe("seconds", 3) == "3 sekundžių" assert self.locale._format_timeframe("seconds", 30) == "30 sekundžių" assert self.locale._format_timeframe("minute", 1) == "minutės" assert self.locale._format_timeframe("minutes", 4) == "4 minučių" assert self.locale._format_timeframe("minutes", 40) == "40 minučių" assert self.locale._format_timeframe("hour", 1) == "valandos" assert self.locale._format_timeframe("hours", 23) == "23 valandų" assert self.locale._format_timeframe("day", 1) == "dieną" assert self.locale._format_timeframe("days", 12) == "12 dienų" assert self.locale._format_timeframe("month", 1) == "mėnesio" assert self.locale._format_timeframe("months", 2) == "2 mėnesių" assert self.locale._format_timeframe("months", 11) == "11 mėnesių" assert self.locale._format_timeframe("year", 1) == "metų" assert self.locale._format_timeframe("years", 2) == "2 metų" def test_weekday(self): dt = arrow.Arrow(2015, 4, 11, 17, 30, 00) assert self.locale.day_name(dt.isoweekday()) == "šeštadienis" assert self.locale.day_abbreviation(dt.isoweekday()) == "še" @pytest.mark.usefixtures("lang_locale") class TestMalayLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("now", 0) == "sekarang" assert self.locale._format_timeframe("second", 1) == "saat" assert self.locale._format_timeframe("seconds", 3) == "3 saat" assert self.locale._format_timeframe("minute", 1) == "minit" assert self.locale._format_timeframe("minutes", 4) == "4 minit" assert self.locale._format_timeframe("hour", 1) == "jam" assert self.locale._format_timeframe("hours", 23) == "23 jam" assert self.locale._format_timeframe("day", 1) == "hari" assert self.locale._format_timeframe("days", 12) == "12 hari" assert self.locale._format_timeframe("month", 1) == "bulan" assert self.locale._format_timeframe("months", 2) == "2 bulan" assert self.locale._format_timeframe("year", 1) == "tahun" assert self.locale._format_timeframe("years", 2) == "2 tahun" def test_weekday(self): dt = arrow.Arrow(2015, 4, 11, 17, 30, 00) assert self.locale.day_name(dt.isoweekday()) == "Sabtu" @pytest.mark.usefixtures("lang_locale") class TestSamiLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("now", 0) == "dál" assert self.locale._format_timeframe("second", 1) == "sekunda" assert self.locale._format_timeframe("seconds", 3) == "3 sekundda" assert self.locale._format_timeframe("minute", 1) == "minuhta" assert self.locale._format_timeframe("minutes", 4) == "4 minuhta" assert self.locale._format_timeframe("hour", 1) == "diimmu" assert self.locale._format_timeframe("hours", 23) == "23 diimmu" assert self.locale._format_timeframe("day", 1) == "beaivvi" assert self.locale._format_timeframe("days", 12) == "12 beaivvi" assert self.locale._format_timeframe("month", 1) == "mánu" assert self.locale._format_timeframe("months", 2) == "2 mánu" assert self.locale._format_timeframe("year", 1) == "jagi" assert self.locale._format_timeframe("years", 2) == "2 jagi" def test_weekday(self): dt = arrow.Arrow(2015, 4, 11, 17, 30, 00) assert self.locale.day_name(dt.isoweekday()) == "Lávvordat" @pytest.mark.usefixtures("lang_locale") class TestZuluLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("now", 0) == "manje" assert self.locale._format_timeframe("second", -1) == "umzuzwana" assert self.locale._format_timeframe("second", 1) == "ngomzuzwana" assert self.locale._format_timeframe("seconds", -3) == "3 imizuzwana" assert self.locale._format_timeframe("minute", -1) == "umzuzu" assert self.locale._format_timeframe("minutes", -4) == "4 imizuzu" assert self.locale._format_timeframe("hour", -1) == "ihora" assert self.locale._format_timeframe("hours", -23) == "23 amahora" assert self.locale._format_timeframe("day", -1) == "usuku" assert self.locale._format_timeframe("days", -12) == "12 izinsuku" assert self.locale._format_timeframe("month", -1) == "inyanga" assert self.locale._format_timeframe("months", -2) == "2 izinyanga" assert self.locale._format_timeframe("year", -1) == "unyaka" assert self.locale._format_timeframe("years", -2) == "2 iminyaka" def test_weekday(self): dt = arrow.Arrow(2015, 4, 11, 17, 30, 00) assert self.locale.day_name(dt.isoweekday()) == "uMgqibelo" @pytest.mark.usefixtures("lang_locale") class TestAlbanianLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("now", 0) == "tani" assert self.locale._format_timeframe("second", -1) == "sekondë" assert self.locale._format_timeframe("second", 1) == "sekondë" assert self.locale._format_timeframe("seconds", -3) == "3 sekonda" assert self.locale._format_timeframe("minute", 1) == "minutë" assert self.locale._format_timeframe("minutes", -4) == "4 minuta" assert self.locale._format_timeframe("hour", 1) == "orë" assert self.locale._format_timeframe("hours", -23) == "23 orë" assert self.locale._format_timeframe("day", 1) == "ditë" assert self.locale._format_timeframe("days", -12) == "12 ditë" assert self.locale._format_timeframe("week", 1) == "javë" assert self.locale._format_timeframe("weeks", -12) == "12 javë" assert self.locale._format_timeframe("month", 1) == "muaj" assert self.locale._format_timeframe("months", -2) == "2 muaj" assert self.locale._format_timeframe("year", 1) == "vit" assert self.locale._format_timeframe("years", -2) == "2 vjet" def test_weekday_and_month(self): dt = arrow.Arrow(2015, 4, 11, 17, 30, 00) # Saturday assert self.locale.day_name(dt.isoweekday()) == "e shtunë" assert self.locale.day_abbreviation(dt.isoweekday()) == "sht" # June assert self.locale.month_name(dt.isoweekday()) == "qershor" assert self.locale.month_abbreviation(dt.isoweekday()) == "qer" @pytest.mark.usefixtures("lang_locale") class TestUrduLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("now", 0) == "ابھی" assert self.locale._format_timeframe("second", -1) == "ایک سیکنڈ" assert self.locale._format_timeframe("second", 1) == "ایک سیکنڈ" assert self.locale._format_timeframe("seconds", -3) == "3 سیکنڈ" assert self.locale._format_timeframe("minute", 1) == "ایک منٹ" assert self.locale._format_timeframe("minutes", -4) == "4 منٹ" assert self.locale._format_timeframe("hour", 1) == "ایک گھنٹے" assert self.locale._format_timeframe("hours", -23) == "23 گھنٹے" assert self.locale._format_timeframe("day", 1) == "ایک دن" assert self.locale._format_timeframe("days", -12) == "12 دن" assert self.locale._format_timeframe("week", 1) == "ایک ہفتے" assert self.locale._format_timeframe("weeks", -12) == "12 ہفتے" assert self.locale._format_timeframe("month", 1) == "ایک مہینہ" assert self.locale._format_timeframe("months", -2) == "2 ماہ" assert self.locale._format_timeframe("year", 1) == "ایک سال" assert self.locale._format_timeframe("years", -2) == "2 سال" def test_weekday_and_month(self): dt = arrow.Arrow(2015, 4, 11, 17, 30, 00) # Saturday assert self.locale.day_name(dt.isoweekday()) == "ہفتہ" assert self.locale.day_abbreviation(dt.isoweekday()) == "ہفتہ" # June assert self.locale.month_name(dt.isoweekday()) == "جون" assert self.locale.month_abbreviation(dt.isoweekday()) == "جون" @pytest.mark.usefixtures("lang_locale") class TestEstonianLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("now", 0) == "just nüüd" assert self.locale._format_timeframe("second", 1) == "ühe sekundi" assert self.locale._format_timeframe("seconds", 3) == "3 sekundi" assert self.locale._format_timeframe("seconds", 30) == "30 sekundi" assert self.locale._format_timeframe("minute", 1) == "ühe minuti" assert self.locale._format_timeframe("minutes", 4) == "4 minuti" assert self.locale._format_timeframe("minutes", 40) == "40 minuti" assert self.locale._format_timeframe("hour", 1) == "tunni aja" assert self.locale._format_timeframe("hours", 5) == "5 tunni" assert self.locale._format_timeframe("hours", 23) == "23 tunni" assert self.locale._format_timeframe("day", 1) == "ühe päeva" assert self.locale._format_timeframe("days", 6) == "6 päeva" assert self.locale._format_timeframe("days", 12) == "12 päeva" assert self.locale._format_timeframe("month", 1) == "ühe kuu" assert self.locale._format_timeframe("months", 7) == "7 kuu" assert self.locale._format_timeframe("months", 11) == "11 kuu" assert self.locale._format_timeframe("year", 1) == "ühe aasta" assert self.locale._format_timeframe("years", 8) == "8 aasta" assert self.locale._format_timeframe("years", 12) == "12 aasta" assert self.locale._format_timeframe("now", 0) == "just nüüd" assert self.locale._format_timeframe("second", -1) == "üks sekund" assert self.locale._format_timeframe("seconds", -9) == "9 sekundit" assert self.locale._format_timeframe("seconds", -12) == "12 sekundit" assert self.locale._format_timeframe("minute", -1) == "üks minut" assert self.locale._format_timeframe("minutes", -2) == "2 minutit" assert self.locale._format_timeframe("minutes", -10) == "10 minutit" assert self.locale._format_timeframe("hour", -1) == "tund aega" assert self.locale._format_timeframe("hours", -3) == "3 tundi" assert self.locale._format_timeframe("hours", -11) == "11 tundi" assert self.locale._format_timeframe("day", -1) == "üks päev" assert self.locale._format_timeframe("days", -2) == "2 päeva" assert self.locale._format_timeframe("days", -12) == "12 päeva" assert self.locale._format_timeframe("month", -1) == "üks kuu" assert self.locale._format_timeframe("months", -3) == "3 kuud" assert self.locale._format_timeframe("months", -13) == "13 kuud" assert self.locale._format_timeframe("year", -1) == "üks aasta" assert self.locale._format_timeframe("years", -4) == "4 aastat" assert self.locale._format_timeframe("years", -14) == "14 aastat" @pytest.mark.usefixtures("lang_locale") class TestPortugueseLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("now", 0) == "agora" assert self.locale._format_timeframe("second", 1) == "um segundo" assert self.locale._format_timeframe("seconds", 30) == "30 segundos" assert self.locale._format_timeframe("minute", 1) == "um minuto" assert self.locale._format_timeframe("minutes", 40) == "40 minutos" assert self.locale._format_timeframe("hour", 1) == "uma hora" assert self.locale._format_timeframe("hours", 23) == "23 horas" assert self.locale._format_timeframe("day", 1) == "um dia" assert self.locale._format_timeframe("days", 12) == "12 dias" assert self.locale._format_timeframe("month", 1) == "um mês" assert self.locale._format_timeframe("months", 11) == "11 meses" assert self.locale._format_timeframe("year", 1) == "um ano" assert self.locale._format_timeframe("years", 12) == "12 anos" @pytest.mark.usefixtures("lang_locale") class TestLatvianLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("now", 0) == "tagad" assert self.locale._format_timeframe("second", 1) == "sekundes" assert self.locale._format_timeframe("seconds", 3) == "3 sekundēm" assert self.locale._format_timeframe("seconds", 30) == "30 sekundēm" assert self.locale._format_timeframe("minute", 1) == "minūtes" assert self.locale._format_timeframe("minutes", 4) == "4 minūtēm" assert self.locale._format_timeframe("minutes", 40) == "40 minūtēm" assert self.locale._format_timeframe("hour", 1) == "stundas" assert self.locale._format_timeframe("hours", 23) == "23 stundām" assert self.locale._format_timeframe("day", 1) == "dienas" assert self.locale._format_timeframe("days", 12) == "12 dienām" assert self.locale._format_timeframe("month", 1) == "mēneša" assert self.locale._format_timeframe("months", 2) == "2 mēnešiem" assert self.locale._format_timeframe("months", 11) == "11 mēnešiem" assert self.locale._format_timeframe("year", 1) == "gada" assert self.locale._format_timeframe("years", 2) == "2 gadiem" assert self.locale._format_timeframe("years", 12) == "12 gadiem" def test_weekday(self): dt = arrow.Arrow(2015, 4, 11, 17, 30, 00) assert self.locale.day_name(dt.isoweekday()) == "sestdiena" assert self.locale.day_abbreviation(dt.isoweekday()) == "se" @pytest.mark.usefixtures("lang_locale") class TestBrazilianPortugueseLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("now", 0) == "agora" assert self.locale._format_timeframe("second", 1) == "um segundo" assert self.locale._format_timeframe("seconds", 30) == "30 segundos" assert self.locale._format_timeframe("minute", 1) == "um minuto" assert self.locale._format_timeframe("minutes", 40) == "40 minutos" assert self.locale._format_timeframe("hour", 1) == "uma hora" assert self.locale._format_timeframe("hours", 23) == "23 horas" assert self.locale._format_timeframe("day", 1) == "um dia" assert self.locale._format_timeframe("days", 12) == "12 dias" assert self.locale._format_timeframe("month", 1) == "um mês" assert self.locale._format_timeframe("months", 11) == "11 meses" assert self.locale._format_timeframe("year", 1) == "um ano" assert self.locale._format_timeframe("years", 12) == "12 anos" assert self.locale._format_relative("uma hora", "hour", -1) == "faz uma hora" @pytest.mark.usefixtures("lang_locale") class TestHongKongLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("now", 0) == "剛才" assert self.locale._format_timeframe("second", 1) == "1秒" assert self.locale._format_timeframe("seconds", 30) == "30秒" assert self.locale._format_timeframe("minute", 1) == "1分鐘" assert self.locale._format_timeframe("minutes", 40) == "40分鐘" assert self.locale._format_timeframe("hour", 1) == "1小時" assert self.locale._format_timeframe("hours", 23) == "23小時" assert self.locale._format_timeframe("day", 1) == "1天" assert self.locale._format_timeframe("days", 12) == "12天" assert self.locale._format_timeframe("week", 1) == "1星期" assert self.locale._format_timeframe("weeks", 38) == "38星期" assert self.locale._format_timeframe("month", 1) == "1個月" assert self.locale._format_timeframe("months", 11) == "11個月" assert self.locale._format_timeframe("year", 1) == "1年" assert self.locale._format_timeframe("years", 12) == "12年" @pytest.mark.usefixtures("lang_locale") class TestChineseTWLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("now", 0) == "剛才" assert self.locale._format_timeframe("second", 1) == "1秒" assert self.locale._format_timeframe("seconds", 30) == "30秒" assert self.locale._format_timeframe("minute", 1) == "1分鐘" assert self.locale._format_timeframe("minutes", 40) == "40分鐘" assert self.locale._format_timeframe("hour", 1) == "1小時" assert self.locale._format_timeframe("hours", 23) == "23小時" assert self.locale._format_timeframe("day", 1) == "1天" assert self.locale._format_timeframe("days", 12) == "12天" assert self.locale._format_timeframe("week", 1) == "1週" assert self.locale._format_timeframe("weeks", 38) == "38週" assert self.locale._format_timeframe("month", 1) == "1個月" assert self.locale._format_timeframe("months", 11) == "11個月" assert self.locale._format_timeframe("year", 1) == "1年" assert self.locale._format_timeframe("years", 12) == "12年" @pytest.mark.usefixtures("lang_locale") class TestChineseCNLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("now", 0) == "刚才" assert self.locale._format_timeframe("second", 1) == "一秒" assert self.locale._format_timeframe("seconds", 30) == "30秒" assert self.locale._format_timeframe("minute", 1) == "1分钟" assert self.locale._format_timeframe("minutes", 40) == "40分钟" assert self.locale._format_timeframe("hour", 1) == "1小时" assert self.locale._format_timeframe("hours", 23) == "23小时" assert self.locale._format_timeframe("day", 1) == "1天" assert self.locale._format_timeframe("days", 12) == "12天" assert self.locale._format_timeframe("week", 1) == "一周" assert self.locale._format_timeframe("weeks", 38) == "38周" assert self.locale._format_timeframe("month", 1) == "1个月" assert self.locale._format_timeframe("months", 11) == "11个月" assert self.locale._format_timeframe("year", 1) == "1年" assert self.locale._format_timeframe("years", 12) == "12年" @pytest.mark.usefixtures("lang_locale") class TestSwahiliLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("now", 0) == "sasa hivi" assert self.locale._format_timeframe("second", 1) == "sekunde" assert self.locale._format_timeframe("seconds", 3) == "sekunde 3" assert self.locale._format_timeframe("seconds", 30) == "sekunde 30" assert self.locale._format_timeframe("minute", 1) == "dakika moja" assert self.locale._format_timeframe("minutes", 4) == "dakika 4" assert self.locale._format_timeframe("minutes", 40) == "dakika 40" assert self.locale._format_timeframe("hour", 1) == "saa moja" assert self.locale._format_timeframe("hours", 5) == "saa 5" assert self.locale._format_timeframe("hours", 23) == "saa 23" assert self.locale._format_timeframe("day", 1) == "siku moja" assert self.locale._format_timeframe("days", 6) == "siku 6" assert self.locale._format_timeframe("days", 12) == "siku 12" assert self.locale._format_timeframe("month", 1) == "mwezi moja" assert self.locale._format_timeframe("months", 7) == "miezi 7" assert self.locale._format_timeframe("week", 1) == "wiki moja" assert self.locale._format_timeframe("weeks", 2) == "wiki 2" assert self.locale._format_timeframe("months", 11) == "miezi 11" assert self.locale._format_timeframe("year", 1) == "mwaka moja" assert self.locale._format_timeframe("years", 8) == "miaka 8" assert self.locale._format_timeframe("years", 12) == "miaka 12" def test_format_relative_now(self): result = self.locale._format_relative("sasa hivi", "now", 0) assert result == "sasa hivi" def test_format_relative_past(self): result = self.locale._format_relative("saa moja", "hour", 1) assert result == "muda wa saa moja" def test_format_relative_future(self): result = self.locale._format_relative("saa moja", "hour", -1) assert result == "saa moja iliyopita" @pytest.mark.usefixtures("lang_locale") class TestKoreanLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("now", 0) == "지금" assert self.locale._format_timeframe("second", 1) == "1초" assert self.locale._format_timeframe("seconds", 2) == "2초" assert self.locale._format_timeframe("minute", 1) == "1분" assert self.locale._format_timeframe("minutes", 2) == "2분" assert self.locale._format_timeframe("hour", 1) == "한시간" assert self.locale._format_timeframe("hours", 2) == "2시간" assert self.locale._format_timeframe("day", 1) == "하루" assert self.locale._format_timeframe("days", 2) == "2일" assert self.locale._format_timeframe("week", 1) == "1주" assert self.locale._format_timeframe("weeks", 2) == "2주" assert self.locale._format_timeframe("month", 1) == "한달" assert self.locale._format_timeframe("months", 2) == "2개월" assert self.locale._format_timeframe("year", 1) == "1년" assert self.locale._format_timeframe("years", 2) == "2년" def test_format_relative(self): assert self.locale._format_relative("지금", "now", 0) == "지금" assert self.locale._format_relative("1초", "second", 1) == "1초 후" assert self.locale._format_relative("2초", "seconds", 2) == "2초 후" assert self.locale._format_relative("1분", "minute", 1) == "1분 후" assert self.locale._format_relative("2분", "minutes", 2) == "2분 후" assert self.locale._format_relative("한시간", "hour", 1) == "한시간 후" assert self.locale._format_relative("2시간", "hours", 2) == "2시간 후" assert self.locale._format_relative("하루", "day", 1) == "내일" assert self.locale._format_relative("2일", "days", 2) == "모레" assert self.locale._format_relative("3일", "days", 3) == "글피" assert self.locale._format_relative("4일", "days", 4) == "그글피" assert self.locale._format_relative("5일", "days", 5) == "5일 후" assert self.locale._format_relative("1주", "week", 1) == "1주 후" assert self.locale._format_relative("2주", "weeks", 2) == "2주 후" assert self.locale._format_relative("한달", "month", 1) == "한달 후" assert self.locale._format_relative("2개월", "months", 2) == "2개월 후" assert self.locale._format_relative("1년", "year", 1) == "내년" assert self.locale._format_relative("2년", "years", 2) == "내후년" assert self.locale._format_relative("3년", "years", 3) == "3년 후" assert self.locale._format_relative("1초", "second", -1) == "1초 전" assert self.locale._format_relative("2초", "seconds", -2) == "2초 전" assert self.locale._format_relative("1분", "minute", -1) == "1분 전" assert self.locale._format_relative("2분", "minutes", -2) == "2분 전" assert self.locale._format_relative("한시간", "hour", -1) == "한시간 전" assert self.locale._format_relative("2시간", "hours", -2) == "2시간 전" assert self.locale._format_relative("하루", "day", -1) == "어제" assert self.locale._format_relative("2일", "days", -2) == "그제" assert self.locale._format_relative("3일", "days", -3) == "그끄제" assert self.locale._format_relative("4일", "days", -4) == "4일 전" assert self.locale._format_relative("1주", "week", -1) == "1주 전" assert self.locale._format_relative("2주", "weeks", -2) == "2주 전" assert self.locale._format_relative("한달", "month", -1) == "한달 전" assert self.locale._format_relative("2개월", "months", -2) == "2개월 전" assert self.locale._format_relative("1년", "year", -1) == "작년" assert self.locale._format_relative("2년", "years", -2) == "제작년" assert self.locale._format_relative("3년", "years", -3) == "3년 전" def test_ordinal_number(self): assert self.locale.ordinal_number(0) == "0번째" assert self.locale.ordinal_number(1) == "첫번째" assert self.locale.ordinal_number(2) == "두번째" assert self.locale.ordinal_number(3) == "세번째" assert self.locale.ordinal_number(4) == "네번째" assert self.locale.ordinal_number(5) == "다섯번째" assert self.locale.ordinal_number(6) == "여섯번째" assert self.locale.ordinal_number(7) == "일곱번째" assert self.locale.ordinal_number(8) == "여덟번째" assert self.locale.ordinal_number(9) == "아홉번째" assert self.locale.ordinal_number(10) == "열번째" assert self.locale.ordinal_number(11) == "11번째" assert self.locale.ordinal_number(12) == "12번째" assert self.locale.ordinal_number(100) == "100번째" @pytest.mark.usefixtures("lang_locale") class TestJapaneseLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("now", 0) == "現在" assert self.locale._format_timeframe("second", 1) == "1秒" assert self.locale._format_timeframe("seconds", 30) == "30秒" assert self.locale._format_timeframe("minute", 1) == "1分" assert self.locale._format_timeframe("minutes", 40) == "40分" assert self.locale._format_timeframe("hour", 1) == "1時間" assert self.locale._format_timeframe("hours", 23) == "23時間" assert self.locale._format_timeframe("day", 1) == "1日" assert self.locale._format_timeframe("days", 12) == "12日" assert self.locale._format_timeframe("week", 1) == "1週間" assert self.locale._format_timeframe("weeks", 38) == "38週間" assert self.locale._format_timeframe("month", 1) == "1ヶ月" assert self.locale._format_timeframe("months", 11) == "11ヶ月" assert self.locale._format_timeframe("year", 1) == "1年" assert self.locale._format_timeframe("years", 12) == "12年" @pytest.mark.usefixtures("lang_locale") class TestSwedishLocale: def test_plurals(self): assert self.locale._format_timeframe("now", 0) == "just nu" assert self.locale._format_timeframe("second", 1) == "en sekund" assert self.locale._format_timeframe("seconds", 30) == "30 sekunder" assert self.locale._format_timeframe("minute", 1) == "en minut" assert self.locale._format_timeframe("minutes", 40) == "40 minuter" assert self.locale._format_timeframe("hour", 1) == "en timme" assert self.locale._format_timeframe("hours", 23) == "23 timmar" assert self.locale._format_timeframe("day", 1) == "en dag" assert self.locale._format_timeframe("days", 12) == "12 dagar" assert self.locale._format_timeframe("week", 1) == "en vecka" assert self.locale._format_timeframe("weeks", 38) == "38 veckor" assert self.locale._format_timeframe("month", 1) == "en månad" assert self.locale._format_timeframe("months", 11) == "11 månader" assert self.locale._format_timeframe("year", 1) == "ett år" assert self.locale._format_timeframe("years", 12) == "12 år" @pytest.mark.usefixtures("lang_locale") class TestOdiaLocale: def test_ordinal_number(self): assert self.locale._ordinal_number(0) == "0ତମ" assert self.locale._ordinal_number(1) == "1ମ" assert self.locale._ordinal_number(3) == "3ୟ" assert self.locale._ordinal_number(4) == "4ର୍ଥ" assert self.locale._ordinal_number(5) == "5ମ" assert self.locale._ordinal_number(6) == "6ଷ୍ଠ" assert self.locale._ordinal_number(10) == "10ମ" assert self.locale._ordinal_number(11) == "11ତମ" assert self.locale._ordinal_number(42) == "42ତମ" assert self.locale._ordinal_number(-1) == "" def test_format_timeframe(self): assert self.locale._format_timeframe("hours", 2) == "2 ଘଣ୍ଟା" assert self.locale._format_timeframe("hour", 0) == "ଏକ ଘଣ୍ଟା" def test_format_relative_now(self): result = self.locale._format_relative("ବର୍ତ୍ତମାନ", "now", 0) assert result == "ବର୍ତ୍ତମାନ" def test_format_relative_past(self): result = self.locale._format_relative("ଏକ ଘଣ୍ଟା", "hour", 1) assert result == "ଏକ ଘଣ୍ଟା ପରେ" def test_format_relative_future(self): result = self.locale._format_relative("ଏକ ଘଣ୍ଟା", "hour", -1) assert result == "ଏକ ଘଣ୍ଟା ପୂର୍ବେ" @pytest.mark.usefixtures("lang_locale") class TestTurkishLocale: def test_singles_mk(self): assert self.locale._format_timeframe("second", 1) == "bir saniye" assert self.locale._format_timeframe("minute", 1) == "bir dakika" assert self.locale._format_timeframe("hour", 1) == "bir saat" assert self.locale._format_timeframe("day", 1) == "bir gün" assert self.locale._format_timeframe("week", 1) == "bir hafta" assert self.locale._format_timeframe("month", 1) == "bir ay" assert self.locale._format_timeframe("year", 1) == "bir yıl" def test_meridians_mk(self): assert self.locale.meridian(7, "A") == "ÖÖ" assert self.locale.meridian(18, "A") == "ÖS" assert self.locale.meridian(10, "a") == "öö" assert self.locale.meridian(22, "a") == "ös" def test_describe_mk(self): assert self.locale.describe("second", only_distance=True) == "bir saniye" assert self.locale.describe("second", only_distance=False) == "bir saniye sonra" assert self.locale.describe("minute", only_distance=True) == "bir dakika" assert self.locale.describe("minute", only_distance=False) == "bir dakika sonra" assert self.locale.describe("hour", only_distance=True) == "bir saat" assert self.locale.describe("hour", only_distance=False) == "bir saat sonra" assert self.locale.describe("day", only_distance=True) == "bir gün" assert self.locale.describe("day", only_distance=False) == "bir gün sonra" assert self.locale.describe("week", only_distance=True) == "bir hafta" assert self.locale.describe("week", only_distance=False) == "bir hafta sonra" assert self.locale.describe("month", only_distance=True) == "bir ay" assert self.locale.describe("month", only_distance=False) == "bir ay sonra" assert self.locale.describe("year", only_distance=True) == "bir yıl" assert self.locale.describe("year", only_distance=False) == "bir yıl sonra" def test_relative_mk(self): assert self.locale._format_relative("şimdi", "now", 0) == "şimdi" assert ( self.locale._format_relative("1 saniye", "seconds", 1) == "1 saniye sonra" ) assert ( self.locale._format_relative("1 saniye", "seconds", -1) == "1 saniye önce" ) assert ( self.locale._format_relative("1 dakika", "minutes", 1) == "1 dakika sonra" ) assert ( self.locale._format_relative("1 dakika", "minutes", -1) == "1 dakika önce" ) assert self.locale._format_relative("1 saat", "hours", 1) == "1 saat sonra" assert self.locale._format_relative("1 saat", "hours", -1) == "1 saat önce" assert self.locale._format_relative("1 gün", "days", 1) == "1 gün sonra" assert self.locale._format_relative("1 gün", "days", -1) == "1 gün önce" assert self.locale._format_relative("1 hafta", "weeks", 1) == "1 hafta sonra" assert self.locale._format_relative("1 hafta", "weeks", -1) == "1 hafta önce" assert self.locale._format_relative("1 ay", "months", 1) == "1 ay sonra" assert self.locale._format_relative("1 ay", "months", -1) == "1 ay önce" assert self.locale._format_relative("1 yıl", "years", 1) == "1 yıl sonra" assert self.locale._format_relative("1 yıl", "years", -1) == "1 yıl önce" def test_plurals_mk(self): assert self.locale._format_timeframe("now", 0) == "şimdi" assert self.locale._format_timeframe("second", 1) == "bir saniye" assert self.locale._format_timeframe("seconds", 30) == "30 saniye" assert self.locale._format_timeframe("minute", 1) == "bir dakika" assert self.locale._format_timeframe("minutes", 40) == "40 dakika" assert self.locale._format_timeframe("hour", 1) == "bir saat" assert self.locale._format_timeframe("hours", 23) == "23 saat" assert self.locale._format_timeframe("day", 1) == "bir gün" assert self.locale._format_timeframe("days", 12) == "12 gün" assert self.locale._format_timeframe("week", 1) == "bir hafta" assert self.locale._format_timeframe("weeks", 38) == "38 hafta" assert self.locale._format_timeframe("month", 1) == "bir ay" assert self.locale._format_timeframe("months", 11) == "11 ay" assert self.locale._format_timeframe("year", 1) == "bir yıl" assert self.locale._format_timeframe("years", 12) == "12 yıl" @pytest.mark.usefixtures("lang_locale") class TestLuxembourgishLocale: def test_ordinal_number(self): assert self.locale.ordinal_number(1) == "1." def test_define(self): assert self.locale.describe("minute", only_distance=True) == "eng Minutt" assert self.locale.describe("minute", only_distance=False) == "an enger Minutt" assert self.locale.describe("hour", only_distance=True) == "eng Stonn" assert self.locale.describe("hour", only_distance=False) == "an enger Stonn" assert self.locale.describe("day", only_distance=True) == "een Dag" assert self.locale.describe("day", only_distance=False) == "an engem Dag" assert self.locale.describe("week", only_distance=True) == "eng Woch" assert self.locale.describe("week", only_distance=False) == "an enger Woch" assert self.locale.describe("month", only_distance=True) == "ee Mount" assert self.locale.describe("month", only_distance=False) == "an engem Mount" assert self.locale.describe("year", only_distance=True) == "ee Joer" assert self.locale.describe("year", only_distance=False) == "an engem Joer" def test_weekday(self): dt = arrow.Arrow(2015, 4, 11, 17, 30, 00) assert self.locale.day_name(dt.isoweekday()) == "Samschdeg" assert self.locale.day_abbreviation(dt.isoweekday()) == "Sam" @pytest.mark.usefixtures("lang_locale") class TestTamilLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("now", 0) == "இப்போது" assert self.locale._format_timeframe("second", 1) == "ஒரு இரண்டாவது" assert self.locale._format_timeframe("seconds", 3) == "3 விநாடிகள்" assert self.locale._format_timeframe("minute", 1) == "ஒரு நிமிடம்" assert self.locale._format_timeframe("minutes", 4) == "4 நிமிடங்கள்" assert self.locale._format_timeframe("hour", 1) == "ஒரு மணி" assert self.locale._format_timeframe("hours", 23) == "23 மணிநேரம்" assert self.locale._format_timeframe("day", 1) == "ஒரு நாள்" assert self.locale._format_timeframe("days", 12) == "12 நாட்கள்" assert self.locale._format_timeframe("week", 1) == "ஒரு வாரம்" assert self.locale._format_timeframe("weeks", 12) == "12 வாரங்கள்" assert self.locale._format_timeframe("month", 1) == "ஒரு மாதம்" assert self.locale._format_timeframe("months", 2) == "2 மாதங்கள்" assert self.locale._format_timeframe("year", 1) == "ஒரு ஆண்டு" assert self.locale._format_timeframe("years", 2) == "2 ஆண்டுகள்" def test_ordinal_number(self): assert self.locale._ordinal_number(0) == "0ஆம்" assert self.locale._ordinal_number(1) == "1வது" assert self.locale._ordinal_number(3) == "3ஆம்" assert self.locale._ordinal_number(11) == "11ஆம்" assert self.locale._ordinal_number(-1) == "" def test_format_relative_now(self): result = self.locale._format_relative("இப்போது", "now", 0) assert result == "இப்போது" def test_format_relative_past(self): result = self.locale._format_relative("ஒரு மணி", "hour", 1) assert result == "இல் ஒரு மணி" def test_format_relative_future(self): result = self.locale._format_relative("ஒரு மணி", "hour", -1) assert result == "ஒரு மணி நேரத்திற்கு முன்பு" def test_weekday(self): dt = arrow.Arrow(2015, 4, 11, 17, 30, 00) assert self.locale.day_name(dt.isoweekday()) == "சனிக்கிழமை" assert self.locale.day_abbreviation(dt.isoweekday()) == "சனி" @pytest.mark.usefixtures("lang_locale") class TestSinhalaLocale: def test_format_timeframe(self): assert self.locale._format_timeframe("now", 0) == "දැන්" assert self.locale._format_timeframe("second", -1) == "තත්පරයක" assert self.locale._format_timeframe("second", 1) == "තත්පරයකින්" assert self.locale._format_timeframe("seconds", -30) == "තත්පර 30 ක" assert self.locale._format_timeframe("minute", -1) == "විනාඩියක" assert self.locale._format_timeframe("minutes", 4) == "මිනිත්තු 4 කින්" assert self.locale._format_timeframe("hour", -1) == "පැයක" assert self.locale._format_timeframe("hours", 23) == "පැය 23 කින්" assert self.locale._format_timeframe("day", 1) == "දිනකට" assert self.locale._format_timeframe("days", -12) == "දින 12 ක" assert self.locale._format_timeframe("week", -1) == "සතියක" assert self.locale._format_timeframe("weeks", -10) == "සති 10 ක" assert self.locale._format_timeframe("month", -1) == "මාසයක" assert self.locale._format_timeframe("months", -2) == "මාස 2 ක" assert self.locale._format_timeframe("year", 1) == "වසරක් තුළ" assert self.locale._format_timeframe("years", -21) == "අවුරුදු 21 ක" def test_describe_si(self): assert self.locale.describe("second", only_distance=True) == "තත්පරයක්" assert ( self.locale.describe("second", only_distance=False) == "තත්පරයකින්" ) # (in) a second assert self.locale.describe("minute", only_distance=True) == "මිනිත්තුවක්" assert ( self.locale.describe("minute", only_distance=False) == "විනාඩියකින්" ) # (in) a minute assert self.locale.describe("hour", only_distance=True) == "පැයක්" assert self.locale.describe("hour", only_distance=False) == "පැයකින්" assert self.locale.describe("day", only_distance=True) == "දවසක්" assert self.locale.describe("day", only_distance=False) == "දිනකට" assert self.locale.describe("week", only_distance=True) == "සතියක්" assert self.locale.describe("week", only_distance=False) == "සතියකින්" assert self.locale.describe("month", only_distance=True) == "මාසයක්" assert self.locale.describe("month", only_distance=False) == "එය මාසය තුළ" assert self.locale.describe("year", only_distance=True) == "අවුරුද්දක්" assert self.locale.describe("year", only_distance=False) == "වසරක් තුළ" def test_format_relative_now(self): result = self.locale._format_relative("දැන්", "now", 0) assert result == "දැන්" def test_format_relative_future(self): result = self.locale._format_relative("පැයකින්", "පැය", 1) assert result == "පැයකින්" # (in) one hour def test_format_relative_past(self): result = self.locale._format_relative("පැයක", "පැය", -1) assert result == "පැයකට පෙර" # an hour ago def test_weekday(self): dt = arrow.Arrow(2015, 4, 11, 17, 30, 00) assert self.locale.day_name(dt.isoweekday()) == "සෙනසුරාදා" assert self.locale.day_abbreviation(dt.isoweekday()) == "අ" python-arrow-1.2.1/tests/test_parser.py000066400000000000000000001677101415100123600202400ustar00rootroot00000000000000import calendar import os import time from datetime import datetime import pytest from dateutil import tz import arrow from arrow import formatter, parser from arrow.constants import MAX_TIMESTAMP_US from arrow.parser import DateTimeParser, ParserError, ParserMatchError from .utils import make_full_tz_list @pytest.mark.usefixtures("dt_parser") class TestDateTimeParser: def test_parse_multiformat(self, mocker): mocker.patch( "arrow.parser.DateTimeParser.parse", string="str", fmt="fmt_a", side_effect=parser.ParserError, ) with pytest.raises(parser.ParserError): self.parser._parse_multiformat("str", ["fmt_a"]) mock_datetime = mocker.Mock() mocker.patch( "arrow.parser.DateTimeParser.parse", string="str", fmt="fmt_b", return_value=mock_datetime, ) result = self.parser._parse_multiformat("str", ["fmt_a", "fmt_b"]) assert result == mock_datetime def test_parse_multiformat_all_fail(self, mocker): mocker.patch( "arrow.parser.DateTimeParser.parse", string="str", fmt="fmt_a", side_effect=parser.ParserError, ) mocker.patch( "arrow.parser.DateTimeParser.parse", string="str", fmt="fmt_b", side_effect=parser.ParserError, ) with pytest.raises(parser.ParserError): self.parser._parse_multiformat("str", ["fmt_a", "fmt_b"]) def test_parse_multiformat_unself_expected_fail(self, mocker): class UnselfExpectedError(Exception): pass mocker.patch( "arrow.parser.DateTimeParser.parse", string="str", fmt="fmt_a", side_effect=UnselfExpectedError, ) with pytest.raises(UnselfExpectedError): self.parser._parse_multiformat("str", ["fmt_a", "fmt_b"]) def test_parse_token_nonsense(self): parts = {} self.parser._parse_token("NONSENSE", "1900", parts) assert parts == {} def test_parse_token_invalid_meridians(self): parts = {} self.parser._parse_token("A", "a..m", parts) assert parts == {} self.parser._parse_token("a", "p..m", parts) assert parts == {} def test_parser_no_caching(self, mocker): mocked_parser = mocker.patch( "arrow.parser.DateTimeParser._generate_pattern_re", fmt="fmt_a" ) self.parser = parser.DateTimeParser(cache_size=0) for _ in range(100): self.parser._generate_pattern_re("fmt_a") assert mocked_parser.call_count == 100 def test_parser_1_line_caching(self, mocker): mocked_parser = mocker.patch("arrow.parser.DateTimeParser._generate_pattern_re") self.parser = parser.DateTimeParser(cache_size=1) for _ in range(100): self.parser._generate_pattern_re(fmt="fmt_a") assert mocked_parser.call_count == 1 assert mocked_parser.call_args_list[0] == mocker.call(fmt="fmt_a") for _ in range(100): self.parser._generate_pattern_re(fmt="fmt_b") assert mocked_parser.call_count == 2 assert mocked_parser.call_args_list[1] == mocker.call(fmt="fmt_b") for _ in range(100): self.parser._generate_pattern_re(fmt="fmt_a") assert mocked_parser.call_count == 3 assert mocked_parser.call_args_list[2] == mocker.call(fmt="fmt_a") def test_parser_multiple_line_caching(self, mocker): mocked_parser = mocker.patch("arrow.parser.DateTimeParser._generate_pattern_re") self.parser = parser.DateTimeParser(cache_size=2) for _ in range(100): self.parser._generate_pattern_re(fmt="fmt_a") assert mocked_parser.call_count == 1 assert mocked_parser.call_args_list[0] == mocker.call(fmt="fmt_a") for _ in range(100): self.parser._generate_pattern_re(fmt="fmt_b") assert mocked_parser.call_count == 2 assert mocked_parser.call_args_list[1] == mocker.call(fmt="fmt_b") # fmt_a and fmt_b are in the cache, so no new calls should be made for _ in range(100): self.parser._generate_pattern_re(fmt="fmt_a") for _ in range(100): self.parser._generate_pattern_re(fmt="fmt_b") assert mocked_parser.call_count == 2 assert mocked_parser.call_args_list[0] == mocker.call(fmt="fmt_a") assert mocked_parser.call_args_list[1] == mocker.call(fmt="fmt_b") def test_YY_and_YYYY_format_list(self): assert self.parser.parse("15/01/19", ["DD/MM/YY", "DD/MM/YYYY"]) == datetime( 2019, 1, 15 ) # Regression test for issue #580 assert self.parser.parse("15/01/2019", ["DD/MM/YY", "DD/MM/YYYY"]) == datetime( 2019, 1, 15 ) assert ( self.parser.parse( "15/01/2019T04:05:06.789120Z", ["D/M/YYThh:mm:ss.SZ", "D/M/YYYYThh:mm:ss.SZ"], ) == datetime(2019, 1, 15, 4, 5, 6, 789120, tzinfo=tz.tzutc()) ) # regression test for issue #447 def test_timestamp_format_list(self): # should not match on the "X" token assert ( self.parser.parse( "15 Jul 2000", ["MM/DD/YYYY", "YYYY-MM-DD", "X", "DD-MMMM-YYYY", "D MMM YYYY"], ) == datetime(2000, 7, 15) ) with pytest.raises(ParserError): self.parser.parse("15 Jul", "X") @pytest.mark.usefixtures("dt_parser") class TestDateTimeParserParse: def test_parse_list(self, mocker): mocker.patch( "arrow.parser.DateTimeParser._parse_multiformat", string="str", formats=["fmt_a", "fmt_b"], return_value="result", ) result = self.parser.parse("str", ["fmt_a", "fmt_b"]) assert result == "result" def test_parse_unrecognized_token(self, mocker): mocker.patch.dict("arrow.parser.DateTimeParser._BASE_INPUT_RE_MAP") del arrow.parser.DateTimeParser._BASE_INPUT_RE_MAP["YYYY"] # need to make another local parser to apply patch changes _parser = parser.DateTimeParser() with pytest.raises(parser.ParserError): _parser.parse("2013-01-01", "YYYY-MM-DD") def test_parse_parse_no_match(self): with pytest.raises(ParserError): self.parser.parse("01-01", "YYYY-MM-DD") def test_parse_separators(self): with pytest.raises(ParserError): self.parser.parse("1403549231", "YYYY-MM-DD") def test_parse_numbers(self): self.expected = datetime(2012, 1, 1, 12, 5, 10) assert ( self.parser.parse("2012-01-01 12:05:10", "YYYY-MM-DD HH:mm:ss") == self.expected ) def test_parse_am(self): with pytest.raises(ParserMatchError): self.parser.parse("2021-01-30 14:00:00 AM", "YYYY-MM-DD HH:mm:ss A") def test_parse_year_two_digit(self): self.expected = datetime(1979, 1, 1, 12, 5, 10) assert ( self.parser.parse("79-01-01 12:05:10", "YY-MM-DD HH:mm:ss") == self.expected ) def test_parse_timestamp(self): tz_utc = tz.tzutc() float_timestamp = time.time() int_timestamp = int(float_timestamp) self.expected = datetime.fromtimestamp(int_timestamp, tz=tz_utc) assert self.parser.parse(f"{int_timestamp:d}", "X") == self.expected self.expected = datetime.fromtimestamp(float_timestamp, tz=tz_utc) assert self.parser.parse(f"{float_timestamp:f}", "X") == self.expected # test handling of ns timestamp (arrow will round to 6 digits regardless) self.expected = datetime.fromtimestamp(float_timestamp, tz=tz_utc) assert self.parser.parse(f"{float_timestamp:f}123", "X") == self.expected # test ps timestamp (arrow will round to 6 digits regardless) self.expected = datetime.fromtimestamp(float_timestamp, tz=tz_utc) assert self.parser.parse(f"{float_timestamp:f}123456", "X") == self.expected # NOTE: timestamps cannot be parsed from natural language strings (by removing the ^...$) because it will # break cases like "15 Jul 2000" and a format list (see issue #447) with pytest.raises(ParserError): natural_lang_string = "Meet me at {} at the restaurant.".format( float_timestamp ) self.parser.parse(natural_lang_string, "X") with pytest.raises(ParserError): self.parser.parse("1565982019.", "X") with pytest.raises(ParserError): self.parser.parse(".1565982019", "X") # NOTE: negative timestamps cannot be handled by datetime on Windows # Must use timedelta to handle them: https://stackoverflow.com/questions/36179914 @pytest.mark.skipif( os.name == "nt", reason="negative timestamps are not supported on Windows" ) def test_parse_negative_timestamp(self): # regression test for issue #662 tz_utc = tz.tzutc() float_timestamp = time.time() int_timestamp = int(float_timestamp) negative_int_timestamp = -int_timestamp self.expected = datetime.fromtimestamp(negative_int_timestamp, tz=tz_utc) assert self.parser.parse(f"{negative_int_timestamp:d}", "X") == self.expected negative_float_timestamp = -float_timestamp self.expected = datetime.fromtimestamp(negative_float_timestamp, tz=tz_utc) assert self.parser.parse(f"{negative_float_timestamp:f}", "X") == self.expected def test_parse_expanded_timestamp(self): # test expanded timestamps that include milliseconds # and microseconds as multiples rather than decimals # requested in issue #357 tz_utc = tz.tzutc() timestamp = 1569982581.413132 timestamp_milli = round(timestamp * 1000) timestamp_micro = round(timestamp * 1_000_000) # "x" token should parse integer timestamps below MAX_TIMESTAMP normally self.expected = datetime.fromtimestamp(int(timestamp), tz=tz_utc) assert self.parser.parse(f"{int(timestamp):d}", "x") == self.expected self.expected = datetime.fromtimestamp(round(timestamp, 3), tz=tz_utc) assert self.parser.parse(f"{timestamp_milli:d}", "x") == self.expected self.expected = datetime.fromtimestamp(timestamp, tz=tz_utc) assert self.parser.parse(f"{timestamp_micro:d}", "x") == self.expected # anything above max µs timestamp should fail with pytest.raises(ValueError): self.parser.parse(f"{int(MAX_TIMESTAMP_US) + 1:d}", "x") # floats are not allowed with the "x" token with pytest.raises(ParserMatchError): self.parser.parse(f"{timestamp:f}", "x") def test_parse_names(self): self.expected = datetime(2012, 1, 1) assert self.parser.parse("January 1, 2012", "MMMM D, YYYY") == self.expected assert self.parser.parse("Jan 1, 2012", "MMM D, YYYY") == self.expected def test_parse_pm(self): self.expected = datetime(1, 1, 1, 13, 0, 0) assert self.parser.parse("1 pm", "H a") == self.expected assert self.parser.parse("1 pm", "h a") == self.expected self.expected = datetime(1, 1, 1, 1, 0, 0) assert self.parser.parse("1 am", "H A") == self.expected assert self.parser.parse("1 am", "h A") == self.expected self.expected = datetime(1, 1, 1, 0, 0, 0) assert self.parser.parse("12 am", "H A") == self.expected assert self.parser.parse("12 am", "h A") == self.expected self.expected = datetime(1, 1, 1, 12, 0, 0) assert self.parser.parse("12 pm", "H A") == self.expected assert self.parser.parse("12 pm", "h A") == self.expected def test_parse_tz_hours_only(self): self.expected = datetime(2025, 10, 17, 5, 30, 10, tzinfo=tz.tzoffset(None, 0)) parsed = self.parser.parse("2025-10-17 05:30:10+00", "YYYY-MM-DD HH:mm:ssZ") assert parsed == self.expected def test_parse_tz_zz(self): self.expected = datetime(2013, 1, 1, tzinfo=tz.tzoffset(None, -7 * 3600)) assert self.parser.parse("2013-01-01 -07:00", "YYYY-MM-DD ZZ") == self.expected @pytest.mark.parametrize("full_tz_name", make_full_tz_list()) def test_parse_tz_name_zzz(self, full_tz_name): self.expected = datetime(2013, 1, 1, tzinfo=tz.gettz(full_tz_name)) assert ( self.parser.parse(f"2013-01-01 {full_tz_name}", "YYYY-MM-DD ZZZ") == self.expected ) # note that offsets are not timezones with pytest.raises(ParserError): self.parser.parse("2013-01-01 12:30:45.9+1000", "YYYY-MM-DDZZZ") with pytest.raises(ParserError): self.parser.parse("2013-01-01 12:30:45.9+10:00", "YYYY-MM-DDZZZ") with pytest.raises(ParserError): self.parser.parse("2013-01-01 12:30:45.9-10", "YYYY-MM-DDZZZ") def test_parse_subsecond(self): self.expected = datetime(2013, 1, 1, 12, 30, 45, 900000) assert ( self.parser.parse("2013-01-01 12:30:45.9", "YYYY-MM-DD HH:mm:ss.S") == self.expected ) self.expected = datetime(2013, 1, 1, 12, 30, 45, 980000) assert ( self.parser.parse("2013-01-01 12:30:45.98", "YYYY-MM-DD HH:mm:ss.SS") == self.expected ) self.expected = datetime(2013, 1, 1, 12, 30, 45, 987000) assert ( self.parser.parse("2013-01-01 12:30:45.987", "YYYY-MM-DD HH:mm:ss.SSS") == self.expected ) self.expected = datetime(2013, 1, 1, 12, 30, 45, 987600) assert ( self.parser.parse("2013-01-01 12:30:45.9876", "YYYY-MM-DD HH:mm:ss.SSSS") == self.expected ) self.expected = datetime(2013, 1, 1, 12, 30, 45, 987650) assert ( self.parser.parse("2013-01-01 12:30:45.98765", "YYYY-MM-DD HH:mm:ss.SSSSS") == self.expected ) self.expected = datetime(2013, 1, 1, 12, 30, 45, 987654) assert ( self.parser.parse( "2013-01-01 12:30:45.987654", "YYYY-MM-DD HH:mm:ss.SSSSSS" ) == self.expected ) def test_parse_subsecond_rounding(self): self.expected = datetime(2013, 1, 1, 12, 30, 45, 987654) datetime_format = "YYYY-MM-DD HH:mm:ss.S" # round up string = "2013-01-01 12:30:45.9876539" assert self.parser.parse(string, datetime_format) == self.expected assert self.parser.parse_iso(string) == self.expected # round down string = "2013-01-01 12:30:45.98765432" assert self.parser.parse(string, datetime_format) == self.expected assert self.parser.parse_iso(string) == self.expected # round half-up string = "2013-01-01 12:30:45.987653521" assert self.parser.parse(string, datetime_format) == self.expected assert self.parser.parse_iso(string) == self.expected # round half-down string = "2013-01-01 12:30:45.9876545210" assert self.parser.parse(string, datetime_format) == self.expected assert self.parser.parse_iso(string) == self.expected # overflow (zero out the subseconds and increment the seconds) # regression tests for issue #636 def test_parse_subsecond_rounding_overflow(self): datetime_format = "YYYY-MM-DD HH:mm:ss.S" self.expected = datetime(2013, 1, 1, 12, 30, 46) string = "2013-01-01 12:30:45.9999995" assert self.parser.parse(string, datetime_format) == self.expected assert self.parser.parse_iso(string) == self.expected self.expected = datetime(2013, 1, 1, 12, 31, 0) string = "2013-01-01 12:30:59.9999999" assert self.parser.parse(string, datetime_format) == self.expected assert self.parser.parse_iso(string) == self.expected self.expected = datetime(2013, 1, 2, 0, 0, 0) string = "2013-01-01 23:59:59.9999999" assert self.parser.parse(string, datetime_format) == self.expected assert self.parser.parse_iso(string) == self.expected # 6 digits should remain unrounded self.expected = datetime(2013, 1, 1, 12, 30, 45, 999999) string = "2013-01-01 12:30:45.999999" assert self.parser.parse(string, datetime_format) == self.expected assert self.parser.parse_iso(string) == self.expected # Regression tests for issue #560 def test_parse_long_year(self): with pytest.raises(ParserError): self.parser.parse("09 January 123456789101112", "DD MMMM YYYY") with pytest.raises(ParserError): self.parser.parse("123456789101112 09 January", "YYYY DD MMMM") with pytest.raises(ParserError): self.parser.parse("68096653015/01/19", "YY/M/DD") def test_parse_with_extra_words_at_start_and_end_invalid(self): input_format_pairs = [ ("blah2016", "YYYY"), ("blah2016blah", "YYYY"), ("2016blah", "YYYY"), ("2016-05blah", "YYYY-MM"), ("2016-05-16blah", "YYYY-MM-DD"), ("2016-05-16T04:05:06.789120blah", "YYYY-MM-DDThh:mm:ss.S"), ("2016-05-16T04:05:06.789120ZblahZ", "YYYY-MM-DDThh:mm:ss.SZ"), ("2016-05-16T04:05:06.789120Zblah", "YYYY-MM-DDThh:mm:ss.SZ"), ("2016-05-16T04:05:06.789120blahZ", "YYYY-MM-DDThh:mm:ss.SZ"), ] for pair in input_format_pairs: with pytest.raises(ParserError): self.parser.parse(pair[0], pair[1]) def test_parse_with_extra_words_at_start_and_end_valid(self): # Spaces surrounding the parsable date are ok because we # allow the parsing of natural language input. Additionally, a single # character of specific punctuation before or after the date is okay. # See docs for full list of valid punctuation. assert self.parser.parse("blah 2016 blah", "YYYY") == datetime(2016, 1, 1) assert self.parser.parse("blah 2016", "YYYY") == datetime(2016, 1, 1) assert self.parser.parse("2016 blah", "YYYY") == datetime(2016, 1, 1) # test one additional space along with space divider assert self.parser.parse( "blah 2016-05-16 04:05:06.789120", "YYYY-MM-DD hh:mm:ss.S" ) == datetime(2016, 5, 16, 4, 5, 6, 789120) assert self.parser.parse( "2016-05-16 04:05:06.789120 blah", "YYYY-MM-DD hh:mm:ss.S" ) == datetime(2016, 5, 16, 4, 5, 6, 789120) # test one additional space along with T divider assert self.parser.parse( "blah 2016-05-16T04:05:06.789120", "YYYY-MM-DDThh:mm:ss.S" ) == datetime(2016, 5, 16, 4, 5, 6, 789120) assert self.parser.parse( "2016-05-16T04:05:06.789120 blah", "YYYY-MM-DDThh:mm:ss.S" ) == datetime(2016, 5, 16, 4, 5, 6, 789120) assert ( self.parser.parse( "Meet me at 2016-05-16T04:05:06.789120 at the restaurant.", "YYYY-MM-DDThh:mm:ss.S", ) == datetime(2016, 5, 16, 4, 5, 6, 789120) ) assert ( self.parser.parse( "Meet me at 2016-05-16 04:05:06.789120 at the restaurant.", "YYYY-MM-DD hh:mm:ss.S", ) == datetime(2016, 5, 16, 4, 5, 6, 789120) ) # regression test for issue #701 # tests cases of a partial match surrounded by punctuation # for the list of valid punctuation, see documentation def test_parse_with_punctuation_fences(self): assert self.parser.parse( "Meet me at my house on Halloween (2019-31-10)", "YYYY-DD-MM" ) == datetime(2019, 10, 31) assert self.parser.parse( "Monday, 9. September 2019, 16:15-20:00", "dddd, D. MMMM YYYY" ) == datetime(2019, 9, 9) assert self.parser.parse("A date is 11.11.2011.", "DD.MM.YYYY") == datetime( 2011, 11, 11 ) with pytest.raises(ParserMatchError): self.parser.parse("11.11.2011.1 is not a valid date.", "DD.MM.YYYY") with pytest.raises(ParserMatchError): self.parser.parse( "This date has too many punctuation marks following it (11.11.2011).", "DD.MM.YYYY", ) def test_parse_with_leading_and_trailing_whitespace(self): assert self.parser.parse(" 2016", "YYYY") == datetime(2016, 1, 1) assert self.parser.parse("2016 ", "YYYY") == datetime(2016, 1, 1) assert self.parser.parse(" 2016 ", "YYYY") == datetime(2016, 1, 1) assert self.parser.parse( " 2016-05-16 04:05:06.789120 ", "YYYY-MM-DD hh:mm:ss.S" ) == datetime(2016, 5, 16, 4, 5, 6, 789120) assert self.parser.parse( " 2016-05-16T04:05:06.789120 ", "YYYY-MM-DDThh:mm:ss.S" ) == datetime(2016, 5, 16, 4, 5, 6, 789120) def test_parse_YYYY_DDDD(self): assert self.parser.parse("1998-136", "YYYY-DDDD") == datetime(1998, 5, 16) assert self.parser.parse("1998-006", "YYYY-DDDD") == datetime(1998, 1, 6) with pytest.raises(ParserError): self.parser.parse("1998-456", "YYYY-DDDD") def test_parse_YYYY_DDD(self): assert self.parser.parse("1998-6", "YYYY-DDD") == datetime(1998, 1, 6) assert self.parser.parse("1998-136", "YYYY-DDD") == datetime(1998, 5, 16) with pytest.raises(ParserError): self.parser.parse("1998-756", "YYYY-DDD") # month cannot be passed with DDD and DDDD tokens def test_parse_YYYY_MM_DDDD(self): with pytest.raises(ParserError): self.parser.parse("2015-01-009", "YYYY-MM-DDDD") # year is required with the DDD and DDDD tokens def test_parse_DDD_only(self): with pytest.raises(ParserError): self.parser.parse("5", "DDD") def test_parse_DDDD_only(self): with pytest.raises(ParserError): self.parser.parse("145", "DDDD") def test_parse_ddd_and_dddd(self): fr_parser = parser.DateTimeParser("fr") # Day of week should be ignored when a day is passed # 2019-10-17 is a Thursday, so we know day of week # is ignored if the same date is outputted expected = datetime(2019, 10, 17) assert self.parser.parse("Tue 2019-10-17", "ddd YYYY-MM-DD") == expected assert fr_parser.parse("mar 2019-10-17", "ddd YYYY-MM-DD") == expected assert self.parser.parse("Tuesday 2019-10-17", "dddd YYYY-MM-DD") == expected assert fr_parser.parse("mardi 2019-10-17", "dddd YYYY-MM-DD") == expected # Get first Tuesday after epoch expected = datetime(1970, 1, 6) assert self.parser.parse("Tue", "ddd") == expected assert fr_parser.parse("mar", "ddd") == expected assert self.parser.parse("Tuesday", "dddd") == expected assert fr_parser.parse("mardi", "dddd") == expected # Get first Tuesday in 2020 expected = datetime(2020, 1, 7) assert self.parser.parse("Tue 2020", "ddd YYYY") == expected assert fr_parser.parse("mar 2020", "ddd YYYY") == expected assert self.parser.parse("Tuesday 2020", "dddd YYYY") == expected assert fr_parser.parse("mardi 2020", "dddd YYYY") == expected # Get first Tuesday in February 2020 expected = datetime(2020, 2, 4) assert self.parser.parse("Tue 02 2020", "ddd MM YYYY") == expected assert fr_parser.parse("mar 02 2020", "ddd MM YYYY") == expected assert self.parser.parse("Tuesday 02 2020", "dddd MM YYYY") == expected assert fr_parser.parse("mardi 02 2020", "dddd MM YYYY") == expected # Get first Tuesday in February after epoch expected = datetime(1970, 2, 3) assert self.parser.parse("Tue 02", "ddd MM") == expected assert fr_parser.parse("mar 02", "ddd MM") == expected assert self.parser.parse("Tuesday 02", "dddd MM") == expected assert fr_parser.parse("mardi 02", "dddd MM") == expected # Times remain intact expected = datetime(2020, 2, 4, 10, 25, 54, 123456, tz.tzoffset(None, -3600)) assert ( self.parser.parse( "Tue 02 2020 10:25:54.123456-01:00", "ddd MM YYYY HH:mm:ss.SZZ" ) == expected ) assert ( fr_parser.parse( "mar 02 2020 10:25:54.123456-01:00", "ddd MM YYYY HH:mm:ss.SZZ" ) == expected ) assert ( self.parser.parse( "Tuesday 02 2020 10:25:54.123456-01:00", "dddd MM YYYY HH:mm:ss.SZZ" ) == expected ) assert ( fr_parser.parse( "mardi 02 2020 10:25:54.123456-01:00", "dddd MM YYYY HH:mm:ss.SZZ" ) == expected ) def test_parse_ddd_and_dddd_ignore_case(self): # Regression test for issue #851 expected = datetime(2019, 6, 24) assert ( self.parser.parse("MONDAY, June 24, 2019", "dddd, MMMM DD, YYYY") == expected ) def test_parse_ddd_and_dddd_then_format(self): # Regression test for issue #446 arw_formatter = formatter.DateTimeFormatter() assert arw_formatter.format(self.parser.parse("Mon", "ddd"), "ddd") == "Mon" assert ( arw_formatter.format(self.parser.parse("Monday", "dddd"), "dddd") == "Monday" ) assert arw_formatter.format(self.parser.parse("Tue", "ddd"), "ddd") == "Tue" assert ( arw_formatter.format(self.parser.parse("Tuesday", "dddd"), "dddd") == "Tuesday" ) assert arw_formatter.format(self.parser.parse("Wed", "ddd"), "ddd") == "Wed" assert ( arw_formatter.format(self.parser.parse("Wednesday", "dddd"), "dddd") == "Wednesday" ) assert arw_formatter.format(self.parser.parse("Thu", "ddd"), "ddd") == "Thu" assert ( arw_formatter.format(self.parser.parse("Thursday", "dddd"), "dddd") == "Thursday" ) assert arw_formatter.format(self.parser.parse("Fri", "ddd"), "ddd") == "Fri" assert ( arw_formatter.format(self.parser.parse("Friday", "dddd"), "dddd") == "Friday" ) assert arw_formatter.format(self.parser.parse("Sat", "ddd"), "ddd") == "Sat" assert ( arw_formatter.format(self.parser.parse("Saturday", "dddd"), "dddd") == "Saturday" ) assert arw_formatter.format(self.parser.parse("Sun", "ddd"), "ddd") == "Sun" assert ( arw_formatter.format(self.parser.parse("Sunday", "dddd"), "dddd") == "Sunday" ) def test_parse_HH_24(self): assert self.parser.parse( "2019-10-30T24:00:00", "YYYY-MM-DDTHH:mm:ss" ) == datetime(2019, 10, 31, 0, 0, 0, 0) assert self.parser.parse("2019-10-30T24:00", "YYYY-MM-DDTHH:mm") == datetime( 2019, 10, 31, 0, 0, 0, 0 ) assert self.parser.parse("2019-10-30T24", "YYYY-MM-DDTHH") == datetime( 2019, 10, 31, 0, 0, 0, 0 ) assert self.parser.parse( "2019-10-30T24:00:00.0", "YYYY-MM-DDTHH:mm:ss.S" ) == datetime(2019, 10, 31, 0, 0, 0, 0) assert self.parser.parse( "2019-10-31T24:00:00", "YYYY-MM-DDTHH:mm:ss" ) == datetime(2019, 11, 1, 0, 0, 0, 0) assert self.parser.parse( "2019-12-31T24:00:00", "YYYY-MM-DDTHH:mm:ss" ) == datetime(2020, 1, 1, 0, 0, 0, 0) assert self.parser.parse( "2019-12-31T23:59:59.9999999", "YYYY-MM-DDTHH:mm:ss.S" ) == datetime(2020, 1, 1, 0, 0, 0, 0) with pytest.raises(ParserError): self.parser.parse("2019-12-31T24:01:00", "YYYY-MM-DDTHH:mm:ss") with pytest.raises(ParserError): self.parser.parse("2019-12-31T24:00:01", "YYYY-MM-DDTHH:mm:ss") with pytest.raises(ParserError): self.parser.parse("2019-12-31T24:00:00.1", "YYYY-MM-DDTHH:mm:ss.S") with pytest.raises(ParserError): self.parser.parse("2019-12-31T24:00:00.999999", "YYYY-MM-DDTHH:mm:ss.S") def test_parse_W(self): assert self.parser.parse("2011-W05-4", "W") == datetime(2011, 2, 3) assert self.parser.parse("2011W054", "W") == datetime(2011, 2, 3) assert self.parser.parse("2011-W05", "W") == datetime(2011, 1, 31) assert self.parser.parse("2011W05", "W") == datetime(2011, 1, 31) assert self.parser.parse("2011-W05-4T14:17:01", "WTHH:mm:ss") == datetime( 2011, 2, 3, 14, 17, 1 ) assert self.parser.parse("2011W054T14:17:01", "WTHH:mm:ss") == datetime( 2011, 2, 3, 14, 17, 1 ) assert self.parser.parse("2011-W05T14:17:01", "WTHH:mm:ss") == datetime( 2011, 1, 31, 14, 17, 1 ) assert self.parser.parse("2011W05T141701", "WTHHmmss") == datetime( 2011, 1, 31, 14, 17, 1 ) assert self.parser.parse("2011W054T141701", "WTHHmmss") == datetime( 2011, 2, 3, 14, 17, 1 ) bad_formats = [ "201W22", "1995-W1-4", "2001-W34-90", "2001--W34", "2011-W03--3", "thstrdjtrsrd676776r65", "2002-W66-1T14:17:01", "2002-W23-03T14:17:01", ] for fmt in bad_formats: with pytest.raises(ParserError): self.parser.parse(fmt, "W") def test_parse_normalize_whitespace(self): assert self.parser.parse( "Jun 1 2005 1:33PM", "MMM D YYYY H:mmA", normalize_whitespace=True ) == datetime(2005, 6, 1, 13, 33) with pytest.raises(ParserError): self.parser.parse("Jun 1 2005 1:33PM", "MMM D YYYY H:mmA") assert ( self.parser.parse( "\t 2013-05-05 T \n 12:30:45\t123456 \t \n", "YYYY-MM-DD T HH:mm:ss S", normalize_whitespace=True, ) == datetime(2013, 5, 5, 12, 30, 45, 123456) ) with pytest.raises(ParserError): self.parser.parse( "\t 2013-05-05 T \n 12:30:45\t123456 \t \n", "YYYY-MM-DD T HH:mm:ss S", ) assert self.parser.parse( " \n Jun 1\t 2005\n ", "MMM D YYYY", normalize_whitespace=True ) == datetime(2005, 6, 1) with pytest.raises(ParserError): self.parser.parse(" \n Jun 1\t 2005\n ", "MMM D YYYY") @pytest.mark.usefixtures("dt_parser_regex") class TestDateTimeParserRegex: def test_format_year(self): assert self.format_regex.findall("YYYY-YY") == ["YYYY", "YY"] def test_format_month(self): assert self.format_regex.findall("MMMM-MMM-MM-M") == ["MMMM", "MMM", "MM", "M"] def test_format_day(self): assert self.format_regex.findall("DDDD-DDD-DD-D") == ["DDDD", "DDD", "DD", "D"] def test_format_hour(self): assert self.format_regex.findall("HH-H-hh-h") == ["HH", "H", "hh", "h"] def test_format_minute(self): assert self.format_regex.findall("mm-m") == ["mm", "m"] def test_format_second(self): assert self.format_regex.findall("ss-s") == ["ss", "s"] def test_format_subsecond(self): assert self.format_regex.findall("SSSSSS-SSSSS-SSSS-SSS-SS-S") == [ "SSSSSS", "SSSSS", "SSSS", "SSS", "SS", "S", ] def test_format_tz(self): assert self.format_regex.findall("ZZZ-ZZ-Z") == ["ZZZ", "ZZ", "Z"] def test_format_am_pm(self): assert self.format_regex.findall("A-a") == ["A", "a"] def test_format_timestamp(self): assert self.format_regex.findall("X") == ["X"] def test_format_timestamp_milli(self): assert self.format_regex.findall("x") == ["x"] def test_escape(self): escape_regex = parser.DateTimeParser._ESCAPE_RE assert escape_regex.findall("2018-03-09 8 [h] 40 [hello]") == ["[h]", "[hello]"] def test_month_names(self): p = parser.DateTimeParser("en-us") text = "_".join(calendar.month_name[1:]) result = p._input_re_map["MMMM"].findall(text) assert result == calendar.month_name[1:] def test_month_abbreviations(self): p = parser.DateTimeParser("en-us") text = "_".join(calendar.month_abbr[1:]) result = p._input_re_map["MMM"].findall(text) assert result == calendar.month_abbr[1:] def test_digits(self): assert parser.DateTimeParser._ONE_OR_TWO_DIGIT_RE.findall("4-56") == ["4", "56"] assert parser.DateTimeParser._ONE_OR_TWO_OR_THREE_DIGIT_RE.findall( "4-56-789" ) == ["4", "56", "789"] assert parser.DateTimeParser._ONE_OR_MORE_DIGIT_RE.findall( "4-56-789-1234-12345" ) == ["4", "56", "789", "1234", "12345"] assert parser.DateTimeParser._TWO_DIGIT_RE.findall("12-3-45") == ["12", "45"] assert parser.DateTimeParser._THREE_DIGIT_RE.findall("123-4-56") == ["123"] assert parser.DateTimeParser._FOUR_DIGIT_RE.findall("1234-56") == ["1234"] def test_tz(self): tz_z_re = parser.DateTimeParser._TZ_Z_RE assert tz_z_re.findall("-0700") == [("-", "07", "00")] assert tz_z_re.findall("+07") == [("+", "07", "")] assert tz_z_re.search("15/01/2019T04:05:06.789120Z") is not None assert tz_z_re.search("15/01/2019T04:05:06.789120") is None tz_zz_re = parser.DateTimeParser._TZ_ZZ_RE assert tz_zz_re.findall("-07:00") == [("-", "07", "00")] assert tz_zz_re.findall("+07") == [("+", "07", "")] assert tz_zz_re.search("15/01/2019T04:05:06.789120Z") is not None assert tz_zz_re.search("15/01/2019T04:05:06.789120") is None tz_name_re = parser.DateTimeParser._TZ_NAME_RE assert tz_name_re.findall("Europe/Warsaw") == ["Europe/Warsaw"] assert tz_name_re.findall("GMT") == ["GMT"] def test_timestamp(self): timestamp_re = parser.DateTimeParser._TIMESTAMP_RE assert timestamp_re.findall("1565707550.452729") == ["1565707550.452729"] assert timestamp_re.findall("-1565707550.452729") == ["-1565707550.452729"] assert timestamp_re.findall("-1565707550") == ["-1565707550"] assert timestamp_re.findall("1565707550") == ["1565707550"] assert timestamp_re.findall("1565707550.") == [] assert timestamp_re.findall(".1565707550") == [] def test_timestamp_milli(self): timestamp_expanded_re = parser.DateTimeParser._TIMESTAMP_EXPANDED_RE assert timestamp_expanded_re.findall("-1565707550") == ["-1565707550"] assert timestamp_expanded_re.findall("1565707550") == ["1565707550"] assert timestamp_expanded_re.findall("1565707550.452729") == [] assert timestamp_expanded_re.findall("1565707550.") == [] assert timestamp_expanded_re.findall(".1565707550") == [] def test_time(self): time_re = parser.DateTimeParser._TIME_RE time_seperators = [":", ""] for sep in time_seperators: assert time_re.findall("12") == [("12", "", "", "", "")] assert time_re.findall(f"12{sep}35") == [("12", "35", "", "", "")] assert time_re.findall("12{sep}35{sep}46".format(sep=sep)) == [ ("12", "35", "46", "", "") ] assert time_re.findall("12{sep}35{sep}46.952313".format(sep=sep)) == [ ("12", "35", "46", ".", "952313") ] assert time_re.findall("12{sep}35{sep}46,952313".format(sep=sep)) == [ ("12", "35", "46", ",", "952313") ] assert time_re.findall("12:") == [] assert time_re.findall("12:35:46.") == [] assert time_re.findall("12:35:46,") == [] @pytest.mark.usefixtures("dt_parser") class TestDateTimeParserISO: def test_YYYY(self): assert self.parser.parse_iso("2013") == datetime(2013, 1, 1) def test_YYYY_DDDD(self): assert self.parser.parse_iso("1998-136") == datetime(1998, 5, 16) assert self.parser.parse_iso("1998-006") == datetime(1998, 1, 6) with pytest.raises(ParserError): self.parser.parse_iso("1998-456") # 2016 is a leap year, so Feb 29 exists (leap day) assert self.parser.parse_iso("2016-059") == datetime(2016, 2, 28) assert self.parser.parse_iso("2016-060") == datetime(2016, 2, 29) assert self.parser.parse_iso("2016-061") == datetime(2016, 3, 1) # 2017 is not a leap year, so Feb 29 does not exist assert self.parser.parse_iso("2017-059") == datetime(2017, 2, 28) assert self.parser.parse_iso("2017-060") == datetime(2017, 3, 1) assert self.parser.parse_iso("2017-061") == datetime(2017, 3, 2) # Since 2016 is a leap year, the 366th day falls in the same year assert self.parser.parse_iso("2016-366") == datetime(2016, 12, 31) # Since 2017 is not a leap year, the 366th day falls in the next year assert self.parser.parse_iso("2017-366") == datetime(2018, 1, 1) def test_YYYY_DDDD_HH_mm_ssZ(self): assert self.parser.parse_iso("2013-036 04:05:06+01:00") == datetime( 2013, 2, 5, 4, 5, 6, tzinfo=tz.tzoffset(None, 3600) ) assert self.parser.parse_iso("2013-036 04:05:06Z") == datetime( 2013, 2, 5, 4, 5, 6, tzinfo=tz.tzutc() ) def test_YYYY_MM_DDDD(self): with pytest.raises(ParserError): self.parser.parse_iso("2014-05-125") def test_YYYY_MM(self): for separator in DateTimeParser.SEPARATORS: assert self.parser.parse_iso(separator.join(("2013", "02"))) == datetime( 2013, 2, 1 ) def test_YYYY_MM_DD(self): for separator in DateTimeParser.SEPARATORS: assert self.parser.parse_iso( separator.join(("2013", "02", "03")) ) == datetime(2013, 2, 3) def test_YYYY_MM_DDTHH_mmZ(self): assert self.parser.parse_iso("2013-02-03T04:05+01:00") == datetime( 2013, 2, 3, 4, 5, tzinfo=tz.tzoffset(None, 3600) ) def test_YYYY_MM_DDTHH_mm(self): assert self.parser.parse_iso("2013-02-03T04:05") == datetime(2013, 2, 3, 4, 5) def test_YYYY_MM_DDTHH(self): assert self.parser.parse_iso("2013-02-03T04") == datetime(2013, 2, 3, 4) def test_YYYY_MM_DDTHHZ(self): assert self.parser.parse_iso("2013-02-03T04+01:00") == datetime( 2013, 2, 3, 4, tzinfo=tz.tzoffset(None, 3600) ) def test_YYYY_MM_DDTHH_mm_ssZ(self): assert self.parser.parse_iso("2013-02-03T04:05:06+01:00") == datetime( 2013, 2, 3, 4, 5, 6, tzinfo=tz.tzoffset(None, 3600) ) def test_YYYY_MM_DDTHH_mm_ss(self): assert self.parser.parse_iso("2013-02-03T04:05:06") == datetime( 2013, 2, 3, 4, 5, 6 ) def test_YYYY_MM_DD_HH_mmZ(self): assert self.parser.parse_iso("2013-02-03 04:05+01:00") == datetime( 2013, 2, 3, 4, 5, tzinfo=tz.tzoffset(None, 3600) ) def test_YYYY_MM_DD_HH_mm(self): assert self.parser.parse_iso("2013-02-03 04:05") == datetime(2013, 2, 3, 4, 5) def test_YYYY_MM_DD_HH(self): assert self.parser.parse_iso("2013-02-03 04") == datetime(2013, 2, 3, 4) def test_invalid_time(self): with pytest.raises(ParserError): self.parser.parse_iso("2013-02-03T") with pytest.raises(ParserError): self.parser.parse_iso("2013-02-03 044") with pytest.raises(ParserError): self.parser.parse_iso("2013-02-03 04:05:06.") def test_YYYY_MM_DD_HH_mm_ssZ(self): assert self.parser.parse_iso("2013-02-03 04:05:06+01:00") == datetime( 2013, 2, 3, 4, 5, 6, tzinfo=tz.tzoffset(None, 3600) ) def test_YYYY_MM_DD_HH_mm_ss(self): assert self.parser.parse_iso("2013-02-03 04:05:06") == datetime( 2013, 2, 3, 4, 5, 6 ) def test_YYYY_MM_DDTHH_mm_ss_S(self): assert self.parser.parse_iso("2013-02-03T04:05:06.7") == datetime( 2013, 2, 3, 4, 5, 6, 700000 ) assert self.parser.parse_iso("2013-02-03T04:05:06.78") == datetime( 2013, 2, 3, 4, 5, 6, 780000 ) assert self.parser.parse_iso("2013-02-03T04:05:06.789") == datetime( 2013, 2, 3, 4, 5, 6, 789000 ) assert self.parser.parse_iso("2013-02-03T04:05:06.7891") == datetime( 2013, 2, 3, 4, 5, 6, 789100 ) assert self.parser.parse_iso("2013-02-03T04:05:06.78912") == datetime( 2013, 2, 3, 4, 5, 6, 789120 ) # ISO 8601:2004(E), ISO, 2004-12-01, 4.2.2.4 ... the decimal fraction # shall be divided from the integer part by the decimal sign specified # in ISO 31-0, i.e. the comma [,] or full stop [.]. Of these, the comma # is the preferred sign. assert self.parser.parse_iso("2013-02-03T04:05:06,789123678") == datetime( 2013, 2, 3, 4, 5, 6, 789124 ) # there is no limit on the number of decimal places assert self.parser.parse_iso("2013-02-03T04:05:06.789123678") == datetime( 2013, 2, 3, 4, 5, 6, 789124 ) def test_YYYY_MM_DDTHH_mm_ss_SZ(self): assert self.parser.parse_iso("2013-02-03T04:05:06.7+01:00") == datetime( 2013, 2, 3, 4, 5, 6, 700000, tzinfo=tz.tzoffset(None, 3600) ) assert self.parser.parse_iso("2013-02-03T04:05:06.78+01:00") == datetime( 2013, 2, 3, 4, 5, 6, 780000, tzinfo=tz.tzoffset(None, 3600) ) assert self.parser.parse_iso("2013-02-03T04:05:06.789+01:00") == datetime( 2013, 2, 3, 4, 5, 6, 789000, tzinfo=tz.tzoffset(None, 3600) ) assert self.parser.parse_iso("2013-02-03T04:05:06.7891+01:00") == datetime( 2013, 2, 3, 4, 5, 6, 789100, tzinfo=tz.tzoffset(None, 3600) ) assert self.parser.parse_iso("2013-02-03T04:05:06.78912+01:00") == datetime( 2013, 2, 3, 4, 5, 6, 789120, tzinfo=tz.tzoffset(None, 3600) ) assert self.parser.parse_iso("2013-02-03 04:05:06.78912Z") == datetime( 2013, 2, 3, 4, 5, 6, 789120, tzinfo=tz.tzutc() ) def test_W(self): assert self.parser.parse_iso("2011-W05-4") == datetime(2011, 2, 3) assert self.parser.parse_iso("2011-W05-4T14:17:01") == datetime( 2011, 2, 3, 14, 17, 1 ) assert self.parser.parse_iso("2011W054") == datetime(2011, 2, 3) assert self.parser.parse_iso("2011W054T141701") == datetime( 2011, 2, 3, 14, 17, 1 ) def test_invalid_Z(self): with pytest.raises(ParserError): self.parser.parse_iso("2013-02-03T04:05:06.78912z") with pytest.raises(ParserError): self.parser.parse_iso("2013-02-03T04:05:06.78912zz") with pytest.raises(ParserError): self.parser.parse_iso("2013-02-03T04:05:06.78912Zz") with pytest.raises(ParserError): self.parser.parse_iso("2013-02-03T04:05:06.78912ZZ") with pytest.raises(ParserError): self.parser.parse_iso("2013-02-03T04:05:06.78912+Z") with pytest.raises(ParserError): self.parser.parse_iso("2013-02-03T04:05:06.78912-Z") with pytest.raises(ParserError): self.parser.parse_iso("2013-02-03T04:05:06.78912 Z") def test_parse_subsecond(self): self.expected = datetime(2013, 1, 1, 12, 30, 45, 900000) assert self.parser.parse_iso("2013-01-01 12:30:45.9") == self.expected self.expected = datetime(2013, 1, 1, 12, 30, 45, 980000) assert self.parser.parse_iso("2013-01-01 12:30:45.98") == self.expected self.expected = datetime(2013, 1, 1, 12, 30, 45, 987000) assert self.parser.parse_iso("2013-01-01 12:30:45.987") == self.expected self.expected = datetime(2013, 1, 1, 12, 30, 45, 987600) assert self.parser.parse_iso("2013-01-01 12:30:45.9876") == self.expected self.expected = datetime(2013, 1, 1, 12, 30, 45, 987650) assert self.parser.parse_iso("2013-01-01 12:30:45.98765") == self.expected self.expected = datetime(2013, 1, 1, 12, 30, 45, 987654) assert self.parser.parse_iso("2013-01-01 12:30:45.987654") == self.expected # use comma as subsecond separator self.expected = datetime(2013, 1, 1, 12, 30, 45, 987654) assert self.parser.parse_iso("2013-01-01 12:30:45,987654") == self.expected def test_gnu_date(self): """Regression tests for parsing output from GNU date.""" # date -Ins assert self.parser.parse_iso("2016-11-16T09:46:30,895636557-0800") == datetime( 2016, 11, 16, 9, 46, 30, 895636, tzinfo=tz.tzoffset(None, -3600 * 8) ) # date --rfc-3339=ns assert self.parser.parse_iso("2016-11-16 09:51:14.682141526-08:00") == datetime( 2016, 11, 16, 9, 51, 14, 682142, tzinfo=tz.tzoffset(None, -3600 * 8) ) def test_isoformat(self): dt = datetime.utcnow() assert self.parser.parse_iso(dt.isoformat()) == dt def test_parse_iso_normalize_whitespace(self): assert self.parser.parse_iso( "2013-036 \t 04:05:06Z", normalize_whitespace=True ) == datetime(2013, 2, 5, 4, 5, 6, tzinfo=tz.tzutc()) with pytest.raises(ParserError): self.parser.parse_iso("2013-036 \t 04:05:06Z") assert self.parser.parse_iso( "\t 2013-05-05T12:30:45.123456 \t \n", normalize_whitespace=True ) == datetime(2013, 5, 5, 12, 30, 45, 123456) with pytest.raises(ParserError): self.parser.parse_iso("\t 2013-05-05T12:30:45.123456 \t \n") def test_parse_iso_with_leading_and_trailing_whitespace(self): datetime_string = " 2016-11-15T06:37:19.123456" with pytest.raises(ParserError): self.parser.parse_iso(datetime_string) datetime_string = " 2016-11-15T06:37:19.123456 " with pytest.raises(ParserError): self.parser.parse_iso(datetime_string) datetime_string = "2016-11-15T06:37:19.123456 " with pytest.raises(ParserError): self.parser.parse_iso(datetime_string) datetime_string = "2016-11-15T 06:37:19.123456" with pytest.raises(ParserError): self.parser.parse_iso(datetime_string) # leading whitespace datetime_string = " 2016-11-15 06:37:19.123456" with pytest.raises(ParserError): self.parser.parse_iso(datetime_string) # trailing whitespace datetime_string = "2016-11-15 06:37:19.123456 " with pytest.raises(ParserError): self.parser.parse_iso(datetime_string) datetime_string = " 2016-11-15 06:37:19.123456 " with pytest.raises(ParserError): self.parser.parse_iso(datetime_string) # two dividing spaces datetime_string = "2016-11-15 06:37:19.123456" with pytest.raises(ParserError): self.parser.parse_iso(datetime_string) def test_parse_iso_with_extra_words_at_start_and_end_invalid(self): test_inputs = [ "blah2016", "blah2016blah", "blah 2016 blah", "blah 2016", "2016 blah", "blah 2016-05-16 04:05:06.789120", "2016-05-16 04:05:06.789120 blah", "blah 2016-05-16T04:05:06.789120", "2016-05-16T04:05:06.789120 blah", "2016blah", "2016-05blah", "2016-05-16blah", "2016-05-16T04:05:06.789120blah", "2016-05-16T04:05:06.789120ZblahZ", "2016-05-16T04:05:06.789120Zblah", "2016-05-16T04:05:06.789120blahZ", "Meet me at 2016-05-16T04:05:06.789120 at the restaurant.", "Meet me at 2016-05-16 04:05:06.789120 at the restaurant.", ] for ti in test_inputs: with pytest.raises(ParserError): self.parser.parse_iso(ti) def test_iso8601_basic_format(self): assert self.parser.parse_iso("20180517") == datetime(2018, 5, 17) assert self.parser.parse_iso("20180517T10") == datetime(2018, 5, 17, 10) assert self.parser.parse_iso("20180517T105513.843456") == datetime( 2018, 5, 17, 10, 55, 13, 843456 ) assert self.parser.parse_iso("20180517T105513Z") == datetime( 2018, 5, 17, 10, 55, 13, tzinfo=tz.tzutc() ) assert self.parser.parse_iso("20180517T105513.843456-0700") == datetime( 2018, 5, 17, 10, 55, 13, 843456, tzinfo=tz.tzoffset(None, -25200) ) assert self.parser.parse_iso("20180517T105513-0700") == datetime( 2018, 5, 17, 10, 55, 13, tzinfo=tz.tzoffset(None, -25200) ) assert self.parser.parse_iso("20180517T105513-07") == datetime( 2018, 5, 17, 10, 55, 13, tzinfo=tz.tzoffset(None, -25200) ) # ordinal in basic format: YYYYDDDD assert self.parser.parse_iso("1998136") == datetime(1998, 5, 16) # timezone requires +- separator with pytest.raises(ParserError): self.parser.parse_iso("20180517T1055130700") with pytest.raises(ParserError): self.parser.parse_iso("20180517T10551307") # too many digits in date with pytest.raises(ParserError): self.parser.parse_iso("201860517T105513Z") # too many digits in time with pytest.raises(ParserError): self.parser.parse_iso("20180517T1055213Z") def test_midnight_end_day(self): assert self.parser.parse_iso("2019-10-30T24:00:00") == datetime( 2019, 10, 31, 0, 0, 0, 0 ) assert self.parser.parse_iso("2019-10-30T24:00") == datetime( 2019, 10, 31, 0, 0, 0, 0 ) assert self.parser.parse_iso("2019-10-30T24:00:00.0") == datetime( 2019, 10, 31, 0, 0, 0, 0 ) assert self.parser.parse_iso("2019-10-31T24:00:00") == datetime( 2019, 11, 1, 0, 0, 0, 0 ) assert self.parser.parse_iso("2019-12-31T24:00:00") == datetime( 2020, 1, 1, 0, 0, 0, 0 ) assert self.parser.parse_iso("2019-12-31T23:59:59.9999999") == datetime( 2020, 1, 1, 0, 0, 0, 0 ) with pytest.raises(ParserError): self.parser.parse_iso("2019-12-31T24:01:00") with pytest.raises(ParserError): self.parser.parse_iso("2019-12-31T24:00:01") with pytest.raises(ParserError): self.parser.parse_iso("2019-12-31T24:00:00.1") with pytest.raises(ParserError): self.parser.parse_iso("2019-12-31T24:00:00.999999") @pytest.mark.usefixtures("tzinfo_parser") class TestTzinfoParser: def test_parse_local(self): assert self.parser.parse("local") == tz.tzlocal() def test_parse_utc(self): assert self.parser.parse("utc") == tz.tzutc() assert self.parser.parse("UTC") == tz.tzutc() def test_parse_iso(self): assert self.parser.parse("01:00") == tz.tzoffset(None, 3600) assert self.parser.parse("11:35") == tz.tzoffset(None, 11 * 3600 + 2100) assert self.parser.parse("+01:00") == tz.tzoffset(None, 3600) assert self.parser.parse("-01:00") == tz.tzoffset(None, -3600) assert self.parser.parse("0100") == tz.tzoffset(None, 3600) assert self.parser.parse("+0100") == tz.tzoffset(None, 3600) assert self.parser.parse("-0100") == tz.tzoffset(None, -3600) assert self.parser.parse("01") == tz.tzoffset(None, 3600) assert self.parser.parse("+01") == tz.tzoffset(None, 3600) assert self.parser.parse("-01") == tz.tzoffset(None, -3600) def test_parse_str(self): assert self.parser.parse("US/Pacific") == tz.gettz("US/Pacific") def test_parse_fails(self): with pytest.raises(parser.ParserError): self.parser.parse("fail") @pytest.mark.usefixtures("dt_parser") class TestDateTimeParserMonthName: def test_shortmonth_capitalized(self): assert self.parser.parse("2013-Jan-01", "YYYY-MMM-DD") == datetime(2013, 1, 1) def test_shortmonth_allupper(self): assert self.parser.parse("2013-JAN-01", "YYYY-MMM-DD") == datetime(2013, 1, 1) def test_shortmonth_alllower(self): assert self.parser.parse("2013-jan-01", "YYYY-MMM-DD") == datetime(2013, 1, 1) def test_month_capitalized(self): assert self.parser.parse("2013-January-01", "YYYY-MMMM-DD") == datetime( 2013, 1, 1 ) def test_month_allupper(self): assert self.parser.parse("2013-JANUARY-01", "YYYY-MMMM-DD") == datetime( 2013, 1, 1 ) def test_month_alllower(self): assert self.parser.parse("2013-january-01", "YYYY-MMMM-DD") == datetime( 2013, 1, 1 ) def test_localized_month_name(self): parser_ = parser.DateTimeParser("fr-fr") assert parser_.parse("2013-Janvier-01", "YYYY-MMMM-DD") == datetime(2013, 1, 1) def test_localized_month_abbreviation(self): parser_ = parser.DateTimeParser("it-it") assert parser_.parse("2013-Gen-01", "YYYY-MMM-DD") == datetime(2013, 1, 1) @pytest.mark.usefixtures("dt_parser") class TestDateTimeParserMeridians: def test_meridians_lowercase(self): assert self.parser.parse("2013-01-01 5am", "YYYY-MM-DD ha") == datetime( 2013, 1, 1, 5 ) assert self.parser.parse("2013-01-01 5pm", "YYYY-MM-DD ha") == datetime( 2013, 1, 1, 17 ) def test_meridians_capitalized(self): assert self.parser.parse("2013-01-01 5AM", "YYYY-MM-DD hA") == datetime( 2013, 1, 1, 5 ) assert self.parser.parse("2013-01-01 5PM", "YYYY-MM-DD hA") == datetime( 2013, 1, 1, 17 ) def test_localized_meridians_lowercase(self): parser_ = parser.DateTimeParser("hu-hu") assert parser_.parse("2013-01-01 5 de", "YYYY-MM-DD h a") == datetime( 2013, 1, 1, 5 ) assert parser_.parse("2013-01-01 5 du", "YYYY-MM-DD h a") == datetime( 2013, 1, 1, 17 ) def test_localized_meridians_capitalized(self): parser_ = parser.DateTimeParser("hu-hu") assert parser_.parse("2013-01-01 5 DE", "YYYY-MM-DD h A") == datetime( 2013, 1, 1, 5 ) assert parser_.parse("2013-01-01 5 DU", "YYYY-MM-DD h A") == datetime( 2013, 1, 1, 17 ) # regression test for issue #607 def test_es_meridians(self): parser_ = parser.DateTimeParser("es") assert parser_.parse( "Junio 30, 2019 - 08:00 pm", "MMMM DD, YYYY - hh:mm a" ) == datetime(2019, 6, 30, 20, 0) with pytest.raises(ParserError): parser_.parse( "Junio 30, 2019 - 08:00 pasdfasdfm", "MMMM DD, YYYY - hh:mm a" ) def test_fr_meridians(self): parser_ = parser.DateTimeParser("fr") # the French locale always uses a 24 hour clock, so it does not support meridians with pytest.raises(ParserError): parser_.parse("Janvier 30, 2019 - 08:00 pm", "MMMM DD, YYYY - hh:mm a") @pytest.mark.usefixtures("dt_parser") class TestDateTimeParserMonthOrdinalDay: def test_english(self): parser_ = parser.DateTimeParser("en-us") assert parser_.parse("January 1st, 2013", "MMMM Do, YYYY") == datetime( 2013, 1, 1 ) assert parser_.parse("January 2nd, 2013", "MMMM Do, YYYY") == datetime( 2013, 1, 2 ) assert parser_.parse("January 3rd, 2013", "MMMM Do, YYYY") == datetime( 2013, 1, 3 ) assert parser_.parse("January 4th, 2013", "MMMM Do, YYYY") == datetime( 2013, 1, 4 ) assert parser_.parse("January 11th, 2013", "MMMM Do, YYYY") == datetime( 2013, 1, 11 ) assert parser_.parse("January 12th, 2013", "MMMM Do, YYYY") == datetime( 2013, 1, 12 ) assert parser_.parse("January 13th, 2013", "MMMM Do, YYYY") == datetime( 2013, 1, 13 ) assert parser_.parse("January 21st, 2013", "MMMM Do, YYYY") == datetime( 2013, 1, 21 ) assert parser_.parse("January 31st, 2013", "MMMM Do, YYYY") == datetime( 2013, 1, 31 ) with pytest.raises(ParserError): parser_.parse("January 1th, 2013", "MMMM Do, YYYY") with pytest.raises(ParserError): parser_.parse("January 11st, 2013", "MMMM Do, YYYY") def test_italian(self): parser_ = parser.DateTimeParser("it-it") assert parser_.parse("Gennaio 1º, 2013", "MMMM Do, YYYY") == datetime( 2013, 1, 1 ) def test_spanish(self): parser_ = parser.DateTimeParser("es-es") assert parser_.parse("Enero 1º, 2013", "MMMM Do, YYYY") == datetime(2013, 1, 1) def test_french(self): parser_ = parser.DateTimeParser("fr-fr") assert parser_.parse("Janvier 1er, 2013", "MMMM Do, YYYY") == datetime( 2013, 1, 1 ) assert parser_.parse("Janvier 2e, 2013", "MMMM Do, YYYY") == datetime( 2013, 1, 2 ) assert parser_.parse("Janvier 11e, 2013", "MMMM Do, YYYY") == datetime( 2013, 1, 11 ) @pytest.mark.usefixtures("dt_parser") class TestDateTimeParserSearchDate: def test_parse_search(self): assert self.parser.parse( "Today is 25 of September of 2003", "DD of MMMM of YYYY" ) == datetime(2003, 9, 25) def test_parse_search_with_numbers(self): assert self.parser.parse( "2000 people met the 2012-01-01 12:05:10", "YYYY-MM-DD HH:mm:ss" ) == datetime(2012, 1, 1, 12, 5, 10) assert self.parser.parse( "Call 01-02-03 on 79-01-01 12:05:10", "YY-MM-DD HH:mm:ss" ) == datetime(1979, 1, 1, 12, 5, 10) def test_parse_search_with_names(self): assert self.parser.parse("June was born in May 1980", "MMMM YYYY") == datetime( 1980, 5, 1 ) def test_parse_search_locale_with_names(self): p = parser.DateTimeParser("sv-se") assert p.parse("Jan föddes den 31 Dec 1980", "DD MMM YYYY") == datetime( 1980, 12, 31 ) assert p.parse("Jag föddes den 25 Augusti 1975", "DD MMMM YYYY") == datetime( 1975, 8, 25 ) def test_parse_search_fails(self): with pytest.raises(parser.ParserError): self.parser.parse("Jag föddes den 25 Augusti 1975", "DD MMMM YYYY") def test_escape(self): format = "MMMM D, YYYY [at] h:mma" assert self.parser.parse( "Thursday, December 10, 2015 at 5:09pm", format ) == datetime(2015, 12, 10, 17, 9) format = "[MMMM] M D, YYYY [at] h:mma" assert self.parser.parse("MMMM 12 10, 2015 at 5:09pm", format) == datetime( 2015, 12, 10, 17, 9 ) format = "[It happened on] MMMM Do [in the year] YYYY [a long time ago]" assert self.parser.parse( "It happened on November 25th in the year 1990 a long time ago", format ) == datetime(1990, 11, 25) format = "[It happened on] MMMM Do [in the][ year] YYYY [a long time ago]" assert self.parser.parse( "It happened on November 25th in the year 1990 a long time ago", format ) == datetime(1990, 11, 25) format = "[I'm][ entirely][ escaped,][ weee!]" assert self.parser.parse("I'm entirely escaped, weee!", format) == datetime( 1, 1, 1 ) # Special RegEx characters format = "MMM DD, YYYY |^${}().*+?<>-& h:mm A" assert self.parser.parse( "Dec 31, 2017 |^${}().*+?<>-& 2:00 AM", format ) == datetime(2017, 12, 31, 2, 0) @pytest.mark.usefixtures("dt_parser") class TestFuzzInput: # Regression test for issue #860 def test_no_match_group(self): fmt_str = str(b"[|\x1f\xb9\x03\x00\x00\x00\x00:-yI:][\x01yI:yI:I") payload = str(b"") with pytest.raises(parser.ParserMatchError): self.parser.parse(payload, fmt_str) # Regression test for issue #854 def test_regex_module_error(self): fmt_str = str(b"struct n[X+,N-M)MMXdMM]<") payload = str(b"") with pytest.raises(parser.ParserMatchError): self.parser.parse(payload, fmt_str) python-arrow-1.2.1/tests/test_util.py000066400000000000000000000106651415100123600177150ustar00rootroot00000000000000import time from datetime import datetime import pytest from arrow import util class TestUtil: def test_next_weekday(self): # Get first Monday after epoch assert util.next_weekday(datetime(1970, 1, 1), 0) == datetime(1970, 1, 5) # Get first Tuesday after epoch assert util.next_weekday(datetime(1970, 1, 1), 1) == datetime(1970, 1, 6) # Get first Wednesday after epoch assert util.next_weekday(datetime(1970, 1, 1), 2) == datetime(1970, 1, 7) # Get first Thursday after epoch assert util.next_weekday(datetime(1970, 1, 1), 3) == datetime(1970, 1, 1) # Get first Friday after epoch assert util.next_weekday(datetime(1970, 1, 1), 4) == datetime(1970, 1, 2) # Get first Saturday after epoch assert util.next_weekday(datetime(1970, 1, 1), 5) == datetime(1970, 1, 3) # Get first Sunday after epoch assert util.next_weekday(datetime(1970, 1, 1), 6) == datetime(1970, 1, 4) # Weekdays are 0-indexed with pytest.raises(ValueError): util.next_weekday(datetime(1970, 1, 1), 7) with pytest.raises(ValueError): util.next_weekday(datetime(1970, 1, 1), -1) def test_is_timestamp(self): timestamp_float = time.time() timestamp_int = int(timestamp_float) assert util.is_timestamp(timestamp_int) assert util.is_timestamp(timestamp_float) assert util.is_timestamp(str(timestamp_int)) assert util.is_timestamp(str(timestamp_float)) assert not util.is_timestamp(True) assert not util.is_timestamp(False) class InvalidTimestamp: pass assert not util.is_timestamp(InvalidTimestamp()) full_datetime = "2019-06-23T13:12:42" assert not util.is_timestamp(full_datetime) def test_validate_ordinal(self): timestamp_float = 1607066816.815537 timestamp_int = int(timestamp_float) timestamp_str = str(timestamp_int) with pytest.raises(TypeError): util.validate_ordinal(timestamp_float) with pytest.raises(TypeError): util.validate_ordinal(timestamp_str) with pytest.raises(TypeError): util.validate_ordinal(True) with pytest.raises(TypeError): util.validate_ordinal(False) with pytest.raises(ValueError): util.validate_ordinal(timestamp_int) with pytest.raises(ValueError): util.validate_ordinal(-1 * timestamp_int) with pytest.raises(ValueError): util.validate_ordinal(0) try: util.validate_ordinal(1) except (ValueError, TypeError) as exp: pytest.fail(f"Exception raised when shouldn't have ({type(exp)}).") try: util.validate_ordinal(datetime.max.toordinal()) except (ValueError, TypeError) as exp: pytest.fail(f"Exception raised when shouldn't have ({type(exp)}).") ordinal = datetime.utcnow().toordinal() ordinal_str = str(ordinal) ordinal_float = float(ordinal) + 0.5 with pytest.raises(TypeError): util.validate_ordinal(ordinal_str) with pytest.raises(TypeError): util.validate_ordinal(ordinal_float) with pytest.raises(ValueError): util.validate_ordinal(-1 * ordinal) try: util.validate_ordinal(ordinal) except (ValueError, TypeError) as exp: pytest.fail(f"Exception raised when shouldn't have ({type(exp)}).") full_datetime = "2019-06-23T13:12:42" class InvalidOrdinal: pass with pytest.raises(TypeError): util.validate_ordinal(InvalidOrdinal()) with pytest.raises(TypeError): util.validate_ordinal(full_datetime) def test_normalize_timestamp(self): timestamp = 1591161115.194556 millisecond_timestamp = 1591161115194 microsecond_timestamp = 1591161115194556 assert util.normalize_timestamp(timestamp) == timestamp assert util.normalize_timestamp(millisecond_timestamp) == 1591161115.194 assert util.normalize_timestamp(microsecond_timestamp) == 1591161115.194556 with pytest.raises(ValueError): util.normalize_timestamp(3e17) def test_iso_gregorian(self): with pytest.raises(ValueError): util.iso_to_gregorian(2013, 0, 5) with pytest.raises(ValueError): util.iso_to_gregorian(2013, 8, 0) python-arrow-1.2.1/tests/utils.py000066400000000000000000000005661415100123600170400ustar00rootroot00000000000000import pytz from dateutil.zoneinfo import get_zonefile_instance def make_full_tz_list(): dateutil_zones = set(get_zonefile_instance().zones) pytz_zones = set(pytz.all_timezones) return dateutil_zones.union(pytz_zones) def assert_datetime_equality(dt1, dt2, within=10): assert dt1.tzinfo == dt2.tzinfo assert abs((dt1 - dt2).total_seconds()) < within python-arrow-1.2.1/tox.ini000066400000000000000000000020151415100123600154660ustar00rootroot00000000000000[tox] minversion = 3.18.0 envlist = py{py3,36,37,38,39,310} skip_missing_interpreters = true [gh-actions] python = pypy-3.7: pypy3 3.6: py36 3.7: py37 3.8: py38 3.9: py39 3.10: py310 [testenv] deps = -rrequirements-dev.txt allowlist_externals = pytest commands = pytest [testenv:lint] basepython = python3 skip_install = true deps = pre-commit commands = pre-commit install pre-commit run --all-files --show-diff-on-failure [testenv:docs] basepython = python3 skip_install = true changedir = docs deps = doc8 sphinx sphinx-autodoc-typehints python-dateutil allowlist_externals = make commands = doc8 index.rst ../README.rst --extension .rst --ignore D001 make html SPHINXOPTS="-W --keep-going" [pytest] addopts = -v --cov-branch --cov=arrow --cov-fail-under=99 --cov-report=term-missing --cov-report=xml testpaths = tests [isort] line_length = 88 multi_line_output = 3 include_trailing_comma = true [flake8] per-file-ignores = arrow/__init__.py:F401 ignore = E203,E501,W503