arrow-0.15.5/0000775000175000017500000000000013603666233013210 5ustar chrischris00000000000000arrow-0.15.5/CHANGELOG.rst0000664000175000017500000004517613603666027015247 0ustar chrischris00000000000000Changelog ========= 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 Portugese 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 arrow-0.15.5/LICENSE0000664000175000017500000002611513545454611014222 0ustar chrischris00000000000000 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 2019 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. arrow-0.15.5/MANIFEST.in0000664000175000017500000000016713577744544014766 0ustar chrischris00000000000000include LICENSE CHANGELOG.rst README.rst recursive-include tests *.py recursive-include docs *.py *.rst *.bat Makefile arrow-0.15.5/PKG-INFO0000664000175000017500000001547513603666233014321 0ustar chrischris00000000000000Metadata-Version: 2.1 Name: arrow Version: 0.15.5 Summary: Better dates & times for Python Home-page: https://arrow.readthedocs.io Author: Chris Smith Author-email: crsmithdev@gmail.com License: Apache 2.0 Project-URL: Repository, https://github.com/crsmithdev/arrow Project-URL: Bug Reports, https://github.com/crsmithdev/arrow/issues Project-URL: Documentation, https://arrow.readthedocs.io Description: Arrow: Better dates & times for Python ====================================== .. start-inclusion-marker-do-not-remove .. image:: https://travis-ci.org/crsmithdev/arrow.svg?branch=master :alt: Build Status :target: https://travis-ci.org/crsmithdev/arrow .. image:: https://codecov.io/github/crsmithdev/arrow/coverage.svg?branch=master :alt: Codecov :target: https://codecov.io/github/crsmithdev/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 - Supports Python 2.7, 3.5, 3.6, 3.7 and 3.8 - Timezone-aware and UTC by default - Provides super-simple creation options for many common input scenarios - :code:`shift` method with support for relative offsets, including weeks - Formats and parses strings automatically - Wide support for ISO 8601 - Timezone conversion - Timestamp available as a property - Generates time spans, ranges, floors and ceilings for time frames ranging from microsecond to year - Humanizes and supports a growing list of contributed locales - Extensible for your own Arrow-derived types 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 >>> 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') '1시간 전' .. 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: 1. 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! 2. Fork `this repository `_ on GitHub and begin making changes in a branch. 3. Add a few tests to ensure that the bug was fixed or the feature works as expected. 4. Submit a pull request and await feedback 😃. If you have any questions along the way, feel free to ask them `here `_. Keywords: arrow date time datetime timestamp timezone Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* Description-Content-Type: text/x-rst arrow-0.15.5/README.rst0000664000175000017500000001130413574430406014674 0ustar chrischris00000000000000Arrow: Better dates & times for Python ====================================== .. start-inclusion-marker-do-not-remove .. image:: https://travis-ci.org/crsmithdev/arrow.svg?branch=master :alt: Build Status :target: https://travis-ci.org/crsmithdev/arrow .. image:: https://codecov.io/github/crsmithdev/arrow/coverage.svg?branch=master :alt: Codecov :target: https://codecov.io/github/crsmithdev/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 - Supports Python 2.7, 3.5, 3.6, 3.7 and 3.8 - Timezone-aware and UTC by default - Provides super-simple creation options for many common input scenarios - :code:`shift` method with support for relative offsets, including weeks - Formats and parses strings automatically - Wide support for ISO 8601 - Timezone conversion - Timestamp available as a property - Generates time spans, ranges, floors and ceilings for time frames ranging from microsecond to year - Humanizes and supports a growing list of contributed locales - Extensible for your own Arrow-derived types 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 >>> 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') '1시간 전' .. 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: 1. 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! 2. Fork `this repository `_ on GitHub and begin making changes in a branch. 3. Add a few tests to ensure that the bug was fixed or the feature works as expected. 4. Submit a pull request and await feedback 😃. If you have any questions along the way, feel free to ask them `here `_. arrow-0.15.5/arrow/0000775000175000017500000000000013603666233014342 5ustar chrischris00000000000000arrow-0.15.5/arrow/__init__.py0000664000175000017500000000026713577744544016474 0ustar chrischris00000000000000# -*- coding: utf-8 -*- from ._version import __version__ from .api import get, now, utcnow from .arrow import Arrow from .factory import ArrowFactory from .parser import ParserError arrow-0.15.5/arrow/_version.py0000664000175000017500000000002713603666027016540 0ustar chrischris00000000000000__version__ = "0.15.5" arrow-0.15.5/arrow/api.py0000664000175000017500000000223613521652254015465 0ustar chrischris00000000000000# -*- coding: utf-8 -*- """ Provides the default implementation of :class:`ArrowFactory ` methods for use as a module API. """ from __future__ import absolute_import from arrow.factory import ArrowFactory # internal default factory. _factory = ArrowFactory() def get(*args, **kwargs): """ Calls the default :class:`ArrowFactory ` ``get`` method. """ return _factory.get(*args, **kwargs) get.__doc__ = _factory.get.__doc__ def utcnow(): """ Calls the default :class:`ArrowFactory ` ``utcnow`` method. """ return _factory.utcnow() utcnow.__doc__ = _factory.utcnow.__doc__ def now(tz=None): """ Calls the default :class:`ArrowFactory ` ``now`` method. """ return _factory.now(tz) now.__doc__ = _factory.now.__doc__ def factory(type): """ 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"] arrow-0.15.5/arrow/arrow.py0000664000175000017500000013727613603664774016076 0ustar chrischris00000000000000# -*- coding: utf-8 -*- """ Provides the :class:`Arrow ` class, an enhanced ``datetime`` replacement. """ from __future__ import absolute_import import calendar import sys from datetime import datetime, timedelta from datetime import tzinfo as dt_tzinfo from math import trunc from dateutil import tz as dateutil_tz from dateutil.relativedelta import relativedelta from arrow import formatter, locales, parser, util class Arrow(object): """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 0. :param tzinfo: (optional) A timezone expression. Defaults to UTC. .. _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 = datetime.resolution _ATTRS = ["year", "month", "day", "hour", "minute", "second", "microsecond"] _ATTRS_PLURAL = ["{}s".format(a) for a in _ATTRS] _MONTHS_PER_QUARTER = 3 _SECS_PER_MINUTE = float(60) _SECS_PER_HOUR = float(60 * 60) _SECS_PER_DAY = float(60 * 60 * 24) _SECS_PER_WEEK = float(60 * 60 * 24 * 7) _SECS_PER_MONTH = float(60 * 60 * 24 * 30.5) _SECS_PER_YEAR = float(60 * 60 * 24 * 365.25) def __init__( self, year, month, day, hour=0, minute=0, second=0, microsecond=0, tzinfo=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 ): tzinfo = parser.TzinfoParser.parse(tzinfo.zone) elif util.isstr(tzinfo): tzinfo = parser.TzinfoParser.parse(tzinfo) self._datetime = datetime( year, month, day, hour, minute, second, microsecond, tzinfo ) # factories: single object, both original and from datetime. @classmethod def now(cls, tzinfo=None): """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 = datetime.now(tzinfo) return cls( dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond, dt.tzinfo, ) @classmethod def utcnow(cls): """ Constructs an :class:`Arrow ` object, representing "now" in UTC time. Usage:: >>> arrow.utcnow() """ dt = datetime.now(dateutil_tz.tzutc()) return cls( dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond, dt.tzinfo, ) @classmethod def fromtimestamp(cls, timestamp, tzinfo=None): """ 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 util.isstr(tzinfo): tzinfo = parser.TzinfoParser.parse(tzinfo) if not util.is_timestamp(timestamp): raise ValueError( "The provided timestamp '{}' is invalid.".format(timestamp) ) dt = datetime.fromtimestamp(float(timestamp), tzinfo) return cls( dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond, dt.tzinfo, ) @classmethod def utcfromtimestamp(cls, timestamp): """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( "The provided timestamp '{}' is invalid.".format(timestamp) ) dt = datetime.utcfromtimestamp(float(timestamp)) return cls( dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond, dateutil_tz.tzutc(), ) @classmethod def fromdatetime(cls, dt, tzinfo=None): """ 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. If you only want to replace the timezone of naive datetimes:: >>> dt datetime.datetime(2013, 5, 5, 0, 0, tzinfo=tzutc()) >>> arrow.Arrow.fromdatetime(dt, dt.tzinfo or 'US/Pacific') """ 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, ) @classmethod def fromdate(cls, date, tzinfo=None): """ Constructs an :class:`Arrow ` object from a ``date`` and optional replacement timezone. 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, fmt, tzinfo=None): """ 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. :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 = 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, ) # factories: ranges and spans @classmethod def range(cls, frame, start, end=None, tz=None, limit=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. 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) 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) + relativedelta( **{frame_relative: relative_steps} ) @classmethod def span_range(cls, frame, start, end, tz=None, limit=None, bounds="[)"): """ 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. **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. 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)[0] _range = cls.range(frame, start, end, tz, limit) return (r.span(frame, bounds=bounds) for r in _range) @classmethod def interval(cls, frame, start, end, interval=1, tz=None, bounds="[)"): """ 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. 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)) while True: try: intvlStart, intvlEnd = next(spanRange) for _ in range(interval - 1): _, intvlEnd = next(spanRange) yield intvlStart, intvlEnd except StopIteration: return # representations def __repr__(self): return "<{} [{}]>".format(self.__class__.__name__, self.__str__()) def __str__(self): return self._datetime.isoformat() def __format__(self, formatstr): if len(formatstr) > 0: return self.format(formatstr) return str(self) def __hash__(self): return self._datetime.__hash__() # attributes & properties def __getattr__(self, name): 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 = getattr(self._datetime, name, None) if value is not None: return value return object.__getattribute__(self, name) @property def tzinfo(self): """ Gets the ``tzinfo`` of the :class:`Arrow ` object. Usage:: >>> arw=arrow.utcnow() >>> arw.tzinfo tzutc() """ return self._datetime.tzinfo @tzinfo.setter def tzinfo(self, tzinfo): """ Sets the ``tzinfo`` of the :class:`Arrow ` object. """ self._datetime = self._datetime.replace(tzinfo=tzinfo) @property def datetime(self): """ 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): """ 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) @property def timestamp(self): """ Returns a timestamp representation of the :class:`Arrow ` object, in UTC time. Usage:: >>> arrow.utcnow().timestamp 1548260567 """ return calendar.timegm(self._datetime.utctimetuple()) @property def float_timestamp(self): """ Returns a floating-point representation of the :class:`Arrow ` object, in UTC time. Usage:: >>> arrow.utcnow().float_timestamp 1548260516.830896 """ return self.timestamp + float(self.microsecond) / 1000000 # mutation and duplication. def clone(self): """ 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): """ 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 AttributeError("setting absolute {} is not supported".format(key)) elif key != "tzinfo": raise AttributeError('unknown attribute: "{}"'.format(key)) 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) return self.fromdatetime(current) def shift(self, **kwargs): """ 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: raise AttributeError( "Invalid shift time frame. Please select one of the following: {}.".format( ", ".join(self._ATTRS_PLURAL + additional_attrs) ) ) # 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) return self.fromdatetime(current) def to(self, tz): """ 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, ) @classmethod def _validate_bounds(cls, bounds): if bounds != "()" and bounds != "(]" and bounds != "[)" and bounds != "[]": raise AttributeError( 'Invalid bounds. Please select between "()", "(]", "[)", or "[]".' ) def span(self, frame, count=1, bounds="[)"): """ Returns 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. 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='[]') (, ) """ self._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 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) if frame_absolute == "week": floor = floor + relativedelta(days=-(self.isoweekday() - 1)) elif frame_absolute == "quarter": floor = floor + relativedelta(months=-((self.month - 1) % 3)) ceil = floor + relativedelta(**{frame_relative: count * relative_steps}) if bounds[0] == "(": floor += relativedelta(microseconds=1) if bounds[1] == ")": ceil += relativedelta(microseconds=-1) return floor, ceil def floor(self, frame): """ 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): """ 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] # string output and formatting. def format(self, fmt="YYYY-MM-DD HH:mm:ssZZ", locale="en_us"): """ Returns a string representation of the :class:`Arrow ` object, formatted according to a format string. :param fmt: the format string. 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=None, locale="en_us", only_distance=False, granularity="auto" ): """ 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 = datetime.utcnow().replace(tzinfo=dateutil_tz.tzutc()) dt = utc.astimezone(self._datetime.tzinfo) elif isinstance(other, Arrow): dt = other._datetime elif isinstance(other, datetime): if other.tzinfo is None: dt = other.replace(tzinfo=self._datetime.tzinfo) else: dt = other.astimezone(self._datetime.tzinfo) else: raise TypeError( "Invalid 'other' argument of type '{}'. " "Argument must be of type None, Arrow, or datetime.".format( type(other).__name__ ) ) if isinstance(granularity, list) and len(granularity) == 1: granularity = granularity[0] delta = int(round(util.total_seconds(self._datetime - dt))) sign = -1 if delta < 0 else 1 diff = abs(delta) delta = diff try: if granularity == "auto": if diff < 10: return locale.describe("now", only_distance=only_distance) if diff < 45: seconds = sign * delta return locale.describe( "seconds", seconds, only_distance=only_distance ) elif diff < 90: return locale.describe("minute", sign, only_distance=only_distance) elif diff < 2700: minutes = sign * int(max(delta / 60, 2)) return locale.describe( "minutes", minutes, only_distance=only_distance ) elif diff < 5400: return locale.describe("hour", sign, only_distance=only_distance) elif diff < 79200: hours = sign * int(max(delta / 3600, 2)) return locale.describe("hours", hours, only_distance=only_distance) # anything less than 48 hours should be 1 day elif diff < 172800: return locale.describe("day", sign, only_distance=only_distance) elif diff < 554400: days = sign * int(max(delta / 86400, 2)) return locale.describe("days", days, only_distance=only_distance) elif diff < 907200: return locale.describe("week", sign, only_distance=only_distance) elif diff < 2419200: weeks = sign * int(max(delta / 604800, 2)) return locale.describe("weeks", weeks, only_distance=only_distance) elif diff < 3888000: return locale.describe("month", sign, only_distance=only_distance) elif diff < 29808000: self_months = self._datetime.year * 12 + self._datetime.month other_months = dt.year * 12 + dt.month months = sign * int(max(abs(other_months - self_months), 2)) return locale.describe( "months", months, only_distance=only_distance ) elif diff < 47260800: return locale.describe("year", sign, only_distance=only_distance) else: years = sign * int(max(delta / 31536000, 2)) return locale.describe("years", years, only_distance=only_distance) elif util.isstr(granularity): if granularity == "second": delta = sign * delta if abs(delta) < 2: return locale.describe("now", only_distance=only_distance) elif granularity == "minute": delta = sign * delta / self._SECS_PER_MINUTE elif granularity == "hour": delta = sign * delta / self._SECS_PER_HOUR elif granularity == "day": delta = sign * delta / self._SECS_PER_DAY elif granularity == "week": delta = sign * delta / self._SECS_PER_WEEK elif granularity == "month": delta = sign * delta / self._SECS_PER_MONTH elif granularity == "year": delta = sign * delta / self._SECS_PER_YEAR else: raise AttributeError( "Invalid level of granularity. Please select between 'second', 'minute', 'hour', 'day', 'week', 'month' or 'year'" ) if trunc(abs(delta)) != 1: granularity += "s" return locale.describe(granularity, delta, only_distance=only_distance) else: timeframes = [] if "year" in granularity: years = sign * delta / self._SECS_PER_YEAR delta %= self._SECS_PER_YEAR timeframes.append(["year", years]) if "month" in granularity: months = sign * delta / self._SECS_PER_MONTH delta %= self._SECS_PER_MONTH timeframes.append(["month", months]) if "week" in granularity: weeks = sign * delta / self._SECS_PER_WEEK delta %= self._SECS_PER_WEEK timeframes.append(["week", weeks]) if "day" in granularity: days = sign * delta / self._SECS_PER_DAY delta %= self._SECS_PER_DAY timeframes.append(["day", days]) if "hour" in granularity: hours = sign * delta / self._SECS_PER_HOUR delta %= self._SECS_PER_HOUR timeframes.append(["hour", hours]) if "minute" in granularity: minutes = sign * delta / self._SECS_PER_MINUTE delta %= self._SECS_PER_MINUTE timeframes.append(["minute", minutes]) if "second" in granularity: seconds = sign * delta timeframes.append(["second", seconds]) if len(timeframes) < len(granularity): raise AttributeError( "Invalid level of granularity. " "Please select between 'second', 'minute', 'hour', 'day', 'week', 'month' or 'year'." ) for tf in timeframes: # Make granularity plural if the delta is not equal to 1 if trunc(abs(tf[1])) != 1: tf[0] += "s" return locale.describe_multi(timeframes, only_distance=only_distance) except KeyError as e: raise ValueError( "Humanization of the {} granularity is not currently translated in the '{}' locale. " "Please consider making a contribution to this locale.".format( e, locale_name ) ) # query functions def is_between(self, start, end, bounds="()"): """ Returns a boolean denoting whether the specified date and time is between the start and end dates and times. :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 """ self._validate_bounds(bounds) if not isinstance(start, Arrow): raise TypeError( "Can't parse start date argument type of '{}'".format(type(start)) ) if not isinstance(end, Arrow): raise TypeError( "Can't parse end date argument type of '{}'".format(type(end)) ) include_start = bounds[0] == "[" include_end = bounds[1] == "]" target_timestamp = self.float_timestamp start_timestamp = start.float_timestamp end_timestamp = end.float_timestamp if include_start and include_end: return ( target_timestamp >= start_timestamp and target_timestamp <= end_timestamp ) elif include_start and not include_end: return ( target_timestamp >= start_timestamp and target_timestamp < end_timestamp ) elif not include_start and include_end: return ( target_timestamp > start_timestamp and target_timestamp <= end_timestamp ) else: return ( target_timestamp > start_timestamp and target_timestamp < end_timestamp ) # math def __add__(self, other): if isinstance(other, (timedelta, relativedelta)): return self.fromdatetime(self._datetime + other, self._datetime.tzinfo) return NotImplemented def __radd__(self, other): return self.__add__(other) def __sub__(self, other): if isinstance(other, (timedelta, relativedelta)): return self.fromdatetime(self._datetime - other, self._datetime.tzinfo) elif isinstance(other, datetime): return self._datetime - other elif isinstance(other, Arrow): return self._datetime - other._datetime return NotImplemented def __rsub__(self, other): if isinstance(other, datetime): return other - self._datetime return NotImplemented # comparisons def __eq__(self, other): if not isinstance(other, (Arrow, datetime)): return False return self._datetime == self._get_datetime(other) def __ne__(self, other): if not isinstance(other, (Arrow, datetime)): return True return not self.__eq__(other) def __gt__(self, other): if not isinstance(other, (Arrow, datetime)): return NotImplemented return self._datetime > self._get_datetime(other) def __ge__(self, other): if not isinstance(other, (Arrow, datetime)): return NotImplemented return self._datetime >= self._get_datetime(other) def __lt__(self, other): if not isinstance(other, (Arrow, datetime)): return NotImplemented return self._datetime < self._get_datetime(other) def __le__(self, other): if not isinstance(other, (Arrow, datetime)): return NotImplemented return self._datetime <= self._get_datetime(other) def __cmp__(self, other): if sys.version_info[0] < 3: # pragma: no cover if not isinstance(other, (Arrow, datetime)): raise TypeError( "can't compare '{}' to '{}'".format(type(self), type(other)) ) # datetime methods def date(self): """ 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): """ 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): """ 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): """ 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): """ 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): """ Returns the daylight savings time adjustment. Usage:: >>> arrow.utcnow().dst() datetime.timedelta(0) """ return self._datetime.dst() def timetuple(self): """ 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): """ 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): """ Returns the proleptic Gregorian ordinal of the date. Usage:: >>> arrow.utcnow().toordinal() 737078 """ return self._datetime.toordinal() def weekday(self): """ Returns the day of the week as an integer (0-6). Usage:: >>> arrow.utcnow().weekday() 5 """ return self._datetime.weekday() def isoweekday(self): """ Returns the ISO day of the week as an integer (1-7). Usage:: >>> arrow.utcnow().isoweekday() 6 """ return self._datetime.isoweekday() def isocalendar(self): """ 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="T"): """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) def ctime(self): """ 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): """ 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): """Serializes for the ``for_json`` protocol of simplejson. Usage:: >>> arrow.utcnow().for_json() '2019-01-19T18:25:36.760079+00:00' """ return self.isoformat() # internal tools. @staticmethod def _get_tzinfo(tz_expr): 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("'{}' not recognized as a timezone".format(tz_expr)) @classmethod def _get_datetime(cls, expr): """Get datetime object for a specified expression.""" if isinstance(expr, Arrow): return expr.datetime elif isinstance(expr, datetime): return expr elif util.is_timestamp(expr): timestamp = float(expr) return cls.utcfromtimestamp(timestamp).datetime else: raise ValueError( "'{}' not recognized as a datetime or timestamp.".format(expr) ) @classmethod def _get_frames(cls, name): if name in cls._ATTRS: return name, "{}s".format(name), 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 supported = ", ".join( [ "year(s)", "month(s)", "day(s)", "hour(s)", "minute(s)", "second(s)", "microsecond(s)", "week(s)", "quarter(s)", ] ) raise AttributeError( "range/span over frame {} not supported. Supported frames: {}".format( name, supported ) ) @classmethod def _get_iteration_params(cls, end, limit): 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 Arrow.min = Arrow.fromdatetime(datetime.min) Arrow.max = Arrow.fromdatetime(datetime.max) arrow-0.15.5/arrow/constants.py0000664000175000017500000000054213574430406016727 0ustar chrischris00000000000000# -*- coding: utf-8 -*- # Output of time.mktime(datetime.max.timetuple()) on macOS # This value must be hardcoded for compatibility with Windows # Platform-independent max timestamps are hard to form # https://stackoverflow.com/q/46133223 MAX_TIMESTAMP = 253402318799.0 MAX_TIMESTAMP_MS = MAX_TIMESTAMP * 1000 MAX_TIMESTAMP_US = MAX_TIMESTAMP * 1000000 arrow-0.15.5/arrow/factory.py0000664000175000017500000002300713577744544016401 0ustar chrischris00000000000000# -*- coding: utf-8 -*- """ Implements the :class:`ArrowFactory ` class, providing factory methods for common :class:`Arrow ` construction scenarios. """ from __future__ import absolute_import import calendar from datetime import date, datetime from datetime import tzinfo as dt_tzinfo from time import struct_time from dateutil import tz as dateutil_tz from arrow import parser from arrow.arrow import Arrow from arrow.util import is_timestamp, iso_to_gregorian, isstr class ArrowFactory(object): """ A factory for generating :class:`Arrow ` objects. :param type: (optional) the :class:`Arrow `-based class to construct from. Defaults to :class:`Arrow `. """ def __init__(self, type=Arrow): self.type = type def get(self, *args, **kwargs): """ 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. Usage:: >>> import arrow **No inputs** to get current UTC time:: >>> arrow.get() **None** to also get current UTC time:: >>> arrow.get(None) **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 constructor of a ``datetime``:: >>> arrow.get(2013, 5, 5, 12, 30, 45) """ arg_count = len(args) locale = kwargs.pop("locale", "en_us") tz = kwargs.get("tzinfo", None) # 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, @ utc. if arg_count == 0: if isstr(tz): tz = parser.TzinfoParser.parse(tz) return self.type.now(tz) if isinstance(tz, dt_tzinfo): return self.type.now(tz) return self.type.utcnow() if arg_count == 1: arg = args[0] # (None) -> now, @ utc. if arg is None: return self.type.utcnow() # try (int, float) -> from timestamp with tz elif not isstr(arg) 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. elif isinstance(arg, Arrow): return self.type.fromdatetime(arg.datetime) # (datetime) -> from datetime. elif isinstance(arg, datetime): return self.type.fromdatetime(arg) # (date) -> from date. elif isinstance(arg, date): return self.type.fromdate(arg) # (tzinfo) -> now, @ tzinfo. elif isinstance(arg, dt_tzinfo): return self.type.now(arg) # (str) -> parse. elif isstr(arg): dt = parser.DateTimeParser(locale).parse_iso(arg) return self.type.fromdatetime(dt, tz) # (struct_time) -> from struct_time elif isinstance(arg, struct_time): return self.type.utcfromtimestamp(calendar.timegm(arg)) # (iso calendar) -> convert then from date elif isinstance(arg, tuple) and len(arg) == 3: dt = iso_to_gregorian(*arg) return self.type.fromdate(dt) else: raise TypeError( "Can't parse single argument of type '{}'".format(type(arg)) ) elif arg_count == 2: arg_1, arg_2 = args[0], args[1] if isinstance(arg_1, datetime): # (datetime, tzinfo/str) -> fromdatetime replace tzinfo. if isinstance(arg_2, dt_tzinfo) or isstr(arg_2): return self.type.fromdatetime(arg_1, arg_2) else: raise TypeError( "Can't parse two arguments of types 'datetime', '{}'".format( type(arg_2) ) ) elif isinstance(arg_1, date): # (date, tzinfo/str) -> fromdate replace tzinfo. if isinstance(arg_2, dt_tzinfo) or isstr(arg_2): return self.type.fromdate(arg_1, tzinfo=arg_2) else: raise TypeError( "Can't parse two arguments of types 'date', '{}'".format( type(arg_2) ) ) # (str, format) -> parse. elif isstr(arg_1) and (isstr(arg_2) or isinstance(arg_2, list)): dt = parser.DateTimeParser(locale).parse(args[0], args[1]) return self.type.fromdatetime(dt, tzinfo=tz) else: raise TypeError( "Can't parse two arguments of types '{}' and '{}'".format( type(arg_1), type(arg_2) ) ) # 3+ args -> datetime-like via constructor. else: return self.type(*args, **kwargs) def utcnow(self): """Returns an :class:`Arrow ` object, representing "now" in UTC time. Usage:: >>> import arrow >>> arrow.utcnow() """ return self.type.utcnow() def now(self, tz=None): """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) arrow-0.15.5/arrow/formatter.py0000664000175000017500000000752213577744544016741 0ustar chrischris00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import import calendar import re from dateutil import tz as dateutil_tz from arrow import locales, util class DateTimeFormatter(object): # This pattern matches characters enclosed in square brackes 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 # TODO: test against full timezone DB _FORMAT_RE = 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)" ) def __init__(self, locale="en_us"): self.locale = locales.get_locale(locale) def format(cls, dt, fmt): return cls._FORMAT_RE.sub(lambda m: cls._format_token(dt, m.group(0)), fmt) def _format_token(self, dt, token): 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 "{:02d}".format(dt.month) if token == "M": return str(dt.month) if token == "DDDD": return "{:03d}".format(dt.timetuple().tm_yday) if token == "DDD": return str(dt.timetuple().tm_yday) if token == "DD": return "{:02d}".format(dt.day) if token == "D": return str(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 str(dt.isoweekday()) if token == "HH": return "{:02d}".format(dt.hour) if token == "H": return str(dt.hour) if token == "hh": return "{:02d}".format(dt.hour if 0 < dt.hour < 13 else abs(dt.hour - 12)) if token == "h": return str(dt.hour if 0 < dt.hour < 13 else abs(dt.hour - 12)) if token == "mm": return "{:02d}".format(dt.minute) if token == "m": return str(dt.minute) if token == "ss": return "{:02d}".format(dt.second) if token == "s": return str(dt.second) if token == "SSSSSS": return str("{:06d}".format(int(dt.microsecond))) if token == "SSSSS": return str("{:05d}".format(int(dt.microsecond / 10))) if token == "SSSS": return str("{:04d}".format(int(dt.microsecond / 100))) if token == "SSS": return str("{:03d}".format(int(dt.microsecond / 1000))) if token == "SS": return str("{:02d}".format(int(dt.microsecond / 10000))) if token == "S": return str(int(dt.microsecond / 100000)) if token == "X": return str(calendar.timegm(dt.utctimetuple())) 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 total_minutes = int(util.total_seconds(tz.utcoffset(dt)) / 60) sign = "+" if total_minutes >= 0 else "-" total_minutes = abs(total_minutes) hour, minute = divmod(total_minutes, 60) return "{}{:02d}{}{:02d}".format(sign, hour, separator, minute) if token in ("a", "A"): return self.locale.meridian(dt.hour, token) arrow-0.15.5/arrow/locales.py0000664000175000017500000027210313603664774016353 0ustar chrischris00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals import inspect import sys from math import trunc def get_locale(name): """Returns an appropriate :class:`Locale ` corresponding to an inpute locale name. :param name: the name of the locale. """ locale_cls = _locales.get(name.lower()) if locale_cls is None: raise ValueError("Unsupported locale '{}'".format(name)) return locale_cls() # base locale type. class Locale(object): """ Represents locale-specific data and functionality. """ names = [] timeframes = { "now": "", "second": "", "seconds": "", "minute": "", "minutes": "", "hour": "", "hours": "", "day": "", "days": "", "week": "", "weeks": "", "month": "", "months": "", "year": "", "years": "", } meridians = {"am": "", "pm": "", "AM": "", "PM": ""} past = None future = None and_word = None month_names = [] month_abbreviations = [] day_names = [] day_abbreviations = [] ordinal_day_re = r"(\d+)" def __init__(self): self._month_name_to_ordinal = None def describe(self, timeframe, delta=0, only_distance=False): """ 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, delta) if not only_distance: humanized = self._format_relative(humanized, timeframe, delta) return humanized def describe_multi(self, timeframes, only_distance=False): """ 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 """ humanized = "" for index, (timeframe, delta) in enumerate(timeframes): humanized += self._format_timeframe(timeframe, delta) if index == len(timeframes) - 2 and self.and_word: humanized += " " + self.and_word + " " elif index < len(timeframes) - 1: humanized += " " if not only_distance: humanized = self._format_relative(humanized, timeframe, delta) return humanized def day_name(self, day): """ 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): """ 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): """ 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): """ 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): """ 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): """ Returns the year for specific locale if available :param name: the ``int`` year (4-digit) """ return "{:04d}".format(year) def year_abbreviation(self, year): """ Returns the year for specific locale if available :param name: the ``int`` year (4-digit) """ return "{:04d}".format(year)[2:] def meridian(self, hour, token): """ 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"] def ordinal_number(self, n): """ Returns the ordinal format of a given integer :param n: an integer """ return self._ordinal_number(n) def _ordinal_number(self, n): return "{}".format(n) def _name_to_ordinal(self, lst): return dict(map(lambda i: (i[1].lower(), i[0] + 1), enumerate(lst[1:]))) def _format_timeframe(self, timeframe, delta): return self.timeframes[timeframe].format(trunc(abs(delta))) def _format_relative(self, humanized, timeframe, delta): if timeframe == "now": return humanized direction = self.past if delta < 0 else self.future return direction.format(humanized) # base locale type implementations. 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", "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): if n % 100 not in (11, 12, 13): remainder = abs(n) % 10 if remainder == 1: return "{}st".format(n) elif remainder == 2: return "{}nd".format(n) elif remainder == 3: return "{}rd".format(n) return "{}th".format(n) def describe(self, timeframe, delta=0, only_distance=False): """ 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(EnglishLocale, self).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", "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): return "{}º".format(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): return "{}º".format(n) class FrenchLocale(Locale): names = ["fr", "fr_fr"] past = "il y a {0}" future = "dans {0}" and_word = "et" timeframes = { "now": "maintenant", "second": "une seconde", "seconds": "{0} quelques 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", ] month_abbreviations = [ "", "janv", "févr", "mars", "avr", "mai", "juin", "juil", "août", "sept", "oct", "nov", "déc", ] 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): if abs(n) == 1: return "{}er".format(n) return "{}e".format(n) 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} μέρες", "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}後" timeframes = { "now": "現在", "second": "二番目の", "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} några sekunder", "minute": "en minut", "minutes": "{0} minuter", "hour": "en timme", "hours": "{0} timmar", "day": "en dag", "days": "{0} dagar", "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 = { "now": ["juuri nyt", "juuri nyt"], "second": ["sekunti", "sekunti"], "seconds": ["{0} muutama sekunti", "{0} muutaman sekunnin"], "minute": ["minuutti", "minuutin"], "minutes": ["{0} minuuttia", "{0} minuutin"], "hour": ["tunti", "tunnin"], "hours": ["{0} tuntia", "{0} tunnin"], "day": ["päivä", "päivä"], "days": ["{0} päivää", "{0} päivän"], "month": ["kuukausi", "kuukauden"], "months": ["{0} kuukautta", "{0} kuukauden"], "year": ["vuosi", "vuoden"], "years": ["{0} vuotta", "{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, delta): return ( self.timeframes[timeframe][0].format(abs(delta)), self.timeframes[timeframe][1].format(abs(delta)), ) def _format_relative(self, humanized, timeframe, delta): if timeframe == "now": return humanized[0] direction = self.past if delta < 0 else self.future which = 0 if delta < 0 else 1 return direction.format(humanized[which]) def _ordinal_number(self, n): return "{}.".format(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}後" 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 = [ "", "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": "두 번째", "seconds": "{0}몇 초", "minute": "1분", "minutes": "{0}분", "hour": "1시간", "hours": "{0}시간", "day": "1일", "days": "{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 = ["", "월", "화", "수", "목", "금", "토", "일"] # 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): def _format_timeframe(self, timeframe, delta): form = self.timeframes[timeframe] delta = abs(delta) if isinstance(form, list): if delta % 10 == 1 and delta % 100 != 11: form = form[0] elif 2 <= delta % 10 <= 4 and (delta % 100 < 10 or delta % 100 >= 20): form = form[1] else: form = form[2] return form.format(delta) class BelarusianLocale(SlavicBaseLocale): names = ["be", "be_by"] past = "{0} таму" future = "праз {0}" timeframes = { "now": "зараз", "second": "секунду", "seconds": "{0} некалькі секунд", "minute": "хвіліну", "minutes": ["{0} хвіліну", "{0} хвіліны", "{0} хвілін"], "hour": "гадзіну", "hours": ["{0} гадзіну", "{0} гадзіны", "{0} гадзін"], "day": "дзень", "days": ["{0} дзень", "{0} дні", "{0} дзён"], "month": "месяц", "months": ["{0} месяц", "{0} месяцы", "{0} месяцаў"], "year": "год", "years": ["{0} год", "{0} гады", "{0} гадоў"], } month_names = [ "", "студзеня", "лютага", "сакавіка", "красавіка", "траўня", "чэрвеня", "ліпеня", "жніўня", "верасня", "кастрычніка", "лістапада", "снежня", ] month_abbreviations = [ "", "студ", "лют", "сак", "крас", "трав", "чэрв", "ліп", "жнів", "вер", "каст", "ліст", "снеж", ] day_names = [ "", "панядзелак", "аўторак", "серада", "чацвер", "пятніца", "субота", "нядзеля", ] day_abbreviations = ["", "пн", "ат", "ср", "чц", "пт", "сб", "нд"] class PolishLocale(SlavicBaseLocale): names = ["pl", "pl_pl"] past = "{0} temu" future = "za {0}" timeframes = { "now": "teraz", "second": "sekunda", "seconds": "{0} kilka sekund", "minute": "minutę", "minutes": ["{0} minut", "{0} minuty", "{0} minut"], "hour": "godzina", "hours": ["{0} godzin", "{0} godziny", "{0} godzin"], "day": "dzień", "days": ["{0} dzień", "{0} dni", "{0} dni"], "month": "miesiąc", "months": ["{0} miesiąc", "{0} miesiące", "{0} miesięcy"], "year": "rok", "years": ["{0} rok", "{0} lata", "{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 = { "now": "сейчас", "second": "Второй", "seconds": "{0} несколько секунд", "minute": "минуту", "minutes": ["{0} минуту", "{0} минуты", "{0} минут"], "hour": "час", "hours": ["{0} час", "{0} часа", "{0} часов"], "day": "день", "days": ["{0} день", "{0} дня", "{0} дней"], "week": "неделю", "weeks": ["{0} неделю", "{0} недели", "{0} недель"], "month": "месяц", "months": ["{0} месяц", "{0} месяца", "{0} месяцев"], "year": "год", "years": ["{0} год", "{0} года", "{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 = { "now": "сега", "second": "секунда", "seconds": "{0} няколко секунди", "minute": "минута", "minutes": ["{0} минута", "{0} минути", "{0} минути"], "hour": "час", "hours": ["{0} час", "{0} часа", "{0} часа"], "day": "ден", "days": ["{0} ден", "{0} дни", "{0} дни"], "month": "месец", "months": ["{0} месец", "{0} месеца", "{0} месеца"], "year": "година", "years": ["{0} година", "{0} години", "{0} години"], } month_names = [ "", "януари", "февруари", "март", "април", "май", "юни", "юли", "август", "септември", "октомври", "ноември", "декември", ] month_abbreviations = [ "", "ян", "февр", "март", "апр", "май", "юни", "юли", "авг", "септ", "окт", "ноем", "дек", ] day_names = [ "", "понеделник", "вторник", "сряда", "четвъртък", "петък", "събота", "неделя", ] day_abbreviations = ["", "пон", "вт", "ср", "четв", "пет", "съб", "нед"] class UkrainianLocale(SlavicBaseLocale): names = ["ua", "uk_ua"] past = "{0} тому" future = "за {0}" timeframes = { "now": "зараз", "second": "секунда", "seconds": "{0} кілька секунд", "minute": "хвилину", "minutes": ["{0} хвилину", "{0} хвилини", "{0} хвилин"], "hour": "годину", "hours": ["{0} годину", "{0} години", "{0} годин"], "day": "день", "days": ["{0} день", "{0} дні", "{0} днів"], "month": "місяць", "months": ["{0} місяць", "{0} місяці", "{0} місяців"], "year": "рік", "years": ["{0} рік", "{0} роки", "{0} років"], } month_names = [ "", "січня", "лютого", "березня", "квітня", "травня", "червня", "липня", "серпня", "вересня", "жовтня", "листопада", "грудня", ] month_abbreviations = [ "", "січ", "лют", "бер", "квіт", "трав", "черв", "лип", "серп", "вер", "жовт", "лист", "груд", ] day_names = [ "", "понеділок", "вівторок", "середа", "четвер", "п’ятниця", "субота", "неділя", ] day_abbreviations = ["", "пн", "вт", "ср", "чт", "пт", "сб", "нд"] class MacedonianLocale(SlavicBaseLocale): names = ["mk", "mk_mk"] past = "пред {0}" future = "за {0}" timeframes = { "now": "сега", "second": "секунда", "seconds": "{0} секунди", "minute": "една минута", "minutes": ["{0} минута", "{0} минути", "{0} минути"], "hour": "еден саат", "hours": ["{0} саат", "{0} саати", "{0} саати"], "day": "еден ден", "days": ["{0} ден", "{0} дена", "{0} дена"], "month": "еден месец", "months": ["{0} месец", "{0} месеци", "{0} месеци"], "year": "една година", "years": ["{0} година", "{0} години", "{0} години"], } meridians = {"am": "дп", "pm": "пп", "AM": "претпладне", "PM": "попладне"} month_names = [ "", "Јануари", "Февруари", "Март", "Април", "Мај", "Јуни", "Јули", "Август", "Септември", "Октомври", "Ноември", "Декември", ] month_abbreviations = [ "", "Јан.", " Фев.", " Мар.", " Апр.", " Мај", " Јун.", " Јул.", " Авг.", " Септ.", " Окт.", " Ноем.", " Декем.", ] day_names = [ "", "Понеделник", " Вторник", " Среда", " Четврток", " Петок", " Сабота", " Недела", ] day_abbreviations = [ "", "Пон.", " Вт.", " Сре.", " Чет.", " Пет.", " Саб.", " Нед.", ] class DeutschBaseLocale(Locale): past = "vor {0}" future = "in {0}" and_word = "und" timeframes = { "now": "gerade eben", "second": "eine Sekunde", "seconds": "{0} Sekunden", "minute": "einer Minute", "minutes": "{0} Minuten", "hour": "einer Stunde", "hours": "{0} Stunden", "day": "einem Tag", "days": "{0} Tagen", "month": "einem Monat", "months": "{0} Monaten", "year": "einem Jahr", "years": "{0} Jahren", } timeframes_only_distance = timeframes.copy() timeframes_only_distance["minute"] = "eine Minute" timeframes_only_distance["hour"] = "eine Stunde" timeframes_only_distance["day"] = "ein Tag" timeframes_only_distance["month"] = "ein Monat" timeframes_only_distance["year"] = "ein Jahr" 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): return "{}.".format(n) def describe(self, timeframe, delta=0, only_distance=False): """ 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.timeframes_only_distance[timeframe].format(trunc(abs(delta))) if not only_distance: humanized = self._format_timeframe(timeframe, delta) humanized = self._format_relative(humanized, timeframe, delta) return humanized class GermanLocale(DeutschBaseLocale, Locale): names = ["de", "de_de"] class AustrianLocale(DeutschBaseLocale, 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": "et sekund", "seconds": "{0} noen 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": "et sekund", "seconds": "{0} nokre sekund", "minute": "ett 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": "eit å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", "maio", "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}" future = "em {0}" 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 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", "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"] def _ordinal_number(self, n): return "ika-{}".format(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" 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", "month": "bir ay", "months": "{0} ay", "year": "yıl", "years": "{0} yıl", } 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": "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", "month": "bir ay", "months": "{0} ay", "year": "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 = { "now": "الآن", "second": "ثانية", "seconds": {"double": "ثانيتين", "ten": "{0} ثوان", "higher": "{0} ثانية"}, "minute": "دقيقة", "minutes": {"double": "دقيقتين", "ten": "{0} دقائق", "higher": "{0} دقيقة"}, "hour": "ساعة", "hours": {"double": "ساعتين", "ten": "{0} ساعات", "higher": "{0} ساعة"}, "day": "يوم", "days": {"double": "يومين", "ten": "{0} أيام", "higher": "{0} يوم"}, "month": "شهر", "months": {"double": "شهرين", "ten": "{0} أشهر", "higher": "{0} شهر"}, "year": "سنة", "years": {"double": "سنتين", "ten": "{0} سنوات", "higher": "{0} سنة"}, } month_names = [ "", "يناير", "فبراير", "مارس", "أبريل", "مايو", "يونيو", "يوليو", "أغسطس", "سبتمبر", "أكتوبر", "نوفمبر", "ديسمبر", ] month_abbreviations = [ "", "يناير", "فبراير", "مارس", "أبريل", "مايو", "يونيو", "يوليو", "أغسطس", "سبتمبر", "أكتوبر", "نوفمبر", "ديسمبر", ] day_names = [ "", "الإثنين", "الثلاثاء", "الأربعاء", "الخميس", "الجمعة", "السبت", "الأحد", ] day_abbreviations = ["", "إثنين", "ثلاثاء", "أربعاء", "خميس", "جمعة", "سبت", "أحد"] def _format_timeframe(self, timeframe, delta): form = self.timeframes[timeframe] delta = abs(delta) if isinstance(form, dict): if delta == 2: form = form["double"] elif delta > 2 and 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, delta): timeframe = self.timeframes[timeframe] if delta < 0: timeframe = timeframe[0] elif delta > 0: timeframe = timeframe[1] return timeframe.format(abs(delta)) names = ["is", "is_is"] past = "fyrir {0} síðan" future = "eftir {0}" timeframes = { "now": "rétt í þessu", "second": ("sekúndu", "sekúndu"), "seconds": ("{0} nokkrum sekúndum", "nokkrar sekúndur"), "minute": ("einni mínútu", "eina mínútu"), "minutes": ("{0} mínútum", "{0} mínútur"), "hour": ("einum tíma", "einn tíma"), "hours": ("{0} tímum", "{0} tíma"), "day": ("einum degi", "einn dag"), "days": ("{0} dögum", "{0} daga"), "month": ("einum mánuði", "einn mánuð"), "months": ("{0} mánuðum", "{0} mánuði"), "year": ("einu ári", "eitt ár"), "years": ("{0} árum", "{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"] 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 = { "now": "Teď", "second": {"past": "vteřina", "future": "vteřina", "zero": "vteřina"}, "seconds": {"past": "{0} sekundami", "future": ["{0} sekundy", "{0} sekund"]}, "minute": {"past": "minutou", "future": "minutu", "zero": "{0} minut"}, "minutes": {"past": "{0} minutami", "future": ["{0} minuty", "{0} minut"]}, "hour": {"past": "hodinou", "future": "hodinu", "zero": "{0} hodin"}, "hours": {"past": "{0} hodinami", "future": ["{0} hodiny", "{0} hodin"]}, "day": {"past": "dnem", "future": "den", "zero": "{0} dnů"}, "days": {"past": "{0} dny", "future": ["{0} dny", "{0} dnů"]}, "month": {"past": "měsícem", "future": "měsíc", "zero": "{0} měsíců"}, "months": {"past": "{0} měsíci", "future": ["{0} měsíce", "{0} měsíců"]}, "year": {"past": "rokem", "future": "rok", "zero": "{0} let"}, "years": {"past": "{0} lety", "future": ["{0} roky", "{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, delta): """Czech aware time frame format function, takes into account the differences between past and future forms.""" form = self.timeframes[timeframe] if isinstance(form, dict): if delta == 0: form = form["zero"] # And *never* use 0 in the singular! elif delta > 0: form = form["future"] else: form = form["past"] delta = abs(delta) if isinstance(form, list): if 2 <= delta % 10 <= 4 and (delta % 100 < 10 or delta % 100 >= 20): form = form[0] else: form = form[1] return form.format(delta) class SlovakLocale(Locale): names = ["sk", "sk_sk"] timeframes = { "now": "Teraz", "second": {"past": "druhý", "future": "druhý", "zero": "druhý"}, "seconds": {"past": "pár sekundami", "future": ["{0} sekundy", "{0} sekúnd"]}, "minute": {"past": "minútou", "future": "minútu", "zero": "{0} minút"}, "minutes": {"past": "{0} minútami", "future": ["{0} minúty", "{0} minút"]}, "hour": {"past": "hodinou", "future": "hodinu", "zero": "{0} hodín"}, "hours": {"past": "{0} hodinami", "future": ["{0} hodiny", "{0} hodín"]}, "day": {"past": "dňom", "future": "deň", "zero": "{0} dní"}, "days": {"past": "{0} dňami", "future": ["{0} dni", "{0} dní"]}, "month": {"past": "mesiacom", "future": "mesiac", "zero": "{0} mesiacov"}, "months": {"past": "{0} mesiacmi", "future": ["{0} mesiace", "{0} mesiacov"]}, "year": {"past": "rokom", "future": "rok", "zero": "{0} rokov"}, "years": {"past": "{0} rokmi", "future": ["{0} roky", "{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, delta): """Slovak aware time frame format function, takes into account the differences between past and future forms.""" form = self.timeframes[timeframe] if isinstance(form, dict): if delta == 0: form = form["zero"] # And *never* use 0 in the singular! elif delta > 0: form = form["future"] else: form = form["past"] delta = abs(delta) if isinstance(form, list): if 2 <= delta % 10 <= 4 and (delta % 100 < 10 or delta % 100 >= 20): form = form[0] else: form = form[1] return form.format(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}" timeframes = { "now": "הרגע", "second": "שנייה", "seconds": "{0} שניות", "minute": "דקה", "minutes": "{0} דקות", "hour": "שעה", "hours": "{0} שעות", "2-hours": "שעתיים", "day": "יום", "days": "{0} ימים", "2-days": "יומיים", "month": "חודש", "months": "{0} חודשים", "2-months": "חודשיים", "year": "שנה", "years": "{0} שנים", "2-years": "שנתיים", } meridians = { "am": 'לפנ"צ', "pm": 'אחר"צ', "AM": "לפני הצהריים", "PM": "אחרי הצהריים", } month_names = [ "", "ינואר", "פברואר", "מרץ", "אפריל", "מאי", "יוני", "יולי", "אוגוסט", "ספטמבר", "אוקטובר", "נובמבר", "דצמבר", ] month_abbreviations = [ "", "ינו׳", "פבר׳", "מרץ", "אפר׳", "מאי", "יוני", "יולי", "אוג׳", "ספט׳", "אוק׳", "נוב׳", "דצמ׳", ] day_names = ["", "שני", "שלישי", "רביעי", "חמישי", "שישי", "שבת", "ראשון"] day_abbreviations = ["", "ב׳", "ג׳", "ד׳", "ה׳", "ו׳", "ש׳", "א׳"] def _format_timeframe(self, timeframe, delta): """Hebrew couple of aware""" couple = "2-{}".format(timeframe) if abs(delta) == 2 and couple in self.timeframes: return self.timeframes[couple].format(abs(delta)) else: return self.timeframes[timeframe].format(abs(delta)) 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 = ["", "सोम", "मंगळ", "बुध", "गुरु", "शुक्र", "शनि", "रवि"] def _map_locales(): locales = {} for _, cls in inspect.getmembers(sys.modules[__name__], inspect.isclass): if issubclass(cls, Locale): # pragma: no branch for name in cls.names: locales[name.lower()] = cls return locales 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": "1 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 = [ "", "Gener", "Febrer", "Març", "Abril", "Maig", "Juny", "Juliol", "Agost", "Setembre", "Octubre", "Novembre", "Desembre", ] day_names = [ "", "Dilluns", "Dimarts", "Dimecres", "Dijous", "Divendres", "Dissabte", "Diumenge", ] day_abbreviations = [ "", "Dilluns", "Dimarts", "Dimecres", "Dijous", "Divendres", "Dissabte", "Diumenge", ] 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 = { "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, delta): form = self.timeframes[timeframe] if isinstance(form, dict): 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): return "{}a".format(n) class ThaiLocale(Locale): names = ["th", "th_th"] past = "{0}{1}ที่ผ่านมา" future = "ในอีก{1}{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): """Thai always use Buddhist Era (BE) which is CE + 543""" year += self.BE_OFFSET return "{:04d}".format(year) def year_abbreviation(self, year): """Thai always use Buddhist Era (BE) which is CE + 543""" year += self.BE_OFFSET return "{:04d}".format(year)[2:] def _format_relative(self, humanized, timeframe, delta): """Thai normally doesn't have any space between words""" if timeframe == "now": return humanized space = "" if timeframe == "seconds" else " " direction = self.past if delta < 0 else self.future return direction.format(humanized, space) 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): if n > 10 or n == 0: return "{}তম".format(n) if n in [1, 5, 7, 8, 9, 10]: return "{}ম".format(n) if n in [2, 3]: return "{}য়".format(n) if n == 4: return "{}র্থ".format(n) if n == 6: return "{}ষ্ঠ".format(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 SwissLocale(Locale): names = ["de", "de_ch"] past = "vor {0}" future = "in {0}" timeframes = { "now": "gerade eben", "second": "eine 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", } 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"] 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": "बर्ष", } 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 = { "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, delta): form = self.timeframes[timeframe] if delta > 0: form = form["future"] else: form = form["past"] return form.format(abs(delta)) _locales = _map_locales() arrow-0.15.5/arrow/parser.py0000664000175000017500000004564413603422516016217 0ustar chrischris00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals import re from datetime import datetime, timedelta from dateutil import tz from arrow import locales from arrow.constants import MAX_TIMESTAMP, MAX_TIMESTAMP_MS, MAX_TIMESTAMP_US try: from functools import lru_cache except ImportError: # pragma: no cover from backports.functools_lru_cache import lru_cache # 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 class DateTimeParser(object): _FORMAT_RE = 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)" ) _ESCAPE_RE = re.compile(r"\[[^\[\]]*\]") _ONE_OR_TWO_DIGIT_RE = re.compile(r"\d{1,2}") _ONE_OR_TWO_OR_THREE_DIGIT_RE = re.compile(r"\d{1,3}") _ONE_OR_MORE_DIGIT_RE = re.compile(r"\d+") _TWO_DIGIT_RE = re.compile(r"\d{2}") _THREE_DIGIT_RE = re.compile(r"\d{3}") _FOUR_DIGIT_RE = re.compile(r"\d{4}") _TZ_Z_RE = re.compile(r"([\+\-])(\d{2})(?:(\d{2}))?|Z") _TZ_ZZ_RE = re.compile(r"([\+\-])(\d{2})(?:\:(\d{2}))?|Z") _TZ_NAME_RE = 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 = re.compile(r"^\-?\d+\.?\d+$") _TIMESTAMP_EXPANDED_RE = re.compile(r"^\-?\d+$") _TIME_RE = re.compile(r"^(\d{2})(?:\:?(\d{2}))?(?:\:?(\d{2}))?(?:([\.\,])(\d+))?$") _BASE_INPUT_RE_MAP = { "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, } SEPARATORS = ["-", "/", "."] def __init__(self, locale="en_us", cache_size=0): 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)( 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): # TODO: add a flag to normalize whitespace (useful in logs, ref issue #421) 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( "Expected an ISO 8601-like string, but was given '{}'. Try passing in a format string to resolve this.".format( datetime_string ) ) 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", ] 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 = 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 = "HH{time_sep}mm".format(time_sep=time_sep) else: time_string = "HH" if has_space_divider: formats = ["{} {}".format(f, time_string) for f in formats] else: formats = ["{}T{}".format(f, 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 = ["{}{}".format(f, tz_format) for f in formats] return self._parse_multiformat(datetime_string, formats) def parse(self, datetime_string, fmt): if isinstance(fmt, list): return self._parse_multiformat(datetime_string, fmt) fmt_tokens, fmt_pattern_re = self._generate_pattern_re(fmt) match = fmt_pattern_re.search(datetime_string) if match is None: raise ParserMatchError( "Failed to match '{}' when parsing '{}'".format(fmt, datetime_string) ) parts = {} for token in fmt_tokens: if token == "Do": value = match.group("value") else: value = match.group(token) self._parse_token(token, value, parts) return self._build_datetime(parts) def _generate_pattern_re(self, fmt): # 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 = [] 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 = m.group(0) try: input_re = self._input_re_map[token] except KeyError: raise ParserError("Unrecognized token '{}'".format(token)) input_pattern = "(?P<{}>{})".format(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) def _parse_token(self, token, value, parts): 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"]: parts["month"] = self.locale.month_number(value.lower()) 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 in ["Do"]: parts["day"] = int(value) 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, str("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" elif value in (self.locale.meridians["pm"], self.locale.meridians["PM"]): parts["am_pm"] = "pm" @staticmethod def _build_datetime(parts): 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: if expanded_timestamp > MAX_TIMESTAMP: if expanded_timestamp < MAX_TIMESTAMP_MS: expanded_timestamp /= 1000.0 elif expanded_timestamp < MAX_TIMESTAMP_US: expanded_timestamp /= 1000000.0 else: raise ValueError( "The specified timestamp '{}' is too large.".format( expanded_timestamp ) ) return datetime.fromtimestamp(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 = "{}-{}".format(year, day_of_year) try: dt = datetime.strptime(date_string, "%Y-%j") except ValueError: raise ParserError( "The provided day of year '{}' is invalid.".format(day_of_year) ) parts["year"] = dt.year parts["month"] = dt.month parts["day"] = 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, formats): _datetime = None for fmt in formats: try: _datetime = self.parse(string, fmt) break except ParserMatchError: pass if _datetime is None: raise ParserError( "Could not match input '{}' to any of the following formats: {}".format( string, ", ".join(formats) ) ) return _datetime # generates a capture group of choices separated by an OR operator @staticmethod def _generate_choice_re(choices, flags=0): return re.compile(r"({})".format("|".join(choices)), flags=flags) class TzinfoParser(object): _TZINFO_RE = re.compile(r"^([\+\-])?(\d{2})(?:\:?(\d{2}))?$") @classmethod def parse(cls, tzinfo_string): 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, hours, minutes = iso_match.groups() if minutes is None: minutes = 0 seconds = int(hours) * 3600 + int(minutes) * 60 if sign == "-": seconds *= -1 tzinfo = tz.tzoffset(None, seconds) else: tzinfo = tz.gettz(tzinfo_string) if tzinfo is None: raise ParserError( 'Could not parse timezone expression "{}"'.format(tzinfo_string) ) return tzinfo arrow-0.15.5/arrow/util.py0000664000175000017500000000301213603422516015657 0ustar chrischris00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import import datetime def total_seconds(td): """Get total seconds for timedelta.""" return td.total_seconds() def is_timestamp(value): """Check if value is a valid timestamp.""" if isinstance(value, bool): return False if not ( isinstance(value, int) or isinstance(value, float) or isinstance(value, str) ): return False try: float(value) return True except ValueError: return False # Credit to https://stackoverflow.com/a/1700069 def iso_to_gregorian(iso_year, iso_week, iso_day): """Converts an ISO week date tuple into a datetime object.""" 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 # Python 2.7 / 3.0+ definitions for isstr function. try: # pragma: no cover basestring def isstr(s): return isinstance(s, basestring) # noqa: F821 except NameError: # pragma: no cover def isstr(s): return isinstance(s, str) __all__ = ["total_seconds", "is_timestamp", "isstr", "iso_to_gregorian"] arrow-0.15.5/arrow.egg-info/0000775000175000017500000000000013603666233016034 5ustar chrischris00000000000000arrow-0.15.5/arrow.egg-info/PKG-INFO0000664000175000017500000001547513603666233017145 0ustar chrischris00000000000000Metadata-Version: 2.1 Name: arrow Version: 0.15.5 Summary: Better dates & times for Python Home-page: https://arrow.readthedocs.io Author: Chris Smith Author-email: crsmithdev@gmail.com License: Apache 2.0 Project-URL: Repository, https://github.com/crsmithdev/arrow Project-URL: Bug Reports, https://github.com/crsmithdev/arrow/issues Project-URL: Documentation, https://arrow.readthedocs.io Description: Arrow: Better dates & times for Python ====================================== .. start-inclusion-marker-do-not-remove .. image:: https://travis-ci.org/crsmithdev/arrow.svg?branch=master :alt: Build Status :target: https://travis-ci.org/crsmithdev/arrow .. image:: https://codecov.io/github/crsmithdev/arrow/coverage.svg?branch=master :alt: Codecov :target: https://codecov.io/github/crsmithdev/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 - Supports Python 2.7, 3.5, 3.6, 3.7 and 3.8 - Timezone-aware and UTC by default - Provides super-simple creation options for many common input scenarios - :code:`shift` method with support for relative offsets, including weeks - Formats and parses strings automatically - Wide support for ISO 8601 - Timezone conversion - Timestamp available as a property - Generates time spans, ranges, floors and ceilings for time frames ranging from microsecond to year - Humanizes and supports a growing list of contributed locales - Extensible for your own Arrow-derived types 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 >>> 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') '1시간 전' .. 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: 1. 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! 2. Fork `this repository `_ on GitHub and begin making changes in a branch. 3. Add a few tests to ensure that the bug was fixed or the feature works as expected. 4. Submit a pull request and await feedback 😃. If you have any questions along the way, feel free to ask them `here `_. Keywords: arrow date time datetime timestamp timezone Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* Description-Content-Type: text/x-rst arrow-0.15.5/arrow.egg-info/SOURCES.txt0000664000175000017500000000122513603666233017720 0ustar chrischris00000000000000CHANGELOG.rst LICENSE MANIFEST.in README.rst setup.cfg setup.py arrow/__init__.py arrow/_version.py arrow/api.py arrow/arrow.py arrow/constants.py arrow/factory.py arrow/formatter.py arrow/locales.py arrow/parser.py arrow/util.py arrow.egg-info/PKG-INFO arrow.egg-info/SOURCES.txt arrow.egg-info/dependency_links.txt arrow.egg-info/not-zip-safe arrow.egg-info/requires.txt arrow.egg-info/top_level.txt docs/Makefile docs/conf.py docs/index.rst docs/make.bat docs/releases.rst tests/__init__.py tests/api_tests.py tests/arrow_tests.py tests/factory_tests.py tests/formatter_tests.py tests/locales_tests.py tests/parser_tests.py tests/util_tests.py tests/utils.pyarrow-0.15.5/arrow.egg-info/dependency_links.txt0000664000175000017500000000000113603666233022102 0ustar chrischris00000000000000 arrow-0.15.5/arrow.egg-info/not-zip-safe0000664000175000017500000000000113603666233020262 0ustar chrischris00000000000000 arrow-0.15.5/arrow.egg-info/requires.txt0000664000175000017500000000012113603666233020426 0ustar chrischris00000000000000python-dateutil [:python_version == "2.7"] backports.functools_lru_cache>=1.2.1 arrow-0.15.5/arrow.egg-info/top_level.txt0000664000175000017500000000000613603666233020562 0ustar chrischris00000000000000arrow arrow-0.15.5/docs/0000775000175000017500000000000013603666233014140 5ustar chrischris00000000000000arrow-0.15.5/docs/Makefile0000664000175000017500000000117213521652254015576 0ustar chrischris00000000000000# 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) arrow-0.15.5/docs/conf.py0000664000175000017500000000252113535221115015425 0ustar chrischris00000000000000# -*- coding: utf-8 -*- # -- Path setup -------------------------------------------------------------- import io import os import sys sys.path.insert(0, os.path.abspath("..")) about = {} with io.open("../arrow/_version.py", "r", encoding="utf-8") as f: exec(f.read(), about) # -- Project information ----------------------------------------------------- project = u"Arrow 🏹" copyright = "2019, Chris Smith" author = "Chris Smith" release = about["__version__"] # -- General configuration --------------------------------------------------- extensions = ["sphinx.ext.autodoc"] 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 html_theme_options = { "description": "Arrow is a sensible and human-friendly approach to dates, times and timestamps.", "github_user": "crsmithdev", "github_repo": "arrow", "github_banner": True, "show_related": False, "show_powered_by": False, } html_sidebars = { "**": ["about.html", "localtoc.html", "relations.html", "searchbox.html"] } arrow-0.15.5/docs/index.rst0000664000175000017500000004143313603664774016016 0ustar chrischris00000000000000Arrow: 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()) >>> a.timestamp 1367901495 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') 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 час(а,ов)' 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 in parsing and formatting. Note that they're not the same as the tokens for `strptime(3) `_: +--------------------------------+--------------+-------------------------------------------+ | |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 | +--------------------------------+--------------+-------------------------------------------+ |**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 .. [#t5] this token cannot be used for parsing timestamps out of natural language strings due to compatibility reasons 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 formats may be fenced on either side by one punctuation character from the following list: :literal:`, . ; : ? ! " \` ' [ ] { } ( ) < >` .. code-block:: python >>> 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 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 arrow-0.15.5/docs/make.bat0000664000175000017500000000137013535221115015534 0ustar chrischris00000000000000@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 arrow-0.15.5/docs/releases.rst0000664000175000017500000000005513537436051016474 0ustar chrischris00000000000000.. _releases: .. include:: ../CHANGELOG.rst arrow-0.15.5/setup.cfg0000664000175000017500000000106213603666233015030 0ustar chrischris00000000000000[nosetests] where = tests verbosity = 2 all-modules = true with-coverage = true cover-min-percentage = 100 cover-package = arrow cover-erase = true [coverage:run] branch = true source = arrow tests [coverage:report] show_missing = true fail_under = 100 [flake8] per-file-ignores = arrow/__init__.py:F401 ignore = E203,E501,W503 [tool:isort] line_length = 88 multi_line_output = 3 include_trailing_comma = true known_third_party = chai,dateparser,dateutil,mock,pytz,setuptools,simplejson [bdist_wheel] universal = 1 [egg_info] tag_build = tag_date = 0 arrow-0.15.5/setup.py0000664000175000017500000000330013535221115014704 0ustar chrischris00000000000000# -*- coding: utf-8 -*- import io from setuptools import setup with io.open("README.rst", "r", encoding="utf-8") as f: readme = f.read() about = {} with io.open("arrow/_version.py", "r", encoding="utf-8") as f: exec(f.read(), 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"], zip_safe=False, python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", install_requires=[ "python-dateutil", "backports.functools_lru_cache>=1.2.1;python_version=='2.7'", ], test_suite="tests", tests_require=["chai", "mock"], classifiers=[ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Topic :: Software Development :: Libraries :: Python Modules", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", ], keywords="arrow date time datetime timestamp timezone", project_urls={ "Repository": "https://github.com/crsmithdev/arrow", "Bug Reports": "https://github.com/crsmithdev/arrow/issues", "Documentation": "https://arrow.readthedocs.io", }, ) arrow-0.15.5/tests/0000775000175000017500000000000013603666233014352 5ustar chrischris00000000000000arrow-0.15.5/tests/__init__.py0000664000175000017500000000000013250061464016442 0ustar chrischris00000000000000arrow-0.15.5/tests/api_tests.py0000664000175000017500000000145413521652254016720 0ustar chrischris00000000000000# -*- coding: utf-8 -*- from chai import Chai from arrow import api, arrow, factory class ModuleTests(Chai): def test_get(self): self.expect(api._factory.get).args(1, b=2).returns("result") self.assertEqual(api.get(1, b=2), "result") def test_utcnow(self): self.expect(api._factory.utcnow).returns("utcnow") self.assertEqual(api.utcnow(), "utcnow") def test_now(self): self.expect(api._factory.now).args("tz").returns("now") self.assertEqual(api.now("tz"), "now") def test_factory(self): class MockCustomArrowClass(arrow.Arrow): pass result = api.factory(MockCustomArrowClass) self.assertIsInstance(result, factory.ArrowFactory) self.assertIsInstance(result.utcnow(), MockCustomArrowClass) arrow-0.15.5/tests/arrow_tests.py0000664000175000017500000020640413603664774017316 0ustar chrischris00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals import calendar import pickle import sys import time from datetime import date, datetime, timedelta import pytz import simplejson as json from chai import Chai from dateutil import tz from dateutil.relativedelta import FR, MO, SA, SU, TH, TU, WE from mock import patch from arrow import arrow, util def assertDtEqual(dt1, dt2, within=10): assertEqual(dt1.tzinfo, dt2.tzinfo) # noqa: F821 assertTrue(abs(util.total_seconds(dt1 - dt2)) < within) # noqa: F821 class ArrowInitTests(Chai): def test_init_bad_input(self): with self.assertRaises(TypeError): arrow.Arrow(2013) with self.assertRaises(TypeError): arrow.Arrow(2013, 2) with self.assertRaises(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()) self.assertEqual(result._datetime, self.expected) result = arrow.Arrow(2013, 2, 2, 12) self.expected = datetime(2013, 2, 2, 12, tzinfo=tz.tzutc()) self.assertEqual(result._datetime, self.expected) result = arrow.Arrow(2013, 2, 2, 12, 30) self.expected = datetime(2013, 2, 2, 12, 30, tzinfo=tz.tzutc()) self.assertEqual(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()) self.assertEqual(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()) self.assertEqual(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") ) self.assertEqual(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") ) self.assertEqual(result._datetime, self.expected) assertDtEqual(result._datetime, self.expected, 1) class ArrowFactoryTests(Chai): def test_now(self): result = arrow.Arrow.now() assertDtEqual(result._datetime, datetime.now().replace(tzinfo=tz.tzlocal())) def test_utcnow(self): result = arrow.Arrow.utcnow() assertDtEqual(result._datetime, datetime.utcnow().replace(tzinfo=tz.tzutc())) def test_fromtimestamp(self): timestamp = time.time() result = arrow.Arrow.fromtimestamp(timestamp) assertDtEqual(result._datetime, datetime.now().replace(tzinfo=tz.tzlocal())) result = arrow.Arrow.fromtimestamp(timestamp, tzinfo=tz.gettz("Europe/Paris")) assertDtEqual( result._datetime, datetime.fromtimestamp(timestamp, tz.gettz("Europe/Paris")), ) result = arrow.Arrow.fromtimestamp(timestamp, tzinfo="Europe/Paris") assertDtEqual( result._datetime, datetime.fromtimestamp(timestamp, tz.gettz("Europe/Paris")), ) with self.assertRaises(ValueError): arrow.Arrow.fromtimestamp("invalid timestamp") def test_utcfromtimestamp(self): timestamp = time.time() result = arrow.Arrow.utcfromtimestamp(timestamp) assertDtEqual(result._datetime, datetime.utcnow().replace(tzinfo=tz.tzutc())) with self.assertRaises(ValueError): arrow.Arrow.utcfromtimestamp("invalid timestamp") def test_fromdatetime(self): dt = datetime(2013, 2, 3, 12, 30, 45, 1) result = arrow.Arrow.fromdatetime(dt) self.assertEqual(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) self.assertEqual(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")) self.assertEqual(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")) self.assertEqual( 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") self.assertEqual( 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") ) self.assertEqual( result._datetime, datetime(2013, 2, 3, 12, 30, 45, tzinfo=tz.gettz("Europe/Paris")), ) class ArrowRepresentationTests(Chai): def setUp(self): super(ArrowRepresentationTests, self).setUp() self.arrow = arrow.Arrow(2013, 2, 3, 12, 30, 45, 1) def test_repr(self): result = self.arrow.__repr__() self.assertEqual( result, "".format(self.arrow._datetime.isoformat()) ) def test_str(self): result = self.arrow.__str__() self.assertEqual(result, self.arrow._datetime.isoformat()) def test_hash(self): result = self.arrow.__hash__() self.assertEqual(result, self.arrow._datetime.__hash__()) def test_format(self): result = "{:YYYY-MM-DD}".format(self.arrow) self.assertEqual(result, "2013-02-03") def test_bare_format(self): result = self.arrow.format() self.assertEqual(result, "2013-02-03 12:30:45+00:00") def test_format_no_format_string(self): result = "{}".format(self.arrow) self.assertEqual(result, str(self.arrow)) def test_clone(self): result = self.arrow.clone() self.assertTrue(result is not self.arrow) self.assertEqual(result._datetime, self.arrow._datetime) class ArrowAttributeTests(Chai): def setUp(self): super(ArrowAttributeTests, self).setUp() self.arrow = arrow.Arrow(2013, 1, 1) def test_getattr_base(self): with self.assertRaises(AttributeError): self.arrow.prop def test_getattr_week(self): self.assertEqual(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) self.assertEqual(q1.quarter, 1) self.assertEqual(q2.quarter, 2) self.assertEqual(q3.quarter, 3) self.assertEqual(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) self.assertEqual(q1.quarter, 1) self.assertEqual(q2.quarter, 2) self.assertEqual(q3.quarter, 3) self.assertEqual(q4.quarter, 4) def test_getattr_dt_value(self): self.assertEqual(self.arrow.year, 2013) def test_tzinfo(self): self.arrow.tzinfo = tz.gettz("PST") self.assertEqual(self.arrow.tzinfo, tz.gettz("PST")) def test_naive(self): self.assertEqual(self.arrow.naive, self.arrow._datetime.replace(tzinfo=None)) def test_timestamp(self): self.assertEqual( self.arrow.timestamp, calendar.timegm(self.arrow._datetime.utctimetuple()) ) def test_float_timestamp(self): result = self.arrow.float_timestamp - self.arrow.timestamp self.assertEqual(result, self.arrow.microsecond) class ArrowComparisonTests(Chai): def setUp(self): super(ArrowComparisonTests, self).setUp() self.arrow = arrow.Arrow.utcnow() def test_eq(self): self.assertTrue(self.arrow == self.arrow) self.assertTrue(self.arrow == self.arrow.datetime) self.assertFalse(self.arrow == "abc") def test_ne(self): self.assertFalse(self.arrow != self.arrow) self.assertFalse(self.arrow != self.arrow.datetime) self.assertTrue(self.arrow != "abc") def test_gt(self): arrow_cmp = self.arrow.shift(minutes=1) self.assertFalse(self.arrow > self.arrow) self.assertFalse(self.arrow > self.arrow.datetime) with self.assertRaises(TypeError): self.arrow > "abc" self.assertTrue(self.arrow < arrow_cmp) self.assertTrue(self.arrow < arrow_cmp.datetime) def test_ge(self): with self.assertRaises(TypeError): self.arrow >= "abc" self.assertTrue(self.arrow >= self.arrow) self.assertTrue(self.arrow >= self.arrow.datetime) def test_lt(self): arrow_cmp = self.arrow.shift(minutes=1) self.assertFalse(self.arrow < self.arrow) self.assertFalse(self.arrow < self.arrow.datetime) with self.assertRaises(TypeError): self.arrow < "abc" self.assertTrue(self.arrow < arrow_cmp) self.assertTrue(self.arrow < arrow_cmp.datetime) def test_le(self): with self.assertRaises(TypeError): self.arrow <= "abc" self.assertTrue(self.arrow <= self.arrow) self.assertTrue(self.arrow <= self.arrow.datetime) class ArrowMathTests(Chai): def setUp(self): super(ArrowMathTests, self).setUp() self.arrow = arrow.Arrow(2013, 1, 1) def test_add_timedelta(self): result = self.arrow.__add__(timedelta(days=1)) self.assertEqual(result._datetime, datetime(2013, 1, 2, tzinfo=tz.tzutc())) def test_add_other(self): with self.assertRaises(TypeError): self.arrow + 1 def test_radd(self): result = self.arrow.__radd__(timedelta(days=1)) self.assertEqual(result._datetime, datetime(2013, 1, 2, tzinfo=tz.tzutc())) def test_sub_timedelta(self): result = self.arrow.__sub__(timedelta(days=1)) self.assertEqual(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())) self.assertEqual(result, timedelta(days=11)) def test_sub_arrow(self): result = self.arrow.__sub__(arrow.Arrow(2012, 12, 21, tzinfo=tz.tzutc())) self.assertEqual(result, timedelta(days=11)) def test_sub_other(self): with self.assertRaises(TypeError): self.arrow - object() def test_rsub_datetime(self): result = self.arrow.__rsub__(datetime(2012, 12, 21, tzinfo=tz.tzutc())) self.assertEqual(result, timedelta(days=-11)) def test_rsub_other(self): with self.assertRaises(TypeError): timedelta(days=1) - self.arrow class ArrowDatetimeInterfaceTests(Chai): def setUp(self): super(ArrowDatetimeInterfaceTests, self).setUp() self.arrow = arrow.Arrow.utcnow() def test_date(self): result = self.arrow.date() self.assertEqual(result, self.arrow._datetime.date()) def test_time(self): result = self.arrow.time() self.assertEqual(result, self.arrow._datetime.time()) def test_timetz(self): result = self.arrow.timetz() self.assertEqual(result, self.arrow._datetime.timetz()) def test_astimezone(self): other_tz = tz.gettz("US/Pacific") result = self.arrow.astimezone(other_tz) self.assertEqual(result, self.arrow._datetime.astimezone(other_tz)) def test_utcoffset(self): result = self.arrow.utcoffset() self.assertEqual(result, self.arrow._datetime.utcoffset()) def test_dst(self): result = self.arrow.dst() self.assertEqual(result, self.arrow._datetime.dst()) def test_timetuple(self): result = self.arrow.timetuple() self.assertEqual(result, self.arrow._datetime.timetuple()) def test_utctimetuple(self): result = self.arrow.utctimetuple() self.assertEqual(result, self.arrow._datetime.utctimetuple()) def test_toordinal(self): result = self.arrow.toordinal() self.assertEqual(result, self.arrow._datetime.toordinal()) def test_weekday(self): result = self.arrow.weekday() self.assertEqual(result, self.arrow._datetime.weekday()) def test_isoweekday(self): result = self.arrow.isoweekday() self.assertEqual(result, self.arrow._datetime.isoweekday()) def test_isocalendar(self): result = self.arrow.isocalendar() self.assertEqual(result, self.arrow._datetime.isocalendar()) def test_isoformat(self): result = self.arrow.isoformat() self.assertEqual(result, self.arrow._datetime.isoformat()) def test_simplejson(self): result = json.dumps({"v": self.arrow.for_json()}, for_json=True) self.assertEqual(json.loads(result)["v"], self.arrow._datetime.isoformat()) def test_ctime(self): result = self.arrow.ctime() self.assertEqual(result, self.arrow._datetime.ctime()) def test_strftime(self): result = self.arrow.strftime("%Y") self.assertEqual(result, self.arrow._datetime.strftime("%Y")) class ArrowFalsePositiveDstTests(Chai): """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 setUp(self): super(ArrowFalsePositiveDstTests, self).setUp() 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") ) def test_dst(self): self.assertEqual(self.before_1.day, self.before_2.day) self.assertEqual(self.after_1.day, self.after_2.day) self.assertEqual(self.before_3.day, self.before_4.day) self.assertEqual(self.after_3.day, self.after_4.day) class ArrowConversionTests(Chai): 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() ) self.assertEqual(arrow_from.to("UTC").datetime, self.expected) self.assertEqual(arrow_from.to(tz.tzutc()).datetime, self.expected) class ArrowPicklingTests(Chai): def test_pickle_and_unpickle(self): dt = arrow.Arrow.utcnow() pickled = pickle.dumps(dt) unpickled = pickle.loads(pickled) self.assertEqual(unpickled, dt) class ArrowReplaceTests(Chai): def test_not_attr(self): with self.assertRaises(AttributeError): arrow.Arrow.utcnow().replace(abc=1) def test_replace(self): arw = arrow.Arrow(2013, 5, 5, 12, 30, 45) self.assertEqual(arw.replace(year=2012), arrow.Arrow(2012, 5, 5, 12, 30, 45)) self.assertEqual(arw.replace(month=1), arrow.Arrow(2013, 1, 5, 12, 30, 45)) self.assertEqual(arw.replace(day=1), arrow.Arrow(2013, 5, 1, 12, 30, 45)) self.assertEqual(arw.replace(hour=1), arrow.Arrow(2013, 5, 5, 1, 30, 45)) self.assertEqual(arw.replace(minute=1), arrow.Arrow(2013, 5, 5, 12, 1, 45)) self.assertEqual(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")) self.assertEqual(result, arw.datetime.replace(tzinfo=tz.gettz("US/Pacific"))) def test_replace_week(self): with self.assertRaises(AttributeError): arrow.Arrow.utcnow().replace(week=1) def test_replace_quarter(self): with self.assertRaises(AttributeError): arrow.Arrow.utcnow().replace(quarter=1) def test_replace_other_kwargs(self): with self.assertRaises(AttributeError): arrow.utcnow().replace(abc="def") class ArrowShiftTests(Chai): def test_not_attr(self): now = arrow.Arrow.utcnow() with self.assertRaises(AttributeError): now.shift(abc=1) with self.assertRaises(AttributeError): now.shift(week=1) def test_shift(self): arw = arrow.Arrow(2013, 5, 5, 12, 30, 45) self.assertEqual(arw.shift(years=1), arrow.Arrow(2014, 5, 5, 12, 30, 45)) self.assertEqual(arw.shift(quarters=1), arrow.Arrow(2013, 8, 5, 12, 30, 45)) self.assertEqual( arw.shift(quarters=1, months=1), arrow.Arrow(2013, 9, 5, 12, 30, 45) ) self.assertEqual(arw.shift(months=1), arrow.Arrow(2013, 6, 5, 12, 30, 45)) self.assertEqual(arw.shift(weeks=1), arrow.Arrow(2013, 5, 12, 12, 30, 45)) self.assertEqual(arw.shift(days=1), arrow.Arrow(2013, 5, 6, 12, 30, 45)) self.assertEqual(arw.shift(hours=1), arrow.Arrow(2013, 5, 5, 13, 30, 45)) self.assertEqual(arw.shift(minutes=1), arrow.Arrow(2013, 5, 5, 12, 31, 45)) self.assertEqual(arw.shift(seconds=1), arrow.Arrow(2013, 5, 5, 12, 30, 46)) self.assertEqual( arw.shift(microseconds=1), arrow.Arrow(2013, 5, 5, 12, 30, 45, 1) ) # Remember: Python's weekday 0 is Monday self.assertEqual(arw.shift(weekday=0), arrow.Arrow(2013, 5, 6, 12, 30, 45)) self.assertEqual(arw.shift(weekday=1), arrow.Arrow(2013, 5, 7, 12, 30, 45)) self.assertEqual(arw.shift(weekday=2), arrow.Arrow(2013, 5, 8, 12, 30, 45)) self.assertEqual(arw.shift(weekday=3), arrow.Arrow(2013, 5, 9, 12, 30, 45)) self.assertEqual(arw.shift(weekday=4), arrow.Arrow(2013, 5, 10, 12, 30, 45)) self.assertEqual(arw.shift(weekday=5), arrow.Arrow(2013, 5, 11, 12, 30, 45)) self.assertEqual(arw.shift(weekday=6), arw) with self.assertRaises(IndexError): arw.shift(weekday=7) # Use dateutil.relativedelta's convenient day instances self.assertEqual(arw.shift(weekday=MO), arrow.Arrow(2013, 5, 6, 12, 30, 45)) self.assertEqual(arw.shift(weekday=MO(0)), arrow.Arrow(2013, 5, 6, 12, 30, 45)) self.assertEqual(arw.shift(weekday=MO(1)), arrow.Arrow(2013, 5, 6, 12, 30, 45)) self.assertEqual(arw.shift(weekday=MO(2)), arrow.Arrow(2013, 5, 13, 12, 30, 45)) self.assertEqual(arw.shift(weekday=TU), arrow.Arrow(2013, 5, 7, 12, 30, 45)) self.assertEqual(arw.shift(weekday=TU(0)), arrow.Arrow(2013, 5, 7, 12, 30, 45)) self.assertEqual(arw.shift(weekday=TU(1)), arrow.Arrow(2013, 5, 7, 12, 30, 45)) self.assertEqual(arw.shift(weekday=TU(2)), arrow.Arrow(2013, 5, 14, 12, 30, 45)) self.assertEqual(arw.shift(weekday=WE), arrow.Arrow(2013, 5, 8, 12, 30, 45)) self.assertEqual(arw.shift(weekday=WE(0)), arrow.Arrow(2013, 5, 8, 12, 30, 45)) self.assertEqual(arw.shift(weekday=WE(1)), arrow.Arrow(2013, 5, 8, 12, 30, 45)) self.assertEqual(arw.shift(weekday=WE(2)), arrow.Arrow(2013, 5, 15, 12, 30, 45)) self.assertEqual(arw.shift(weekday=TH), arrow.Arrow(2013, 5, 9, 12, 30, 45)) self.assertEqual(arw.shift(weekday=TH(0)), arrow.Arrow(2013, 5, 9, 12, 30, 45)) self.assertEqual(arw.shift(weekday=TH(1)), arrow.Arrow(2013, 5, 9, 12, 30, 45)) self.assertEqual(arw.shift(weekday=TH(2)), arrow.Arrow(2013, 5, 16, 12, 30, 45)) self.assertEqual(arw.shift(weekday=FR), arrow.Arrow(2013, 5, 10, 12, 30, 45)) self.assertEqual(arw.shift(weekday=FR(0)), arrow.Arrow(2013, 5, 10, 12, 30, 45)) self.assertEqual(arw.shift(weekday=FR(1)), arrow.Arrow(2013, 5, 10, 12, 30, 45)) self.assertEqual(arw.shift(weekday=FR(2)), arrow.Arrow(2013, 5, 17, 12, 30, 45)) self.assertEqual(arw.shift(weekday=SA), arrow.Arrow(2013, 5, 11, 12, 30, 45)) self.assertEqual(arw.shift(weekday=SA(0)), arrow.Arrow(2013, 5, 11, 12, 30, 45)) self.assertEqual(arw.shift(weekday=SA(1)), arrow.Arrow(2013, 5, 11, 12, 30, 45)) self.assertEqual(arw.shift(weekday=SA(2)), arrow.Arrow(2013, 5, 18, 12, 30, 45)) self.assertEqual(arw.shift(weekday=SU), arw) self.assertEqual(arw.shift(weekday=SU(0)), arw) self.assertEqual(arw.shift(weekday=SU(1)), arw) self.assertEqual(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) self.assertEqual(arw.shift(years=-1), arrow.Arrow(2012, 5, 5, 12, 30, 45)) self.assertEqual(arw.shift(quarters=-1), arrow.Arrow(2013, 2, 5, 12, 30, 45)) self.assertEqual( arw.shift(quarters=-1, months=-1), arrow.Arrow(2013, 1, 5, 12, 30, 45) ) self.assertEqual(arw.shift(months=-1), arrow.Arrow(2013, 4, 5, 12, 30, 45)) self.assertEqual(arw.shift(weeks=-1), arrow.Arrow(2013, 4, 28, 12, 30, 45)) self.assertEqual(arw.shift(days=-1), arrow.Arrow(2013, 5, 4, 12, 30, 45)) self.assertEqual(arw.shift(hours=-1), arrow.Arrow(2013, 5, 5, 11, 30, 45)) self.assertEqual(arw.shift(minutes=-1), arrow.Arrow(2013, 5, 5, 12, 29, 45)) self.assertEqual(arw.shift(seconds=-1), arrow.Arrow(2013, 5, 5, 12, 30, 44)) self.assertEqual( arw.shift(microseconds=-1), arrow.Arrow(2013, 5, 5, 12, 30, 44, 999999) ) # Not sure how practical these negative weekdays are self.assertEqual(arw.shift(weekday=-1), arw.shift(weekday=SU)) self.assertEqual(arw.shift(weekday=-2), arw.shift(weekday=SA)) self.assertEqual(arw.shift(weekday=-3), arw.shift(weekday=FR)) self.assertEqual(arw.shift(weekday=-4), arw.shift(weekday=TH)) self.assertEqual(arw.shift(weekday=-5), arw.shift(weekday=WE)) self.assertEqual(arw.shift(weekday=-6), arw.shift(weekday=TU)) self.assertEqual(arw.shift(weekday=-7), arw.shift(weekday=MO)) with self.assertRaises(IndexError): arw.shift(weekday=-8) self.assertEqual( arw.shift(weekday=MO(-1)), arrow.Arrow(2013, 4, 29, 12, 30, 45) ) self.assertEqual( arw.shift(weekday=TU(-1)), arrow.Arrow(2013, 4, 30, 12, 30, 45) ) self.assertEqual(arw.shift(weekday=WE(-1)), arrow.Arrow(2013, 5, 1, 12, 30, 45)) self.assertEqual(arw.shift(weekday=TH(-1)), arrow.Arrow(2013, 5, 2, 12, 30, 45)) self.assertEqual(arw.shift(weekday=FR(-1)), arrow.Arrow(2013, 5, 3, 12, 30, 45)) self.assertEqual(arw.shift(weekday=SA(-1)), arrow.Arrow(2013, 5, 4, 12, 30, 45)) self.assertEqual(arw.shift(weekday=SU(-1)), arw) self.assertEqual( 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. self.assertEqual( arw.shift(quarters=0, years=1), arrow.Arrow(2014, 5, 5, 12, 30, 45) ) self.assertEqual( arw.shift(quarters=0, months=1), arrow.Arrow(2013, 6, 5, 12, 30, 45) ) self.assertEqual( arw.shift(quarters=0, weeks=1), arrow.Arrow(2013, 5, 12, 12, 30, 45) ) self.assertEqual( arw.shift(quarters=0, days=1), arrow.Arrow(2013, 5, 6, 12, 30, 45) ) self.assertEqual( arw.shift(quarters=0, hours=1), arrow.Arrow(2013, 5, 5, 13, 30, 45) ) self.assertEqual( arw.shift(quarters=0, minutes=1), arrow.Arrow(2013, 5, 5, 12, 31, 45) ) self.assertEqual( arw.shift(quarters=0, seconds=1), arrow.Arrow(2013, 5, 5, 12, 30, 46) ) self.assertEqual( arw.shift(quarters=0, microseconds=1), arrow.Arrow(2013, 5, 5, 12, 30, 45, 1), ) class ArrowRangeTests(Chai): def test_year(self): result = list( arrow.Arrow.range( "year", datetime(2013, 1, 2, 3, 4, 5), datetime(2016, 4, 5, 6, 7, 8) ) ) self.assertEqual( 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) ) ) self.assertEqual( 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) ) ) self.assertEqual( 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) ) ) self.assertEqual( 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) ) ) self.assertEqual( 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) ) ) self.assertEqual( 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) ) ) self.assertEqual(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) ) ) self.assertEqual( 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) ) ) self.assertEqual( 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), ) ) self.assertEqual( 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" ) [self.assertEqual(r.tzinfo, tz.gettz("US/Pacific")) for r in result] 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")), ) [self.assertEqual(r.tzinfo, tz.gettz("US/Pacific")) for r in result] 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")), ) [self.assertEqual(r.tzinfo, tz.gettz("US/Eastern")) for r in result] 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"), ) [self.assertEqual(r.tzinfo, tz.gettz("US/Central")) for r in result] def test_unsupported(self): with self.assertRaises(AttributeError): next(arrow.Arrow.range("abc", datetime.utcnow(), datetime.utcnow())) class ArrowSpanRangeTests(Chai): def test_year(self): result = list( arrow.Arrow.span_range("year", datetime(2013, 2, 1), datetime(2016, 3, 31)) ) self.assertEqual( 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) ) ) self.assertEqual( 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)) ) self.assertEqual( 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)) ) self.assertEqual( 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) ) ) self.assertEqual( 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) ) ) self.assertEqual( 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) ) ) self.assertEqual( 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) ) ) self.assertEqual( 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) ) ) self.assertEqual( 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) ) ) self.assertEqual( 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: self.assertEqual(f.tzinfo, tzinfo) self.assertEqual(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: self.assertEqual(f.tzinfo, tzinfo) self.assertEqual(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: self.assertEqual(f.tzinfo, tzinfo1) self.assertEqual(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: self.assertEqual(f.tzinfo, tz.gettz("US/Central")) self.assertEqual(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="[]" ) ) self.assertEqual( result, [ (arrow.Arrow(2013, 1, 1), arrow.Arrow(2013, 4, 1)), (arrow.Arrow(2013, 4, 1), arrow.Arrow(2013, 7, 1)), ], ) class ArrowIntervalTests(Chai): def test_incorrect_input(self): correct = True try: list( arrow.Arrow.interval( "month", datetime(2013, 1, 2), datetime(2013, 4, 15), 0 ) ) except: # noqa: E722 correct = False self.assertEqual(correct, False) def test_correct(self): result = list( arrow.Arrow.interval( "hour", datetime(2013, 5, 5, 12, 30), datetime(2013, 5, 5, 17, 15), 2 ) ) self.assertEqual( 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="[]", ) ) self.assertEqual( 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)), ], ) class ArrowSpanTests(Chai): def setUp(self): super(ArrowSpanTests, self).setUp() self.datetime = datetime(2013, 2, 15, 3, 41, 22, 8923) self.arrow = arrow.Arrow.fromdatetime(self.datetime) def test_span_attribute(self): with self.assertRaises(AttributeError): self.arrow.span("span") def test_span_year(self): floor, ceil = self.arrow.span("year") self.assertEqual(floor, datetime(2013, 1, 1, tzinfo=tz.tzutc())) self.assertEqual( ceil, datetime(2013, 12, 31, 23, 59, 59, 999999, tzinfo=tz.tzutc()) ) def test_span_quarter(self): floor, ceil = self.arrow.span("quarter") self.assertEqual(floor, datetime(2013, 1, 1, tzinfo=tz.tzutc())) self.assertEqual( 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) self.assertEqual(floor, datetime(2013, 1, 1, tzinfo=tz.tzutc())) self.assertEqual( 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) self.assertEqual(floor, datetime(2013, 1, 1, tzinfo=tz.tzutc())) self.assertEqual( ceil, datetime(2014, 12, 31, 23, 59, 59, 999999, tzinfo=tz.tzutc()) ) def test_span_month(self): floor, ceil = self.arrow.span("month") self.assertEqual(floor, datetime(2013, 2, 1, tzinfo=tz.tzutc())) self.assertEqual( ceil, datetime(2013, 2, 28, 23, 59, 59, 999999, tzinfo=tz.tzutc()) ) def test_span_week(self): floor, ceil = self.arrow.span("week") self.assertEqual(floor, datetime(2013, 2, 11, tzinfo=tz.tzutc())) self.assertEqual( ceil, datetime(2013, 2, 17, 23, 59, 59, 999999, tzinfo=tz.tzutc()) ) def test_span_day(self): floor, ceil = self.arrow.span("day") self.assertEqual(floor, datetime(2013, 2, 15, tzinfo=tz.tzutc())) self.assertEqual( ceil, datetime(2013, 2, 15, 23, 59, 59, 999999, tzinfo=tz.tzutc()) ) def test_span_hour(self): floor, ceil = self.arrow.span("hour") self.assertEqual(floor, datetime(2013, 2, 15, 3, tzinfo=tz.tzutc())) self.assertEqual( ceil, datetime(2013, 2, 15, 3, 59, 59, 999999, tzinfo=tz.tzutc()) ) def test_span_minute(self): floor, ceil = self.arrow.span("minute") self.assertEqual(floor, datetime(2013, 2, 15, 3, 41, tzinfo=tz.tzutc())) self.assertEqual( ceil, datetime(2013, 2, 15, 3, 41, 59, 999999, tzinfo=tz.tzutc()) ) def test_span_second(self): floor, ceil = self.arrow.span("second") self.assertEqual(floor, datetime(2013, 2, 15, 3, 41, 22, tzinfo=tz.tzutc())) self.assertEqual( ceil, datetime(2013, 2, 15, 3, 41, 22, 999999, tzinfo=tz.tzutc()) ) def test_span_microsecond(self): floor, ceil = self.arrow.span("microsecond") self.assertEqual( floor, datetime(2013, 2, 15, 3, 41, 22, 8923, tzinfo=tz.tzutc()) ) self.assertEqual( ceil, datetime(2013, 2, 15, 3, 41, 22, 8923, tzinfo=tz.tzutc()) ) def test_floor(self): floor, ceil = self.arrow.span("month") self.assertEqual(floor, self.arrow.floor("month")) self.assertEqual(ceil, self.arrow.ceil("month")) def test_span_inclusive_inclusive(self): floor, ceil = self.arrow.span("hour", bounds="[]") self.assertEqual(floor, datetime(2013, 2, 15, 3, tzinfo=tz.tzutc())) self.assertEqual(ceil, datetime(2013, 2, 15, 4, tzinfo=tz.tzutc())) def test_span_exclusive_inclusive(self): floor, ceil = self.arrow.span("hour", bounds="(]") self.assertEqual(floor, datetime(2013, 2, 15, 3, 0, 0, 1, tzinfo=tz.tzutc())) self.assertEqual(ceil, datetime(2013, 2, 15, 4, tzinfo=tz.tzutc())) def test_span_exclusive_exclusive(self): floor, ceil = self.arrow.span("hour", bounds="()") self.assertEqual(floor, datetime(2013, 2, 15, 3, 0, 0, 1, tzinfo=tz.tzutc())) self.assertEqual( ceil, datetime(2013, 2, 15, 3, 59, 59, 999999, tzinfo=tz.tzutc()) ) def test_bounds_are_validated(self): with self.assertRaises(AttributeError): floor, ceil = self.arrow.span("hour", bounds="][") class ArrowHumanizeTests(Chai): def setUp(self): super(ArrowHumanizeTests, self).setUp() self.datetime = datetime(2013, 1, 1) self.now = arrow.Arrow.utcnow() def test_granularity(self): self.assertEqual(self.now.humanize(granularity="second"), "just now") later1 = self.now.shift(seconds=1) self.assertEqual(self.now.humanize(later1, granularity="second"), "just now") self.assertEqual(later1.humanize(self.now, granularity="second"), "just now") self.assertEqual( self.now.humanize(later1, granularity="minute"), "0 minutes ago" ) self.assertEqual( later1.humanize(self.now, granularity="minute"), "in 0 minutes" ) later100 = self.now.shift(seconds=100) self.assertEqual( self.now.humanize(later100, granularity="second"), "100 seconds ago" ) self.assertEqual( later100.humanize(self.now, granularity="second"), "in 100 seconds" ) self.assertEqual( self.now.humanize(later100, granularity="minute"), "a minute ago" ) self.assertEqual( later100.humanize(self.now, granularity="minute"), "in a minute" ) self.assertEqual(self.now.humanize(later100, granularity="hour"), "0 hours ago") self.assertEqual(later100.humanize(self.now, granularity="hour"), "in 0 hours") later4000 = self.now.shift(seconds=4000) self.assertEqual( self.now.humanize(later4000, granularity="minute"), "66 minutes ago" ) self.assertEqual( later4000.humanize(self.now, granularity="minute"), "in 66 minutes" ) self.assertEqual( self.now.humanize(later4000, granularity="hour"), "an hour ago" ) self.assertEqual(later4000.humanize(self.now, granularity="hour"), "in an hour") self.assertEqual(self.now.humanize(later4000, granularity="day"), "0 days ago") self.assertEqual(later4000.humanize(self.now, granularity="day"), "in 0 days") later105 = self.now.shift(seconds=10 ** 5) self.assertEqual( self.now.humanize(later105, granularity="hour"), "27 hours ago" ) self.assertEqual(later105.humanize(self.now, granularity="hour"), "in 27 hours") self.assertEqual(self.now.humanize(later105, granularity="day"), "a day ago") self.assertEqual(later105.humanize(self.now, granularity="day"), "in a day") self.assertEqual(self.now.humanize(later105, granularity="week"), "0 weeks ago") self.assertEqual(later105.humanize(self.now, granularity="week"), "in 0 weeks") self.assertEqual( self.now.humanize(later105, granularity="month"), "0 months ago" ) self.assertEqual( later105.humanize(self.now, granularity="month"), "in 0 months" ) self.assertEqual( self.now.humanize(later105, granularity=["month"]), "0 months ago" ) self.assertEqual( later105.humanize(self.now, granularity=["month"]), "in 0 months" ) later106 = self.now.shift(seconds=3 * 10 ** 6) self.assertEqual(self.now.humanize(later106, granularity="day"), "34 days ago") self.assertEqual(later106.humanize(self.now, granularity="day"), "in 34 days") self.assertEqual(self.now.humanize(later106, granularity="week"), "4 weeks ago") self.assertEqual(later106.humanize(self.now, granularity="week"), "in 4 weeks") self.assertEqual( self.now.humanize(later106, granularity="month"), "a month ago" ) self.assertEqual(later106.humanize(self.now, granularity="month"), "in a month") self.assertEqual(self.now.humanize(later106, granularity="year"), "0 years ago") self.assertEqual(later106.humanize(self.now, granularity="year"), "in 0 years") later506 = self.now.shift(seconds=50 * 10 ** 6) self.assertEqual( self.now.humanize(later506, granularity="week"), "82 weeks ago" ) self.assertEqual(later506.humanize(self.now, granularity="week"), "in 82 weeks") self.assertEqual( self.now.humanize(later506, granularity="month"), "18 months ago" ) self.assertEqual( later506.humanize(self.now, granularity="month"), "in 18 months" ) self.assertEqual(self.now.humanize(later506, granularity="year"), "a year ago") self.assertEqual(later506.humanize(self.now, granularity="year"), "in a year") later108 = self.now.shift(seconds=10 ** 8) self.assertEqual(self.now.humanize(later108, granularity="year"), "3 years ago") self.assertEqual(later108.humanize(self.now, granularity="year"), "in 3 years") later108onlydistance = self.now.shift(seconds=10 ** 8) self.assertEqual( self.now.humanize( later108onlydistance, only_distance=True, granularity="year" ), "3 years", ) self.assertEqual( later108onlydistance.humanize( self.now, only_distance=True, granularity="year" ), "3 years", ) with self.assertRaises(AttributeError): self.now.humanize(later108, granularity="years") def test_multiple_granularity(self): self.assertEqual(self.now.humanize(granularity="second"), "just now") self.assertEqual(self.now.humanize(granularity=["second"]), "just now") self.assertEqual( 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) self.assertEqual( later4000.humanize(self.now, granularity=["hour", "minute"]), "in an hour and 6 minutes", ) self.assertEqual( self.now.humanize(later4000, granularity=["hour", "minute"]), "an hour and 6 minutes ago", ) self.assertEqual( later4000.humanize( self.now, granularity=["hour", "minute"], only_distance=True ), "an hour and 6 minutes", ) self.assertEqual( later4000.humanize(self.now, granularity=["day", "hour", "minute"]), "in 0 days an hour and 6 minutes", ) self.assertEqual( self.now.humanize(later4000, granularity=["day", "hour", "minute"]), "0 days an hour and 6 minutes ago", ) later105 = self.now.shift(seconds=10 ** 5) self.assertEqual( self.now.humanize(later105, granularity=["hour", "day", "minute"]), "a day 3 hours and 46 minutes ago", ) with self.assertRaises(AttributeError): self.now.humanize(later105, granularity=["error", "second"]) later108onlydistance = self.now.shift(seconds=10 ** 8) self.assertEqual( self.now.humanize( later108onlydistance, only_distance=True, granularity=["year"] ), "3 years", ) self.assertEqual( self.now.humanize( later108onlydistance, only_distance=True, granularity=["month", "week"] ), "37 months and 4 weeks", ) self.assertEqual( self.now.humanize( later108onlydistance, only_distance=True, granularity=["year", "second"] ), "3 years and 5327200 seconds", ) one_min_one_sec_ago = self.now.shift(minutes=-1, seconds=-1) self.assertEqual( 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) self.assertEqual( 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 self.assertEqual(self.now.humanize(later), "10 seconds ago") self.assertEqual(later.humanize(self.now), "in 10 seconds") self.assertEqual(self.now.humanize(later, only_distance=True), "10 seconds") self.assertEqual(later.humanize(self.now, only_distance=True), "10 seconds") def test_minute(self): later = self.now.shift(minutes=1) self.assertEqual(self.now.humanize(later), "a minute ago") self.assertEqual(later.humanize(self.now), "in a minute") self.assertEqual(self.now.humanize(later, only_distance=True), "a minute") self.assertEqual(later.humanize(self.now, only_distance=True), "a minute") def test_minutes(self): later = self.now.shift(minutes=2) self.assertEqual(self.now.humanize(later), "2 minutes ago") self.assertEqual(later.humanize(self.now), "in 2 minutes") self.assertEqual(self.now.humanize(later, only_distance=True), "2 minutes") self.assertEqual(later.humanize(self.now, only_distance=True), "2 minutes") def test_hour(self): later = self.now.shift(hours=1) self.assertEqual(self.now.humanize(later), "an hour ago") self.assertEqual(later.humanize(self.now), "in an hour") self.assertEqual(self.now.humanize(later, only_distance=True), "an hour") self.assertEqual(later.humanize(self.now, only_distance=True), "an hour") def test_hours(self): later = self.now.shift(hours=2) self.assertEqual(self.now.humanize(later), "2 hours ago") self.assertEqual(later.humanize(self.now), "in 2 hours") self.assertEqual(self.now.humanize(later, only_distance=True), "2 hours") self.assertEqual(later.humanize(self.now, only_distance=True), "2 hours") def test_day(self): later = self.now.shift(days=1) self.assertEqual(self.now.humanize(later), "a day ago") self.assertEqual(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 ) self.assertEqual(self.now.humanize(less_than_48_hours), "a day ago") self.assertEqual(less_than_48_hours.humanize(self.now), "in a day") less_than_48_hours_date = less_than_48_hours._datetime.date() with self.assertRaises(TypeError): # humanize other argument does not take raw datetime.date objects self.now.humanize(less_than_48_hours_date) # convert from date to arrow object less_than_48_hours_date = arrow.Arrow.fromdate(less_than_48_hours_date) self.assertEqual(self.now.humanize(less_than_48_hours_date), "a day ago") self.assertEqual(less_than_48_hours_date.humanize(self.now), "in a day") self.assertEqual(self.now.humanize(later, only_distance=True), "a day") self.assertEqual(later.humanize(self.now, only_distance=True), "a day") def test_days(self): later = self.now.shift(days=2) self.assertEqual(self.now.humanize(later), "2 days ago") self.assertEqual(later.humanize(self.now), "in 2 days") self.assertEqual(self.now.humanize(later, only_distance=True), "2 days") self.assertEqual(later.humanize(self.now, only_distance=True), "2 days") # Regression tests for humanize bug referenced in issue 541 later = self.now.shift(days=3) self.assertEqual(later.humanize(), "in 3 days") later = self.now.shift(days=3, seconds=1) self.assertEqual(later.humanize(), "in 3 days") later = self.now.shift(days=4) self.assertEqual(later.humanize(), "in 4 days") def test_week(self): later = self.now.shift(weeks=1) self.assertEqual(self.now.humanize(later), "a week ago") self.assertEqual(later.humanize(self.now), "in a week") self.assertEqual(self.now.humanize(later, only_distance=True), "a week") self.assertEqual(later.humanize(self.now, only_distance=True), "a week") def test_weeks(self): later = self.now.shift(weeks=2) self.assertEqual(self.now.humanize(later), "2 weeks ago") self.assertEqual(later.humanize(self.now), "in 2 weeks") self.assertEqual(self.now.humanize(later, only_distance=True), "2 weeks") self.assertEqual(later.humanize(self.now, only_distance=True), "2 weeks") def test_month(self): later = self.now.shift(months=1) self.assertEqual(self.now.humanize(later), "a month ago") self.assertEqual(later.humanize(self.now), "in a month") self.assertEqual(self.now.humanize(later, only_distance=True), "a month") self.assertEqual(later.humanize(self.now, only_distance=True), "a month") def test_months(self): later = self.now.shift(months=2) earlier = self.now.shift(months=-2) self.assertEqual(earlier.humanize(self.now), "2 months ago") self.assertEqual(later.humanize(self.now), "in 2 months") self.assertEqual(self.now.humanize(later, only_distance=True), "2 months") self.assertEqual(later.humanize(self.now, only_distance=True), "2 months") def test_year(self): later = self.now.shift(years=1) self.assertEqual(self.now.humanize(later), "a year ago") self.assertEqual(later.humanize(self.now), "in a year") self.assertEqual(self.now.humanize(later, only_distance=True), "a year") self.assertEqual(later.humanize(self.now, only_distance=True), "a year") def test_years(self): later = self.now.shift(years=2) self.assertEqual(self.now.humanize(later), "2 years ago") self.assertEqual(later.humanize(self.now), "in 2 years") self.assertEqual(self.now.humanize(later, only_distance=True), "2 years") self.assertEqual(later.humanize(self.now, only_distance=True), "2 years") arw = arrow.Arrow(2014, 7, 2) result = arw.humanize(self.datetime) self.assertEqual(result, "in 2 years") def test_arrow(self): arw = arrow.Arrow.fromdatetime(self.datetime) result = arw.humanize(arrow.Arrow.fromdatetime(self.datetime)) self.assertEqual(result, "just now") def test_datetime_tzinfo(self): arw = arrow.Arrow.fromdatetime(self.datetime) result = arw.humanize(self.datetime.replace(tzinfo=tz.tzutc())) self.assertEqual(result, "just now") def test_other(self): arw = arrow.Arrow.fromdatetime(self.datetime) with self.assertRaises(TypeError): arw.humanize(object()) def test_invalid_locale(self): arw = arrow.Arrow.fromdatetime(self.datetime) with self.assertRaises(ValueError): arw.humanize(locale="klingon") def test_none(self): arw = arrow.Arrow.utcnow() result = arw.humanize() self.assertEqual(result, "just now") result = arw.humanize(None) self.assertEqual(result, "just now") def test_untranslated_granularity(self): arw = arrow.Arrow.utcnow() later = arw.shift(weeks=1) # simulate an untranslated timeframe key with patch.dict("arrow.locales.EnglishLocale.timeframes"): del arrow.locales.EnglishLocale.timeframes["week"] with self.assertRaises(ValueError): arw.humanize(later, granularity="week") class ArrowHumanizeTestsWithLocale(Chai): def setUp(self): super(ArrowHumanizeTestsWithLocale, self).setUp() self.datetime = datetime(2013, 1, 1) def test_now(self): arw = arrow.Arrow(2013, 1, 1, 0, 0, 0) result = arw.humanize(self.datetime, locale="ru") self.assertEqual(result, "сейчас") def test_seconds(self): arw = arrow.Arrow(2013, 1, 1, 0, 0, 44) result = arw.humanize(self.datetime, locale="ru") self.assertEqual(result, "через 44 несколько секунд") def test_years(self): arw = arrow.Arrow(2011, 7, 2) result = arw.humanize(self.datetime, locale="ru") self.assertEqual(result, "2 года назад") class ArrowIsBetweenTests(Chai): 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)) result = target.is_between(start, end) self.assertFalse(result) 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)) result = target.is_between(start, end, "()") self.assertTrue(result) result = target.is_between(start, end) self.assertTrue(result) 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)) result = target.is_between(start, end, "()") self.assertFalse(result) 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)) result = target.is_between(start, end, "[)") self.assertFalse(result) 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)) result = target.is_between(start, end, "(]") self.assertTrue(result) 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)) result = target.is_between(start, end, "[]") self.assertTrue(result) def test_type_error_exception(self): with self.assertRaises(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 self.assertRaises(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 self.assertRaises(TypeError): target.is_between(None, None) def test_attribute_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 self.assertRaises(AttributeError): target.is_between(start, end, "][") with self.assertRaises(AttributeError): target.is_between(start, end, "") with self.assertRaises(AttributeError): target.is_between(start, end, "]") with self.assertRaises(AttributeError): target.is_between(start, end, "[") with self.assertRaises(AttributeError): target.is_between(start, end, "hello") class ArrowUtilTests(Chai): def test_get_datetime(self): get_datetime = arrow.Arrow._get_datetime arw = arrow.Arrow.utcnow() dt = datetime.utcnow() timestamp = time.time() self.assertEqual(get_datetime(arw), arw.datetime) self.assertEqual(get_datetime(dt), dt) self.assertEqual( get_datetime(timestamp), arrow.Arrow.utcfromtimestamp(timestamp).datetime ) with self.assertRaises(ValueError) as raise_ctx: get_datetime("abc") self.assertFalse("{}" in str(raise_ctx.exception)) def test_get_tzinfo(self): get_tzinfo = arrow.Arrow._get_tzinfo with self.assertRaises(ValueError) as raise_ctx: get_tzinfo("abc") self.assertFalse("{}" in str(raise_ctx.exception)) def test_get_iteration_params(self): self.assertEqual( arrow.Arrow._get_iteration_params("end", None), ("end", sys.maxsize) ) self.assertEqual( arrow.Arrow._get_iteration_params(None, 100), (arrow.Arrow.max, 100) ) self.assertEqual(arrow.Arrow._get_iteration_params(100, 120), (100, 120)) with self.assertRaises(ValueError): arrow.Arrow._get_iteration_params(None, None) arrow-0.15.5/tests/factory_tests.py0000664000175000017500000002602113577744544017632 0ustar chrischris00000000000000# -*- coding: utf-8 -*- import time from datetime import date, datetime import dateparser from chai import Chai from dateutil import tz from arrow import factory, util from arrow.parser import ParserError def assertDtEqual(dt1, dt2, within=10): assertEqual(dt1.tzinfo, dt2.tzinfo) # noqa: F821 assertTrue(abs(util.total_seconds(dt1 - dt2)) < within) # noqa: F821 class GetTests(Chai): def setUp(self): super(GetTests, self).setUp() self.factory = factory.ArrowFactory() def test_no_args(self): assertDtEqual(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 self.assertEqual(no_arg, one_arg) def test_one_arg_none(self): assertDtEqual( self.factory.get(None), datetime.utcnow().replace(tzinfo=tz.tzutc()) ) def test_struct_time(self): assertDtEqual( 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() ) self.assertEqual(self.factory.get(int_timestamp), timestamp_dt) with self.assertRaises(ParserError): self.factory.get(str(int_timestamp)) float_timestamp = time.time() timestamp_dt = datetime.utcfromtimestamp(float_timestamp).replace( tzinfo=tz.tzutc() ) self.assertEqual(self.factory.get(float_timestamp), timestamp_dt) with self.assertRaises(ParserError): self.factory.get(str(float_timestamp)) # Regression test for issue #216 # Python 3 raises OverflowError, Python 2 raises ValueError timestamp = 99999999999999999999999999.99999999999999999999999999 with self.assertRaises((OverflowError, ValueError)): self.factory.get(timestamp) 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") assertDtEqual(self.factory.get(timestamp, tzinfo=timezone), timestamp_dt) def test_one_arg_arrow(self): arw = self.factory.utcnow() result = self.factory.get(arw) self.assertEqual(arw, result) def test_one_arg_datetime(self): dt = datetime.utcnow().replace(tzinfo=tz.tzutc()) self.assertEqual(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()) self.assertEqual(self.factory.get(d), dt) def test_one_arg_tzinfo(self): self.expected = ( datetime.utcnow() .replace(tzinfo=tz.tzutc()) .astimezone(tz.gettz("US/Pacific")) ) assertDtEqual(self.factory.get(tz.gettz("US/Pacific")), self.expected) # regression test for issue #658 def test_one_arg_dateparser_datetime(self): 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()) self.assertEqual(dt_output, expected) def test_kwarg_tzinfo(self): self.expected = ( datetime.utcnow() .replace(tzinfo=tz.tzutc()) .astimezone(tz.gettz("US/Pacific")) ) assertDtEqual(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")) ) assertDtEqual(self.factory.get(tzinfo="US/Pacific"), self.expected) with self.assertRaises(ParserError): self.factory.get(tzinfo="US/PacificInvalidTzinfo") def test_one_arg_iso_str(self): dt = datetime.utcnow() assertDtEqual(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 self.assertEqual(self.factory.get(iso), self.factory.get(dt)) with self.assertRaises(TypeError): self.factory.get((2014, 7, 1, 4)) with self.assertRaises(TypeError): self.factory.get((2014, 7)) with self.assertRaises(ValueError): self.factory.get((2014, 70, 1)) with self.assertRaises(ValueError): self.factory.get((2014, 7, 10)) def test_one_arg_other(self): with self.assertRaises(TypeError): self.factory.get(object()) def test_one_arg_bool(self): with self.assertRaises(TypeError): self.factory.get(False) with self.assertRaises(TypeError): self.factory.get(True) def test_two_args_datetime_tzinfo(self): result = self.factory.get(datetime(2013, 1, 1), tz.gettz("US/Pacific")) self.assertEqual( 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") self.assertEqual( 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")) self.assertEqual( 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") self.assertEqual( result._datetime, datetime(2013, 1, 1, tzinfo=tz.gettz("US/Pacific")) ) def test_two_args_datetime_other(self): with self.assertRaises(TypeError): self.factory.get(datetime.utcnow(), object()) def test_two_args_date_other(self): with self.assertRaises(TypeError): self.factory.get(date.today(), object()) def test_two_args_str_str(self): result = self.factory.get("2013-01-01", "YYYY-MM-DD") self.assertEqual(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")) assertDtEqual( 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") self.assertEqual( 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"]) self.assertEqual(result._datetime, datetime(2013, 1, 1, tzinfo=tz.tzutc())) def test_two_args_unicode_unicode(self): result = self.factory.get(u"2013-01-01", u"YYYY-MM-DD") self.assertEqual(result._datetime, datetime(2013, 1, 1, tzinfo=tz.tzutc())) def test_two_args_other(self): with self.assertRaises(TypeError): self.factory.get(object(), object()) def test_three_args_with_tzinfo(self): timefmt = "YYYYMMDD" d = "20150514" self.assertEqual( self.factory.get(d, timefmt, tzinfo=tz.tzlocal()), datetime(2015, 5, 14, tzinfo=tz.tzlocal()), ) def test_three_args(self): self.assertEqual( self.factory.get(2013, 1, 1), datetime(2013, 1, 1, tzinfo=tz.tzutc()) ) def test_full_kwargs(self): self.assertEqual( 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): self.assertEqual( 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") self.assertEqual( result._datetime, datetime(2019, 7, 28, 7, 0, 0, 0, tzinfo=tz.tzutc()) ) def test_insufficient_kwargs(self): with self.assertRaises(TypeError): self.factory.get(year=2016) with self.assertRaises(TypeError): self.factory.get(year=2016, month=7) def test_locale(self): result = self.factory.get("2010", "YYYY", locale="ja") self.assertEqual( 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" ) self.assertEqual( 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") self.assertEqual(res.tzinfo, tz.tzutc()) def test_locale_with_tzinfo(self): res = self.factory.get(locale="ja", tzinfo=tz.gettz("Asia/Tokyo")) self.assertEqual(res.tzinfo, tz.gettz("Asia/Tokyo")) class UtcNowTests(Chai): def setUp(self): super(UtcNowTests, self).setUp() self.factory = factory.ArrowFactory() def test_utcnow(self): assertDtEqual( self.factory.utcnow()._datetime, datetime.utcnow().replace(tzinfo=tz.tzutc()), ) class NowTests(Chai): def setUp(self): super(NowTests, self).setUp() self.factory = factory.ArrowFactory() def test_no_tz(self): assertDtEqual(self.factory.now(), datetime.now(tz.tzlocal())) def test_tzinfo(self): assertDtEqual(self.factory.now(tz.gettz("EST")), datetime.now(tz.gettz("EST"))) def test_tz_str(self): assertDtEqual(self.factory.now("EST"), datetime.now(tz.gettz("EST"))) arrow-0.15.5/tests/formatter_tests.py0000664000175000017500000001647313577744544020200 0ustar chrischris00000000000000# -*- coding: utf-8 -*- import time from datetime import datetime import pytz from chai import Chai from dateutil import tz as dateutil_tz from arrow import formatter from .utils import make_full_tz_list class DateTimeFormatterFormatTokenTests(Chai): def setUp(self): super(DateTimeFormatterFormatTokenTests, self).setUp() self.formatter = formatter.DateTimeFormatter() def test_format(self): dt = datetime(2013, 2, 5, 12, 32, 51) result = self.formatter.format(dt, "MM-DD-YYYY hh:mm:ss a") self.assertEqual(result, "02-05-2013 12:32:51 pm") def test_year(self): dt = datetime(2013, 1, 1) self.assertEqual(self.formatter._format_token(dt, "YYYY"), "2013") self.assertEqual(self.formatter._format_token(dt, "YY"), "13") def test_month(self): dt = datetime(2013, 1, 1) self.assertEqual(self.formatter._format_token(dt, "MMMM"), "January") self.assertEqual(self.formatter._format_token(dt, "MMM"), "Jan") self.assertEqual(self.formatter._format_token(dt, "MM"), "01") self.assertEqual(self.formatter._format_token(dt, "M"), "1") def test_day(self): dt = datetime(2013, 2, 1) self.assertEqual(self.formatter._format_token(dt, "DDDD"), "032") self.assertEqual(self.formatter._format_token(dt, "DDD"), "32") self.assertEqual(self.formatter._format_token(dt, "DD"), "01") self.assertEqual(self.formatter._format_token(dt, "D"), "1") self.assertEqual(self.formatter._format_token(dt, "Do"), "1st") self.assertEqual(self.formatter._format_token(dt, "dddd"), "Friday") self.assertEqual(self.formatter._format_token(dt, "ddd"), "Fri") self.assertEqual(self.formatter._format_token(dt, "d"), "5") def test_hour(self): dt = datetime(2013, 1, 1, 2) self.assertEqual(self.formatter._format_token(dt, "HH"), "02") self.assertEqual(self.formatter._format_token(dt, "H"), "2") dt = datetime(2013, 1, 1, 13) self.assertEqual(self.formatter._format_token(dt, "HH"), "13") self.assertEqual(self.formatter._format_token(dt, "H"), "13") dt = datetime(2013, 1, 1, 2) self.assertEqual(self.formatter._format_token(dt, "hh"), "02") self.assertEqual(self.formatter._format_token(dt, "h"), "2") dt = datetime(2013, 1, 1, 13) self.assertEqual(self.formatter._format_token(dt, "hh"), "01") self.assertEqual(self.formatter._format_token(dt, "h"), "1") # test that 12-hour time converts to '12' at midnight dt = datetime(2013, 1, 1, 0) self.assertEqual(self.formatter._format_token(dt, "hh"), "12") self.assertEqual(self.formatter._format_token(dt, "h"), "12") def test_minute(self): dt = datetime(2013, 1, 1, 0, 1) self.assertEqual(self.formatter._format_token(dt, "mm"), "01") self.assertEqual(self.formatter._format_token(dt, "m"), "1") def test_second(self): dt = datetime(2013, 1, 1, 0, 0, 1) self.assertEqual(self.formatter._format_token(dt, "ss"), "01") self.assertEqual(self.formatter._format_token(dt, "s"), "1") def test_sub_second(self): dt = datetime(2013, 1, 1, 0, 0, 0, 123456) self.assertEqual(self.formatter._format_token(dt, "SSSSSS"), "123456") self.assertEqual(self.formatter._format_token(dt, "SSSSS"), "12345") self.assertEqual(self.formatter._format_token(dt, "SSSS"), "1234") self.assertEqual(self.formatter._format_token(dt, "SSS"), "123") self.assertEqual(self.formatter._format_token(dt, "SS"), "12") self.assertEqual(self.formatter._format_token(dt, "S"), "1") dt = datetime(2013, 1, 1, 0, 0, 0, 2000) self.assertEqual(self.formatter._format_token(dt, "SSSSSS"), "002000") self.assertEqual(self.formatter._format_token(dt, "SSSSS"), "00200") self.assertEqual(self.formatter._format_token(dt, "SSSS"), "0020") self.assertEqual(self.formatter._format_token(dt, "SSS"), "002") self.assertEqual(self.formatter._format_token(dt, "SS"), "00") self.assertEqual(self.formatter._format_token(dt, "S"), "0") def test_timestamp(self): timestamp = time.time() dt = datetime.utcfromtimestamp(timestamp) self.assertEqual(self.formatter._format_token(dt, "X"), str(int(timestamp))) def test_timezone(self): dt = datetime.utcnow().replace(tzinfo=dateutil_tz.gettz("US/Pacific")) result = self.formatter._format_token(dt, "ZZ") self.assertTrue(result == "-07:00" or result == "-08:00") result = self.formatter._format_token(dt, "Z") self.assertTrue(result == "-0700" or result == "-0800") def test_timezone_formatter(self): for full_name in make_full_tz_list(): # 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_name) ) abbreviation = dt.tzname() result = self.formatter._format_token(dt, "ZZZ") self.assertEqual(result, abbreviation) def test_am_pm(self): dt = datetime(2012, 1, 1, 11) self.assertEqual(self.formatter._format_token(dt, "a"), "am") self.assertEqual(self.formatter._format_token(dt, "A"), "AM") dt = datetime(2012, 1, 1, 13) self.assertEqual(self.formatter._format_token(dt, "a"), "pm") self.assertEqual(self.formatter._format_token(dt, "A"), "PM") def test_nonsense(self): dt = datetime(2012, 1, 1, 11) self.assertEqual(self.formatter._format_token(dt, None), None) self.assertEqual(self.formatter._format_token(dt, "NONSENSE"), None) def test_escape(self): self.assertEqual( self.formatter.format( datetime(2015, 12, 10, 17, 9), "MMMM D, YYYY [at] h:mma" ), "December 10, 2015 at 5:09pm", ) self.assertEqual( self.formatter.format( datetime(2015, 12, 10, 17, 9), "[MMMM] M D, YYYY [at] h:mma" ), "MMMM 12 10, 2015 at 5:09pm", ) self.assertEqual( 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", ) self.assertEqual( 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", ) self.assertEqual( self.formatter.format( datetime(1, 1, 1), "[I'm][ entirely][ escaped,][ weee!]" ), "I'm entirely escaped, weee!", ) # Special RegEx characters self.assertEqual( 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 litterally self.assertEqual(self.formatter.format(datetime(1, 1, 1), "[[[ ]]"), "[[ ]") arrow-0.15.5/tests/locales_tests.py0000664000175000017500000012437413603664774017613 0ustar chrischris00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals from chai import Chai from arrow import arrow, locales class LocaleValidationTests(Chai): """Validate locales to ensure that translations are valid and complete""" def setUp(self): super(LocaleValidationTests, self).setUp() self.locales = locales._locales def test_locale_validation(self): for _, locale_cls in self.locales.items(): # 7 days + 1 spacer to allow for 1-indexing of months self.assertEqual(len(locale_cls.day_names), 8) self.assertTrue(locale_cls.day_names[0] == "") # ensure that all string from index 1 onward are valid (not blank or None) self.assertTrue(all(locale_cls.day_names[1:])) self.assertEqual(len(locale_cls.day_abbreviations), 8) self.assertTrue(locale_cls.day_abbreviations[0] == "") self.assertTrue(all(locale_cls.day_abbreviations[1:])) # 12 months + 1 spacer to allow for 1-indexing of months self.assertEqual(len(locale_cls.month_names), 13) self.assertTrue(locale_cls.month_names[0] == "") self.assertTrue(all(locale_cls.month_names[1:])) self.assertEqual(len(locale_cls.month_abbreviations), 13) self.assertTrue(locale_cls.month_abbreviations[0] == "") self.assertTrue(all(locale_cls.month_abbreviations[1:])) self.assertTrue(len(locale_cls.names) > 0) self.assertTrue(locale_cls.past is not None) self.assertTrue(locale_cls.future is not None) class ModuleTests(Chai): def test_get_locale(self): mock_locales = self.mock(locales, "_locales") mock_locale_cls = self.mock() mock_locale = self.mock() self.expect(mock_locales.get).args("name").returns(mock_locale_cls) self.expect(mock_locale_cls).returns(mock_locale) result = locales.get_locale("name") self.assertEqual(result, mock_locale) def test_locales(self): self.assertTrue(len(locales._locales) > 0) class LocaleTests(Chai): def setUp(self): super(LocaleTests, self).setUp() self.locale = locales.EnglishLocale() def test_format_timeframe(self): self.assertEqual(self.locale._format_timeframe("hours", 2), "2 hours") self.assertEqual(self.locale._format_timeframe("hour", 0), "an hour") def test_format_relative_now(self): result = self.locale._format_relative("just now", "now", 0) self.assertEqual(result, "just now") def test_format_relative_past(self): result = self.locale._format_relative("an hour", "hour", 1) self.assertEqual(result, "in an hour") def test_format_relative_future(self): result = self.locale._format_relative("an hour", "hour", -1) self.assertEqual(result, "an hour ago") def test_ordinal_number(self): self.assertEqual(self.locale.ordinal_number(0), "0th") self.assertEqual(self.locale.ordinal_number(1), "1st") self.assertEqual(self.locale.ordinal_number(2), "2nd") self.assertEqual(self.locale.ordinal_number(3), "3rd") self.assertEqual(self.locale.ordinal_number(4), "4th") self.assertEqual(self.locale.ordinal_number(10), "10th") self.assertEqual(self.locale.ordinal_number(11), "11th") self.assertEqual(self.locale.ordinal_number(12), "12th") self.assertEqual(self.locale.ordinal_number(13), "13th") self.assertEqual(self.locale.ordinal_number(14), "14th") self.assertEqual(self.locale.ordinal_number(21), "21st") self.assertEqual(self.locale.ordinal_number(22), "22nd") self.assertEqual(self.locale.ordinal_number(23), "23rd") self.assertEqual(self.locale.ordinal_number(24), "24th") self.assertEqual(self.locale.ordinal_number(100), "100th") self.assertEqual(self.locale.ordinal_number(101), "101st") self.assertEqual(self.locale.ordinal_number(102), "102nd") self.assertEqual(self.locale.ordinal_number(103), "103rd") self.assertEqual(self.locale.ordinal_number(104), "104th") self.assertEqual(self.locale.ordinal_number(110), "110th") self.assertEqual(self.locale.ordinal_number(111), "111th") self.assertEqual(self.locale.ordinal_number(112), "112th") self.assertEqual(self.locale.ordinal_number(113), "113th") self.assertEqual(self.locale.ordinal_number(114), "114th") self.assertEqual(self.locale.ordinal_number(121), "121st") self.assertEqual(self.locale.ordinal_number(122), "122nd") self.assertEqual(self.locale.ordinal_number(123), "123rd") self.assertEqual(self.locale.ordinal_number(124), "124th") def test_meridian_invalid_token(self): self.assertEqual(self.locale.meridian(7, None), None) self.assertEqual(self.locale.meridian(7, "B"), None) self.assertEqual(self.locale.meridian(7, "NONSENSE"), None) class EnglishLocaleTests(Chai): def setUp(self): super(EnglishLocaleTests, self).setUp() self.locale = locales.EnglishLocale() def test_describe(self): self.assertEqual(self.locale.describe("now", only_distance=True), "instantly") self.assertEqual(self.locale.describe("now", only_distance=False), "just now") class ItalianLocalesTests(Chai): def test_ordinal_number(self): locale = locales.ItalianLocale() self.assertEqual(locale.ordinal_number(1), "1º") class SpanishLocalesTests(Chai): def test_ordinal_number(self): locale = locales.SpanishLocale() self.assertEqual(locale.ordinal_number(1), "1º") def test_format_timeframe(self): locale = locales.SpanishLocale() self.assertEqual(locale._format_timeframe("now", 0), "ahora") self.assertEqual(locale._format_timeframe("seconds", 1), "1 segundos") self.assertEqual(locale._format_timeframe("seconds", 3), "3 segundos") self.assertEqual(locale._format_timeframe("seconds", 30), "30 segundos") self.assertEqual(locale._format_timeframe("minute", 1), "un minuto") self.assertEqual(locale._format_timeframe("minutes", 4), "4 minutos") self.assertEqual(locale._format_timeframe("minutes", 40), "40 minutos") self.assertEqual(locale._format_timeframe("hour", 1), "una hora") self.assertEqual(locale._format_timeframe("hours", 5), "5 horas") self.assertEqual(locale._format_timeframe("hours", 23), "23 horas") self.assertEqual(locale._format_timeframe("day", 1), "un día") self.assertEqual(locale._format_timeframe("days", 6), "6 días") self.assertEqual(locale._format_timeframe("days", 12), "12 días") self.assertEqual(locale._format_timeframe("week", 1), "una semana") self.assertEqual(locale._format_timeframe("weeks", 2), "2 semanas") self.assertEqual(locale._format_timeframe("weeks", 3), "3 semanas") self.assertEqual(locale._format_timeframe("month", 1), "un mes") self.assertEqual(locale._format_timeframe("months", 7), "7 meses") self.assertEqual(locale._format_timeframe("months", 11), "11 meses") self.assertEqual(locale._format_timeframe("year", 1), "un año") self.assertEqual(locale._format_timeframe("years", 8), "8 años") self.assertEqual(locale._format_timeframe("years", 12), "12 años") self.assertEqual(locale._format_timeframe("now", 0), "ahora") self.assertEqual(locale._format_timeframe("seconds", -1), "1 segundos") self.assertEqual(locale._format_timeframe("seconds", -9), "9 segundos") self.assertEqual(locale._format_timeframe("seconds", -12), "12 segundos") self.assertEqual(locale._format_timeframe("minute", -1), "un minuto") self.assertEqual(locale._format_timeframe("minutes", -2), "2 minutos") self.assertEqual(locale._format_timeframe("minutes", -10), "10 minutos") self.assertEqual(locale._format_timeframe("hour", -1), "una hora") self.assertEqual(locale._format_timeframe("hours", -3), "3 horas") self.assertEqual(locale._format_timeframe("hours", -11), "11 horas") self.assertEqual(locale._format_timeframe("day", -1), "un día") self.assertEqual(locale._format_timeframe("days", -2), "2 días") self.assertEqual(locale._format_timeframe("days", -12), "12 días") self.assertEqual(locale._format_timeframe("week", -1), "una semana") self.assertEqual(locale._format_timeframe("weeks", -2), "2 semanas") self.assertEqual(locale._format_timeframe("weeks", -3), "3 semanas") self.assertEqual(locale._format_timeframe("month", -1), "un mes") self.assertEqual(locale._format_timeframe("months", -3), "3 meses") self.assertEqual(locale._format_timeframe("months", -13), "13 meses") self.assertEqual(locale._format_timeframe("year", -1), "un año") self.assertEqual(locale._format_timeframe("years", -4), "4 años") self.assertEqual(locale._format_timeframe("years", -14), "14 años") class FrenchLocalesTests(Chai): def test_ordinal_number(self): locale = locales.FrenchLocale() self.assertEqual(locale.ordinal_number(1), "1er") self.assertEqual(locale.ordinal_number(2), "2e") class RussianLocalesTests(Chai): def test_plurals2(self): locale = locales.RussianLocale() self.assertEqual(locale._format_timeframe("hours", 0), "0 часов") self.assertEqual(locale._format_timeframe("hours", 1), "1 час") self.assertEqual(locale._format_timeframe("hours", 2), "2 часа") self.assertEqual(locale._format_timeframe("hours", 4), "4 часа") self.assertEqual(locale._format_timeframe("hours", 5), "5 часов") self.assertEqual(locale._format_timeframe("hours", 21), "21 час") self.assertEqual(locale._format_timeframe("hours", 22), "22 часа") self.assertEqual(locale._format_timeframe("hours", 25), "25 часов") # feminine grammatical gender should be tested separately self.assertEqual(locale._format_timeframe("minutes", 0), "0 минут") self.assertEqual(locale._format_timeframe("minutes", 1), "1 минуту") self.assertEqual(locale._format_timeframe("minutes", 2), "2 минуты") self.assertEqual(locale._format_timeframe("minutes", 4), "4 минуты") self.assertEqual(locale._format_timeframe("minutes", 5), "5 минут") self.assertEqual(locale._format_timeframe("minutes", 21), "21 минуту") self.assertEqual(locale._format_timeframe("minutes", 22), "22 минуты") self.assertEqual(locale._format_timeframe("minutes", 25), "25 минут") class PolishLocalesTests(Chai): def test_plurals(self): locale = locales.PolishLocale() self.assertEqual(locale._format_timeframe("hours", 0), "0 godzin") self.assertEqual(locale._format_timeframe("hours", 1), "1 godzin") self.assertEqual(locale._format_timeframe("hours", 2), "2 godziny") self.assertEqual(locale._format_timeframe("hours", 4), "4 godziny") self.assertEqual(locale._format_timeframe("hours", 5), "5 godzin") self.assertEqual(locale._format_timeframe("hours", 21), "21 godzin") self.assertEqual(locale._format_timeframe("hours", 22), "22 godziny") self.assertEqual(locale._format_timeframe("hours", 25), "25 godzin") class IcelandicLocalesTests(Chai): def setUp(self): super(IcelandicLocalesTests, self).setUp() self.locale = locales.IcelandicLocale() def test_format_timeframe(self): self.assertEqual(self.locale._format_timeframe("minute", -1), "einni mínútu") self.assertEqual(self.locale._format_timeframe("minute", 1), "eina mínútu") self.assertEqual(self.locale._format_timeframe("hours", -2), "2 tímum") self.assertEqual(self.locale._format_timeframe("hours", 2), "2 tíma") self.assertEqual(self.locale._format_timeframe("now", 0), "rétt í þessu") class MalayalamLocaleTests(Chai): def setUp(self): super(MalayalamLocaleTests, self).setUp() self.locale = locales.MalayalamLocale() def test_format_timeframe(self): self.assertEqual(self.locale._format_timeframe("hours", 2), "2 മണിക്കൂർ") self.assertEqual(self.locale._format_timeframe("hour", 0), "ഒരു മണിക്കൂർ") def test_format_relative_now(self): result = self.locale._format_relative("ഇപ്പോൾ", "now", 0) self.assertEqual(result, "ഇപ്പോൾ") def test_format_relative_past(self): result = self.locale._format_relative("ഒരു മണിക്കൂർ", "hour", 1) self.assertEqual(result, "ഒരു മണിക്കൂർ ശേഷം") def test_format_relative_future(self): result = self.locale._format_relative("ഒരു മണിക്കൂർ", "hour", -1) self.assertEqual(result, "ഒരു മണിക്കൂർ മുമ്പ്") class HindiLocaleTests(Chai): def setUp(self): super(HindiLocaleTests, self).setUp() self.locale = locales.HindiLocale() def test_format_timeframe(self): self.assertEqual(self.locale._format_timeframe("hours", 2), "2 घंटे") self.assertEqual(self.locale._format_timeframe("hour", 0), "एक घंटा") def test_format_relative_now(self): result = self.locale._format_relative("अभी", "now", 0) self.assertEqual(result, "अभी") def test_format_relative_past(self): result = self.locale._format_relative("एक घंटा", "hour", 1) self.assertEqual(result, "एक घंटा बाद") def test_format_relative_future(self): result = self.locale._format_relative("एक घंटा", "hour", -1) self.assertEqual(result, "एक घंटा पहले") class CzechLocaleTests(Chai): def setUp(self): super(CzechLocaleTests, self).setUp() self.locale = locales.CzechLocale() def test_format_timeframe(self): self.assertEqual(self.locale._format_timeframe("hours", 2), "2 hodiny") self.assertEqual(self.locale._format_timeframe("hours", 5), "5 hodin") self.assertEqual(self.locale._format_timeframe("hour", 0), "0 hodin") self.assertEqual(self.locale._format_timeframe("hours", -2), "2 hodinami") self.assertEqual(self.locale._format_timeframe("hours", -5), "5 hodinami") self.assertEqual(self.locale._format_timeframe("now", 0), "Teď") def test_format_relative_now(self): result = self.locale._format_relative("Teď", "now", 0) self.assertEqual(result, "Teď") def test_format_relative_future(self): result = self.locale._format_relative("hodinu", "hour", 1) self.assertEqual(result, "Za hodinu") def test_format_relative_past(self): result = self.locale._format_relative("hodinou", "hour", -1) self.assertEqual(result, "Před hodinou") class SlovakLocaleTests(Chai): def setUp(self): super(SlovakLocaleTests, self).setUp() self.locale = locales.SlovakLocale() def test_format_timeframe(self): self.assertEqual(self.locale._format_timeframe("hours", 2), "2 hodiny") self.assertEqual(self.locale._format_timeframe("hours", 5), "5 hodín") self.assertEqual(self.locale._format_timeframe("hour", 0), "0 hodín") self.assertEqual(self.locale._format_timeframe("hours", -2), "2 hodinami") self.assertEqual(self.locale._format_timeframe("hours", -5), "5 hodinami") self.assertEqual(self.locale._format_timeframe("now", 0), "Teraz") def test_format_relative_now(self): result = self.locale._format_relative("Teraz", "now", 0) self.assertEqual(result, "Teraz") def test_format_relative_future(self): result = self.locale._format_relative("hodinu", "hour", 1) self.assertEqual(result, "O hodinu") def test_format_relative_past(self): result = self.locale._format_relative("hodinou", "hour", -1) self.assertEqual(result, "Pred hodinou") class BulgarianLocaleTests(Chai): def test_plurals2(self): locale = locales.BulgarianLocale() self.assertEqual(locale._format_timeframe("hours", 0), "0 часа") self.assertEqual(locale._format_timeframe("hours", 1), "1 час") self.assertEqual(locale._format_timeframe("hours", 2), "2 часа") self.assertEqual(locale._format_timeframe("hours", 4), "4 часа") self.assertEqual(locale._format_timeframe("hours", 5), "5 часа") self.assertEqual(locale._format_timeframe("hours", 21), "21 час") self.assertEqual(locale._format_timeframe("hours", 22), "22 часа") self.assertEqual(locale._format_timeframe("hours", 25), "25 часа") # feminine grammatical gender should be tested separately self.assertEqual(locale._format_timeframe("minutes", 0), "0 минути") self.assertEqual(locale._format_timeframe("minutes", 1), "1 минута") self.assertEqual(locale._format_timeframe("minutes", 2), "2 минути") self.assertEqual(locale._format_timeframe("minutes", 4), "4 минути") self.assertEqual(locale._format_timeframe("minutes", 5), "5 минути") self.assertEqual(locale._format_timeframe("minutes", 21), "21 минута") self.assertEqual(locale._format_timeframe("minutes", 22), "22 минути") self.assertEqual(locale._format_timeframe("minutes", 25), "25 минути") class MacedonianLocaleTests(Chai): def test_plurals_mk(self): locale = locales.MacedonianLocale() # time self.assertEqual(locale._format_relative("сега", "now", 0), "сега") # Hours self.assertEqual(locale._format_timeframe("hours", 0), "0 саати") self.assertEqual(locale._format_timeframe("hours", 1), "1 саат") self.assertEqual(locale._format_timeframe("hours", 2), "2 саати") self.assertEqual(locale._format_timeframe("hours", 4), "4 саати") self.assertEqual(locale._format_timeframe("hours", 5), "5 саати") self.assertEqual(locale._format_timeframe("hours", 21), "21 саат") self.assertEqual(locale._format_timeframe("hours", 22), "22 саати") self.assertEqual(locale._format_timeframe("hours", 25), "25 саати") # Minutes self.assertEqual(locale._format_timeframe("minutes", 0), "0 минути") self.assertEqual(locale._format_timeframe("minutes", 1), "1 минута") self.assertEqual(locale._format_timeframe("minutes", 2), "2 минути") self.assertEqual(locale._format_timeframe("minutes", 4), "4 минути") self.assertEqual(locale._format_timeframe("minutes", 5), "5 минути") self.assertEqual(locale._format_timeframe("minutes", 21), "21 минута") self.assertEqual(locale._format_timeframe("minutes", 22), "22 минути") self.assertEqual(locale._format_timeframe("minutes", 25), "25 минути") class HebrewLocaleTests(Chai): def test_couple_of_timeframe(self): locale = locales.HebrewLocale() self.assertEqual(locale._format_timeframe("hours", 2), "שעתיים") self.assertEqual(locale._format_timeframe("months", 2), "חודשיים") self.assertEqual(locale._format_timeframe("days", 2), "יומיים") self.assertEqual(locale._format_timeframe("years", 2), "שנתיים") self.assertEqual(locale._format_timeframe("hours", 3), "3 שעות") self.assertEqual(locale._format_timeframe("months", 4), "4 חודשים") self.assertEqual(locale._format_timeframe("days", 3), "3 ימים") self.assertEqual(locale._format_timeframe("years", 5), "5 שנים") class MarathiLocaleTests(Chai): def setUp(self): super(MarathiLocaleTests, self).setUp() self.locale = locales.MarathiLocale() def test_dateCoreFunctionality(self): dt = arrow.Arrow(2015, 4, 11, 17, 30, 00) self.assertEqual(self.locale.month_name(dt.month), "एप्रिल") self.assertEqual(self.locale.month_abbreviation(dt.month), "एप्रि") self.assertEqual(self.locale.day_name(dt.isoweekday()), "शनिवार") self.assertEqual(self.locale.day_abbreviation(dt.isoweekday()), "शनि") def test_format_timeframe(self): self.assertEqual(self.locale._format_timeframe("hours", 2), "2 तास") self.assertEqual(self.locale._format_timeframe("hour", 0), "एक तास") def test_format_relative_now(self): result = self.locale._format_relative("सद्य", "now", 0) self.assertEqual(result, "सद्य") def test_format_relative_past(self): result = self.locale._format_relative("एक तास", "hour", 1) self.assertEqual(result, "एक तास नंतर") def test_format_relative_future(self): result = self.locale._format_relative("एक तास", "hour", -1) self.assertEqual(result, "एक तास आधी") # Not currently implemented def test_ordinal_number(self): self.assertEqual(self.locale.ordinal_number(1), "1") class FinnishLocaleTests(Chai): def setUp(self): super(FinnishLocaleTests, self).setUp() self.locale = locales.FinnishLocale() def test_format_timeframe(self): self.assertEqual( self.locale._format_timeframe("hours", 2), ("2 tuntia", "2 tunnin") ) self.assertEqual(self.locale._format_timeframe("hour", 0), ("tunti", "tunnin")) def test_format_relative_now(self): result = self.locale._format_relative(["juuri nyt", "juuri nyt"], "now", 0) self.assertEqual(result, "juuri nyt") def test_format_relative_past(self): result = self.locale._format_relative(["tunti", "tunnin"], "hour", 1) self.assertEqual(result, "tunnin kuluttua") def test_format_relative_future(self): result = self.locale._format_relative(["tunti", "tunnin"], "hour", -1) self.assertEqual(result, "tunti sitten") def test_ordinal_number(self): self.assertEqual(self.locale.ordinal_number(1), "1.") class GermanLocaleTests(Chai): def setUp(self): super(GermanLocaleTests, self).setUp() self.locale = locales.GermanLocale() def test_ordinal_number(self): self.assertEqual(self.locale.ordinal_number(1), "1.") def test_define(self): self.assertEqual( self.locale.describe("minute", only_distance=True), "eine Minute" ) self.assertEqual( self.locale.describe("minute", only_distance=False), "in einer Minute" ) self.assertEqual( self.locale.describe("hour", only_distance=True), "eine Stunde" ) self.assertEqual( self.locale.describe("hour", only_distance=False), "in einer Stunde" ) self.assertEqual(self.locale.describe("day", only_distance=True), "ein Tag") self.assertEqual( self.locale.describe("day", only_distance=False), "in einem Tag" ) self.assertEqual(self.locale.describe("month", only_distance=True), "ein Monat") self.assertEqual( self.locale.describe("month", only_distance=False), "in einem Monat" ) self.assertEqual(self.locale.describe("year", only_distance=True), "ein Jahr") self.assertEqual( self.locale.describe("year", only_distance=False), "in einem Jahr" ) class HungarianLocaleTests(Chai): def setUp(self): super(HungarianLocaleTests, self).setUp() self.locale = locales.HungarianLocale() def test_format_timeframe(self): self.assertEqual(self.locale._format_timeframe("hours", 2), "2 óra") self.assertEqual(self.locale._format_timeframe("hour", 0), "egy órával") self.assertEqual(self.locale._format_timeframe("hours", -2), "2 órával") self.assertEqual(self.locale._format_timeframe("now", 0), "éppen most") class EsperantoLocaleTests(Chai): def setUp(self): super(EsperantoLocaleTests, self).setUp() self.locale = locales.EsperantoLocale() def test_format_timeframe(self): self.assertEqual(self.locale._format_timeframe("hours", 2), "2 horoj") self.assertEqual(self.locale._format_timeframe("hour", 0), "un horo") self.assertEqual(self.locale._format_timeframe("hours", -2), "2 horoj") self.assertEqual(self.locale._format_timeframe("now", 0), "nun") def test_ordinal_number(self): self.assertEqual(self.locale.ordinal_number(1), "1a") class ThaiLocaleTests(Chai): def setUp(self): super(ThaiLocaleTests, self).setUp() self.locale = locales.ThaiLocale() def test_year_full(self): self.assertEqual(self.locale.year_full(2015), "2558") def test_year_abbreviation(self): self.assertEqual(self.locale.year_abbreviation(2015), "58") def test_format_relative_now(self): result = self.locale._format_relative("ขณะนี้", "now", 0) self.assertEqual(result, "ขณะนี้") def test_format_relative_past(self): result = self.locale._format_relative("1 ชั่วโมง", "hour", 1) self.assertEqual(result, "ในอีก 1 ชั่วโมง") result = self.locale._format_relative("{0} ชั่วโมง", "hours", 2) self.assertEqual(result, "ในอีก {0} ชั่วโมง") result = self.locale._format_relative("ไม่กี่วินาที", "seconds", 42) self.assertEqual(result, "ในอีกไม่กี่วินาที") def test_format_relative_future(self): result = self.locale._format_relative("1 ชั่วโมง", "hour", -1) self.assertEqual(result, "1 ชั่วโมง ที่ผ่านมา") class BengaliLocaleTests(Chai): def setUp(self): super(BengaliLocaleTests, self).setUp() self.locale = locales.BengaliLocale() def test_ordinal_number(self): result0 = self.locale._ordinal_number(0) result1 = self.locale._ordinal_number(1) result3 = self.locale._ordinal_number(3) result4 = self.locale._ordinal_number(4) result5 = self.locale._ordinal_number(5) result6 = self.locale._ordinal_number(6) result10 = self.locale._ordinal_number(10) result11 = self.locale._ordinal_number(11) result42 = self.locale._ordinal_number(42) self.assertEqual(result0, "0তম") self.assertEqual(result1, "1ম") self.assertEqual(result3, "3য়") self.assertEqual(result4, "4র্থ") self.assertEqual(result5, "5ম") self.assertEqual(result6, "6ষ্ঠ") self.assertEqual(result10, "10ম") self.assertEqual(result11, "11তম") self.assertEqual(result42, "42তম") self.assertEqual(self.locale._ordinal_number(-1), None) class SwissLocaleTests(Chai): def setUp(self): super(SwissLocaleTests, self).setUp() self.locale = locales.SwissLocale() def test_ordinal_number(self): dt = arrow.Arrow(2015, 4, 11, 17, 30, 00) self.assertEqual(self.locale._format_timeframe("minute", 1), "einer Minute") self.assertEqual(self.locale._format_timeframe("hour", 1), "einer Stunde") self.assertEqual(self.locale.day_abbreviation(dt.isoweekday()), "Sa") class RomanianLocaleTests(Chai): def setUp(self): super(RomanianLocaleTests, self).setUp() self.locale = locales.RomanianLocale() def test_timeframes(self): self.assertEqual(self.locale._format_timeframe("hours", 2), "2 ore") self.assertEqual(self.locale._format_timeframe("months", 2), "2 luni") self.assertEqual(self.locale._format_timeframe("days", 2), "2 zile") self.assertEqual(self.locale._format_timeframe("years", 2), "2 ani") self.assertEqual(self.locale._format_timeframe("hours", 3), "3 ore") self.assertEqual(self.locale._format_timeframe("months", 4), "4 luni") self.assertEqual(self.locale._format_timeframe("days", 3), "3 zile") self.assertEqual(self.locale._format_timeframe("years", 5), "5 ani") def test_relative_timeframes(self): self.assertEqual(self.locale._format_relative("acum", "now", 0), "acum") self.assertEqual( self.locale._format_relative("o oră", "hour", 1), "peste o oră" ) self.assertEqual( self.locale._format_relative("o oră", "hour", -1), "o oră în urmă" ) self.assertEqual( self.locale._format_relative("un minut", "minute", 1), "peste un minut" ) self.assertEqual( self.locale._format_relative("un minut", "minute", -1), "un minut în urmă" ) self.assertEqual( self.locale._format_relative("câteva secunde", "seconds", -1), "câteva secunde în urmă", ) self.assertEqual( self.locale._format_relative("câteva secunde", "seconds", 1), "peste câteva secunde", ) self.assertEqual( self.locale._format_relative("o zi", "day", -1), "o zi în urmă" ) self.assertEqual(self.locale._format_relative("o zi", "day", 1), "peste o zi") class ArabicLocalesTest(Chai): def setUp(self): super(ArabicLocalesTest, self).setUp() self.locale = locales.ArabicLocale() def test_timeframes(self): # single self.assertEqual(self.locale._format_timeframe("minute", 1), "دقيقة") self.assertEqual(self.locale._format_timeframe("hour", 1), "ساعة") self.assertEqual(self.locale._format_timeframe("day", 1), "يوم") self.assertEqual(self.locale._format_timeframe("month", 1), "شهر") self.assertEqual(self.locale._format_timeframe("year", 1), "سنة") # double self.assertEqual(self.locale._format_timeframe("minutes", 2), "دقيقتين") self.assertEqual(self.locale._format_timeframe("hours", 2), "ساعتين") self.assertEqual(self.locale._format_timeframe("days", 2), "يومين") self.assertEqual(self.locale._format_timeframe("months", 2), "شهرين") self.assertEqual(self.locale._format_timeframe("years", 2), "سنتين") # up to ten self.assertEqual(self.locale._format_timeframe("minutes", 3), "3 دقائق") self.assertEqual(self.locale._format_timeframe("hours", 4), "4 ساعات") self.assertEqual(self.locale._format_timeframe("days", 5), "5 أيام") self.assertEqual(self.locale._format_timeframe("months", 6), "6 أشهر") self.assertEqual(self.locale._format_timeframe("years", 10), "10 سنوات") # more than ten self.assertEqual(self.locale._format_timeframe("minutes", 11), "11 دقيقة") self.assertEqual(self.locale._format_timeframe("hours", 19), "19 ساعة") self.assertEqual(self.locale._format_timeframe("months", 24), "24 شهر") self.assertEqual(self.locale._format_timeframe("days", 50), "50 يوم") self.assertEqual(self.locale._format_timeframe("years", 115), "115 سنة") class NepaliLocaleTests(Chai): def setUp(self): super(NepaliLocaleTests, self).setUp() self.locale = locales.NepaliLocale() def test_format_timeframe(self): self.assertEqual(self.locale._format_timeframe("hours", 3), "3 घण्टा") self.assertEqual(self.locale._format_timeframe("hour", 0), "एक घण्टा") def test_format_relative_now(self): result = self.locale._format_relative("अहिले", "now", 0) self.assertEqual(result, "अहिले") def test_format_relative_future(self): result = self.locale._format_relative("एक घण्टा", "hour", 1) self.assertEqual(result, "एक घण्टा पछी") def test_format_relative_past(self): result = self.locale._format_relative("एक घण्टा", "hour", -1) self.assertEqual(result, "एक घण्टा पहिले") class IndonesianLocaleTests(Chai): def setUp(self): super(IndonesianLocaleTests, self).setUp() self.locale = locales.IndonesianLocale() def test_timeframes(self): self.assertEqual(self.locale._format_timeframe("hours", 2), "2 jam") self.assertEqual(self.locale._format_timeframe("months", 2), "2 bulan") self.assertEqual(self.locale._format_timeframe("days", 2), "2 hari") self.assertEqual(self.locale._format_timeframe("years", 2), "2 tahun") self.assertEqual(self.locale._format_timeframe("hours", 3), "3 jam") self.assertEqual(self.locale._format_timeframe("months", 4), "4 bulan") self.assertEqual(self.locale._format_timeframe("days", 3), "3 hari") self.assertEqual(self.locale._format_timeframe("years", 5), "5 tahun") def test_format_relative_now(self): self.assertEqual( self.locale._format_relative("baru saja", "now", 0), "baru saja" ) def test_format_relative_past(self): self.assertEqual( self.locale._format_relative("1 jam", "hour", 1), "dalam 1 jam" ) self.assertEqual( self.locale._format_relative("1 detik", "seconds", 1), "dalam 1 detik" ) def test_format_relative_future(self): self.assertEqual( self.locale._format_relative("1 jam", "hour", -1), "1 jam yang lalu" ) class TagalogLocaleTests(Chai): def setUp(self): super(TagalogLocaleTests, self).setUp() self.locale = locales.TagalogLocale() def test_format_timeframe(self): self.assertEqual(self.locale._format_timeframe("minute", 1), "isang minuto") self.assertEqual(self.locale._format_timeframe("hour", 1), "isang oras") self.assertEqual(self.locale._format_timeframe("month", 1), "isang buwan") self.assertEqual(self.locale._format_timeframe("year", 1), "isang taon") self.assertEqual(self.locale._format_timeframe("seconds", 2), "2 segundo") self.assertEqual(self.locale._format_timeframe("minutes", 3), "3 minuto") self.assertEqual(self.locale._format_timeframe("hours", 4), "4 oras") self.assertEqual(self.locale._format_timeframe("months", 5), "5 buwan") self.assertEqual(self.locale._format_timeframe("years", 6), "6 taon") def test_format_relative_now(self): self.assertEqual( self.locale._format_relative("ngayon lang", "now", 0), "ngayon lang" ) def test_format_relative_past(self): self.assertEqual( self.locale._format_relative("2 oras", "hour", 2), "2 oras mula ngayon" ) def test_format_relative_future(self): self.assertEqual( self.locale._format_relative("3 oras", "hour", -3), "nakaraang 3 oras" ) def test_ordinal_number(self): self.assertEqual(self.locale.ordinal_number(0), "ika-0") self.assertEqual(self.locale.ordinal_number(1), "ika-1") self.assertEqual(self.locale.ordinal_number(2), "ika-2") self.assertEqual(self.locale.ordinal_number(3), "ika-3") self.assertEqual(self.locale.ordinal_number(10), "ika-10") self.assertEqual(self.locale.ordinal_number(23), "ika-23") self.assertEqual(self.locale.ordinal_number(100), "ika-100") self.assertEqual(self.locale.ordinal_number(103), "ika-103") self.assertEqual(self.locale.ordinal_number(114), "ika-114") class EstonianLocaleTests(Chai): def setUp(self): super(EstonianLocaleTests, self).setUp() self.locale = locales.EstonianLocale() def test_format_timeframe(self): self.assertEqual(self.locale._format_timeframe("now", 0), "just nüüd") self.assertEqual(self.locale._format_timeframe("second", 1), "ühe sekundi") self.assertEqual(self.locale._format_timeframe("seconds", 3), "3 sekundi") self.assertEqual(self.locale._format_timeframe("seconds", 30), "30 sekundi") self.assertEqual(self.locale._format_timeframe("minute", 1), "ühe minuti") self.assertEqual(self.locale._format_timeframe("minutes", 4), "4 minuti") self.assertEqual(self.locale._format_timeframe("minutes", 40), "40 minuti") self.assertEqual(self.locale._format_timeframe("hour", 1), "tunni aja") self.assertEqual(self.locale._format_timeframe("hours", 5), "5 tunni") self.assertEqual(self.locale._format_timeframe("hours", 23), "23 tunni") self.assertEqual(self.locale._format_timeframe("day", 1), "ühe päeva") self.assertEqual(self.locale._format_timeframe("days", 6), "6 päeva") self.assertEqual(self.locale._format_timeframe("days", 12), "12 päeva") self.assertEqual(self.locale._format_timeframe("month", 1), "ühe kuu") self.assertEqual(self.locale._format_timeframe("months", 7), "7 kuu") self.assertEqual(self.locale._format_timeframe("months", 11), "11 kuu") self.assertEqual(self.locale._format_timeframe("year", 1), "ühe aasta") self.assertEqual(self.locale._format_timeframe("years", 8), "8 aasta") self.assertEqual(self.locale._format_timeframe("years", 12), "12 aasta") self.assertEqual(self.locale._format_timeframe("now", 0), "just nüüd") self.assertEqual(self.locale._format_timeframe("second", -1), "üks sekund") self.assertEqual(self.locale._format_timeframe("seconds", -9), "9 sekundit") self.assertEqual(self.locale._format_timeframe("seconds", -12), "12 sekundit") self.assertEqual(self.locale._format_timeframe("minute", -1), "üks minut") self.assertEqual(self.locale._format_timeframe("minutes", -2), "2 minutit") self.assertEqual(self.locale._format_timeframe("minutes", -10), "10 minutit") self.assertEqual(self.locale._format_timeframe("hour", -1), "tund aega") self.assertEqual(self.locale._format_timeframe("hours", -3), "3 tundi") self.assertEqual(self.locale._format_timeframe("hours", -11), "11 tundi") self.assertEqual(self.locale._format_timeframe("day", -1), "üks päev") self.assertEqual(self.locale._format_timeframe("days", -2), "2 päeva") self.assertEqual(self.locale._format_timeframe("days", -12), "12 päeva") self.assertEqual(self.locale._format_timeframe("month", -1), "üks kuu") self.assertEqual(self.locale._format_timeframe("months", -3), "3 kuud") self.assertEqual(self.locale._format_timeframe("months", -13), "13 kuud") self.assertEqual(self.locale._format_timeframe("year", -1), "üks aasta") self.assertEqual(self.locale._format_timeframe("years", -4), "4 aastat") self.assertEqual(self.locale._format_timeframe("years", -14), "14 aastat") class PortugueseLocaleTests(Chai): def setUp(self): super(PortugueseLocaleTests, self).setUp() self.locale = locales.PortugueseLocale() def test_format_timeframe(self): self.assertEqual(self.locale._format_timeframe("now", 0), "agora") self.assertEqual(self.locale._format_timeframe("second", 1), "um segundo") self.assertEqual(self.locale._format_timeframe("seconds", 30), "30 segundos") self.assertEqual(self.locale._format_timeframe("minute", 1), "um minuto") self.assertEqual(self.locale._format_timeframe("minutes", 40), "40 minutos") self.assertEqual(self.locale._format_timeframe("hour", 1), "uma hora") self.assertEqual(self.locale._format_timeframe("hours", 23), "23 horas") self.assertEqual(self.locale._format_timeframe("day", 1), "um dia") self.assertEqual(self.locale._format_timeframe("days", 12), "12 dias") self.assertEqual(self.locale._format_timeframe("month", 1), "um mês") self.assertEqual(self.locale._format_timeframe("months", 11), "11 meses") self.assertEqual(self.locale._format_timeframe("year", 1), "um ano") self.assertEqual(self.locale._format_timeframe("years", 12), "12 anos") class BrazilianLocaleTests(Chai): def setUp(self): super(BrazilianLocaleTests, self).setUp() self.locale = locales.BrazilianPortugueseLocale() def test_format_timeframe(self): self.assertEqual(self.locale._format_timeframe("now", 0), "agora") self.assertEqual(self.locale._format_timeframe("second", 1), "um segundo") self.assertEqual(self.locale._format_timeframe("seconds", 30), "30 segundos") self.assertEqual(self.locale._format_timeframe("minute", 1), "um minuto") self.assertEqual(self.locale._format_timeframe("minutes", 40), "40 minutos") self.assertEqual(self.locale._format_timeframe("hour", 1), "uma hora") self.assertEqual(self.locale._format_timeframe("hours", 23), "23 horas") self.assertEqual(self.locale._format_timeframe("day", 1), "um dia") self.assertEqual(self.locale._format_timeframe("days", 12), "12 dias") self.assertEqual(self.locale._format_timeframe("month", 1), "um mês") self.assertEqual(self.locale._format_timeframe("months", 11), "11 meses") self.assertEqual(self.locale._format_timeframe("year", 1), "um ano") self.assertEqual(self.locale._format_timeframe("years", 12), "12 anos") class HongKongLocaleTests(Chai): def setUp(self): super(HongKongLocaleTests, self).setUp() self.locale = locales.HongKongLocale() def test_format_timeframe(self): self.assertEqual(self.locale._format_timeframe("now", 0), "剛才") self.assertEqual(self.locale._format_timeframe("second", 1), "1秒") self.assertEqual(self.locale._format_timeframe("seconds", 30), "30秒") self.assertEqual(self.locale._format_timeframe("minute", 1), "1分鐘") self.assertEqual(self.locale._format_timeframe("minutes", 40), "40分鐘") self.assertEqual(self.locale._format_timeframe("hour", 1), "1小時") self.assertEqual(self.locale._format_timeframe("hours", 23), "23小時") self.assertEqual(self.locale._format_timeframe("day", 1), "1天") self.assertEqual(self.locale._format_timeframe("days", 12), "12天") self.assertEqual(self.locale._format_timeframe("week", 1), "1星期") self.assertEqual(self.locale._format_timeframe("weeks", 38), "38星期") self.assertEqual(self.locale._format_timeframe("month", 1), "1個月") self.assertEqual(self.locale._format_timeframe("months", 11), "11個月") self.assertEqual(self.locale._format_timeframe("year", 1), "1年") self.assertEqual(self.locale._format_timeframe("years", 12), "12年") arrow-0.15.5/tests/parser_tests.py0000664000175000017500000015750213603422516017446 0ustar chrischris00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals import calendar import os import time from datetime import datetime from chai import Chai from dateutil import tz from arrow import parser from arrow.constants import MAX_TIMESTAMP_US from arrow.parser import DateTimeParser, ParserError, ParserMatchError from .utils import make_full_tz_list class DateTimeParserTests(Chai): def setUp(self): super(DateTimeParserTests, self).setUp() self.parser = parser.DateTimeParser() def test_parse_multiformat(self): mock_datetime = self.mock() self.expect(self.parser.parse).args("str", "fmt_a").raises(ParserMatchError) self.expect(self.parser.parse).args("str", "fmt_b").returns(mock_datetime) result = self.parser._parse_multiformat("str", ["fmt_a", "fmt_b"]) self.assertEqual(result, mock_datetime) def test_parse_multiformat_all_fail(self): self.expect(self.parser.parse).args("str", "fmt_a").raises(ParserMatchError) self.expect(self.parser.parse).args("str", "fmt_b").raises(ParserMatchError) with self.assertRaises(ParserError): self.parser._parse_multiformat("str", ["fmt_a", "fmt_b"]) def test_parse_multiformat_unself_expected_fail(self): class UnselfExpectedError(Exception): pass self.expect(self.parser.parse).args("str", "fmt_a").raises(UnselfExpectedError) with self.assertRaises(UnselfExpectedError): self.parser._parse_multiformat("str", ["fmt_a", "fmt_b"]) def test_parse_token_nonsense(self): parts = {} self.parser._parse_token("NONSENSE", "1900", parts) self.assertEqual(parts, {}) def test_parse_token_invalid_meridians(self): parts = {} self.parser._parse_token("A", "a..m", parts) self.assertEqual(parts, {}) self.parser._parse_token("a", "p..m", parts) self.assertEqual(parts, {}) def test_parser_no_caching(self): self.expect(parser.DateTimeParser, "_generate_pattern_re").args("fmt_a").times( 100 ) self.parser = parser.DateTimeParser(cache_size=0) for _ in range(100): self.parser._generate_pattern_re("fmt_a") def test_parser_1_line_caching(self): self.expect(parser.DateTimeParser, "_generate_pattern_re").args("fmt_a").times( 1 ) self.parser = parser.DateTimeParser(cache_size=1) for _ in range(100): self.parser._generate_pattern_re("fmt_a") self.expect(parser.DateTimeParser, "_generate_pattern_re").args("fmt_b").times( 1 ) for _ in range(100): self.parser._generate_pattern_re("fmt_a") self.parser._generate_pattern_re("fmt_b") self.expect(parser.DateTimeParser, "_generate_pattern_re").args("fmt_a").times( 1 ) for _ in range(100): self.parser._generate_pattern_re("fmt_a") def test_parser_multiple_line_caching(self): self.expect(parser.DateTimeParser, "_generate_pattern_re").args("fmt_a").times( 1 ) self.parser = parser.DateTimeParser(cache_size=2) for _ in range(100): self.parser._generate_pattern_re("fmt_a") self.expect(parser.DateTimeParser, "_generate_pattern_re").args("fmt_b").times( 1 ) for _ in range(100): self.parser._generate_pattern_re("fmt_a") self.parser._generate_pattern_re("fmt_b") self.expect(parser.DateTimeParser, "_generate_pattern_re").args("fmt_a").times( 0 ) for _ in range(100): self.parser._generate_pattern_re("fmt_a") def test_YY_and_YYYY_format_list(self): self.assertEqual( self.parser.parse("15/01/19", ["DD/MM/YY", "DD/MM/YYYY"]), datetime(2019, 1, 15), ) # Regression test for issue #580 self.assertEqual( self.parser.parse("15/01/2019", ["DD/MM/YY", "DD/MM/YYYY"]), datetime(2019, 1, 15), ) self.assertEqual( 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 self.assertEqual( self.parser.parse( "15 Jul 2000", ["MM/DD/YYYY", "YYYY-MM-DD", "X", "DD-MMMM-YYYY", "D MMM YYYY"], ), datetime(2000, 7, 15), ) with self.assertRaises(ParserError): self.parser.parse("15 Jul", "X") class DateTimeParserParseTests(Chai): def setUp(self): super(DateTimeParserParseTests, self).setUp() self.parser = parser.DateTimeParser() def test_parse_list(self): self.expect(self.parser._parse_multiformat).args( "str", ["fmt_a", "fmt_b"] ).returns("result") result = self.parser.parse("str", ["fmt_a", "fmt_b"]) self.assertEqual(result, "result") def test_parse_unrecognized_token(self): mock_input_re_map = self.mock(self.parser, "_input_re_map") self.expect(mock_input_re_map.__getitem__).args("YYYY").raises(KeyError) with self.assertRaises(parser.ParserError): self.parser.parse("2013-01-01", "YYYY-MM-DD") def test_parse_parse_no_match(self): with self.assertRaises(ParserError): self.parser.parse("01-01", "YYYY-MM-DD") def test_parse_separators(self): with self.assertRaises(ParserError): self.parser.parse("1403549231", "YYYY-MM-DD") def test_parse_numbers(self): self.expected = datetime(2012, 1, 1, 12, 5, 10) self.assertEqual( self.parser.parse("2012-01-01 12:05:10", "YYYY-MM-DD HH:mm:ss"), self.expected, ) def test_parse_year_two_digit(self): self.expected = datetime(1979, 1, 1, 12, 5, 10) self.assertEqual( 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() int_timestamp = int(time.time()) self.expected = datetime.fromtimestamp(int_timestamp, tz=tz_utc) self.assertEqual( self.parser.parse("{:d}".format(int_timestamp), "X"), self.expected ) float_timestamp = time.time() self.expected = datetime.fromtimestamp(float_timestamp, tz=tz_utc) self.assertEqual( self.parser.parse("{:f}".format(float_timestamp), "X"), self.expected ) # test handling of ns timestamp (arrow will round to 6 digits regardless) self.expected = datetime.fromtimestamp(float_timestamp, tz=tz_utc) self.assertEqual( self.parser.parse("{:f}123".format(float_timestamp), "X"), self.expected ) # test ps timestamp (arrow will round to 6 digits regardless) self.expected = datetime.fromtimestamp(float_timestamp, tz=tz_utc) self.assertEqual( self.parser.parse("{:f}123456".format(float_timestamp), "X"), self.expected ) # NOTE: negative timestamps cannot be handled by datetime on Window # Must use timedelta to handle them. ref: https://stackoverflow.com/questions/36179914 if os.name != "nt": # regression test for issue #662 negative_int_timestamp = -int_timestamp self.expected = datetime.fromtimestamp(negative_int_timestamp, tz=tz_utc) self.assertEqual( self.parser.parse("{:d}".format(negative_int_timestamp), "X"), self.expected, ) negative_float_timestamp = -float_timestamp self.expected = datetime.fromtimestamp(negative_float_timestamp, tz=tz_utc) self.assertEqual( self.parser.parse("{:f}".format(negative_float_timestamp), "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 self.assertRaises(ParserError): natural_lang_string = "Meet me at {} at the restaurant.".format( float_timestamp ) self.parser.parse(natural_lang_string, "X") with self.assertRaises(ParserError): self.parser.parse("1565982019.", "X") with self.assertRaises(ParserError): self.parser.parse(".1565982019", "X") 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 = int(round(timestamp * 1000)) timestamp_micro = int(round(timestamp * 1000000)) # "x" token should parse integer timestamps below MAX_TIMESTAMP normally self.expected = datetime.fromtimestamp(int(timestamp), tz=tz_utc) self.assertEqual( self.parser.parse("{:d}".format(int(timestamp)), "x"), self.expected ) self.expected = datetime.fromtimestamp(round(timestamp, 3), tz=tz_utc) self.assertEqual( self.parser.parse("{:d}".format(timestamp_milli), "x"), self.expected ) self.expected = datetime.fromtimestamp(timestamp, tz=tz_utc) self.assertEqual( self.parser.parse("{:d}".format(timestamp_micro), "x"), self.expected ) # anything above max µs timestamp should fail with self.assertRaises(ValueError): self.parser.parse("{:d}".format(int(MAX_TIMESTAMP_US) + 1), "x") # floats are not allowed with the "x" token with self.assertRaises(ParserMatchError): self.parser.parse("{:f}".format(timestamp), "x") def test_parse_names(self): self.expected = datetime(2012, 1, 1) self.assertEqual( self.parser.parse("January 1, 2012", "MMMM D, YYYY"), self.expected ) self.assertEqual(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) self.assertEqual(self.parser.parse("1 pm", "H a"), self.expected) self.assertEqual(self.parser.parse("1 pm", "h a"), self.expected) self.expected = datetime(1, 1, 1, 1, 0, 0) self.assertEqual(self.parser.parse("1 am", "H A"), self.expected) self.assertEqual(self.parser.parse("1 am", "h A"), self.expected) self.expected = datetime(1, 1, 1, 0, 0, 0) self.assertEqual(self.parser.parse("12 am", "H A"), self.expected) self.assertEqual(self.parser.parse("12 am", "h A"), self.expected) self.expected = datetime(1, 1, 1, 12, 0, 0) self.assertEqual(self.parser.parse("12 pm", "H A"), self.expected) self.assertEqual(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") self.assertEqual(parsed, self.expected) def test_parse_tz_zz(self): self.expected = datetime(2013, 1, 1, tzinfo=tz.tzoffset(None, -7 * 3600)) self.assertEqual( self.parser.parse("2013-01-01 -07:00", "YYYY-MM-DD ZZ"), self.expected ) def test_parse_tz_name_zzz(self): for tz_name in make_full_tz_list(): self.expected = datetime(2013, 1, 1, tzinfo=tz.gettz(tz_name)) self.assertEqual( self.parser.parse("2013-01-01 %s" % tz_name, "YYYY-MM-DD ZZZ"), self.expected, ) # note that offsets are not timezones with self.assertRaises(ParserError): self.parser.parse("2013-01-01 12:30:45.9+1000", "YYYY-MM-DDZZZ") with self.assertRaises(ParserError): self.parser.parse("2013-01-01 12:30:45.9+10:00", "YYYY-MM-DDZZZ") with self.assertRaises(ParserError): self.parser.parse("2013-01-01 12:30:45.9-10", "YYYY-MM-DDZZZ") def test_parse_subsecond(self): # TODO: make both test_parse_subsecond functions in Parse and ParseISO # tests use the same expected objects (use pytest fixtures) self.expected = datetime(2013, 1, 1, 12, 30, 45, 900000) self.assertEqual( 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) self.assertEqual( 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) self.assertEqual( 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) self.assertEqual( 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) self.assertEqual( 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) self.assertEqual( 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" self.assertEqual(self.parser.parse(string, datetime_format), self.expected) self.assertEqual(self.parser.parse_iso(string), self.expected) # round down string = "2013-01-01 12:30:45.98765432" self.assertEqual(self.parser.parse(string, datetime_format), self.expected) self.assertEqual(self.parser.parse_iso(string), self.expected) # round half-up string = "2013-01-01 12:30:45.987653521" self.assertEqual(self.parser.parse(string, datetime_format), self.expected) self.assertEqual(self.parser.parse_iso(string), self.expected) # round half-down string = "2013-01-01 12:30:45.9876545210" self.assertEqual(self.parser.parse(string, datetime_format), self.expected) self.assertEqual(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" self.assertEqual(self.parser.parse(string, datetime_format), self.expected) self.assertEqual(self.parser.parse_iso(string), self.expected) self.expected = datetime(2013, 1, 1, 12, 31, 0) string = "2013-01-01 12:30:59.9999999" self.assertEqual(self.parser.parse(string, datetime_format), self.expected) self.assertEqual(self.parser.parse_iso(string), self.expected) self.expected = datetime(2013, 1, 2, 0, 0, 0) string = "2013-01-01 23:59:59.9999999" self.assertEqual(self.parser.parse(string, datetime_format), self.expected) self.assertEqual(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" self.assertEqual(self.parser.parse(string, datetime_format), self.expected) self.assertEqual(self.parser.parse_iso(string), self.expected) # Regression tests for issue #560 def test_parse_long_year(self): with self.assertRaises(ParserError): self.parser.parse("09 January 123456789101112", "DD MMMM YYYY") with self.assertRaises(ParserError): self.parser.parse("123456789101112 09 January", "YYYY DD MMMM") with self.assertRaises(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 self.assertRaises(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. self.assertEqual( self.parser.parse("blah 2016 blah", "YYYY"), datetime(2016, 1, 1) ) self.assertEqual(self.parser.parse("blah 2016", "YYYY"), datetime(2016, 1, 1)) self.assertEqual(self.parser.parse("2016 blah", "YYYY"), datetime(2016, 1, 1)) # test one additional space along with space divider self.assertEqual( 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), ) self.assertEqual( 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 self.assertEqual( self.parser.parse( "blah 2016-05-16T04:05:06.789120", "YYYY-MM-DDThh:mm:ss.S" ), datetime(2016, 5, 16, 4, 5, 6, 789120), ) self.assertEqual( self.parser.parse( "2016-05-16T04:05:06.789120 blah", "YYYY-MM-DDThh:mm:ss.S" ), datetime(2016, 5, 16, 4, 5, 6, 789120), ) self.assertEqual( 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), ) self.assertEqual( 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): self.assertEqual( self.parser.parse( "Meet me at my house on Halloween (2019-31-10)", "YYYY-DD-MM" ), datetime(2019, 10, 31), ) self.assertEqual( self.parser.parse( "Monday, 9. September 2019, 16:15-20:00", "dddd, D. MMMM YYYY" ), datetime(2019, 9, 9), ) self.assertEqual( self.parser.parse("A date is 11.11.2011.", "DD.MM.YYYY"), datetime(2011, 11, 11), ) with self.assertRaises(ParserMatchError): self.parser.parse("11.11.2011.1 is not a valid date.", "DD.MM.YYYY") with self.assertRaises(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): self.assertEqual(self.parser.parse(" 2016", "YYYY"), datetime(2016, 1, 1)) self.assertEqual(self.parser.parse("2016 ", "YYYY"), datetime(2016, 1, 1)) self.assertEqual( self.parser.parse(" 2016 ", "YYYY"), datetime(2016, 1, 1) ) self.assertEqual( 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), ) self.assertEqual( 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): self.assertEqual( self.parser.parse("1998-136", "YYYY-DDDD"), datetime(1998, 5, 16) ) self.assertEqual( self.parser.parse("1998-006", "YYYY-DDDD"), datetime(1998, 1, 6) ) with self.assertRaises(ParserError): self.parser.parse("1998-456", "YYYY-DDDD") def test_parse_YYYY_DDD(self): self.assertEqual(self.parser.parse("1998-6", "YYYY-DDD"), datetime(1998, 1, 6)) self.assertEqual( self.parser.parse("1998-136", "YYYY-DDD"), datetime(1998, 5, 16) ) with self.assertRaises(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 self.assertRaises(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 self.assertRaises(ParserError): self.parser.parse("5", "DDD") def test_parse_DDDD_only(self): with self.assertRaises(ParserError): self.parser.parse("145", "DDDD") def test_parse_HH_24(self): self.assertEqual( self.parser.parse("2019-10-30T24:00:00", "YYYY-MM-DDTHH:mm:ss"), datetime(2019, 10, 31, 0, 0, 0, 0), ) self.assertEqual( self.parser.parse("2019-10-30T24:00", "YYYY-MM-DDTHH:mm"), datetime(2019, 10, 31, 0, 0, 0, 0), ) self.assertEqual( self.parser.parse("2019-10-30T24", "YYYY-MM-DDTHH"), datetime(2019, 10, 31, 0, 0, 0, 0), ) self.assertEqual( self.parser.parse("2019-10-30T24:00:00.0", "YYYY-MM-DDTHH:mm:ss.S"), datetime(2019, 10, 31, 0, 0, 0, 0), ) self.assertEqual( self.parser.parse("2019-10-31T24:00:00", "YYYY-MM-DDTHH:mm:ss"), datetime(2019, 11, 1, 0, 0, 0, 0), ) self.assertEqual( self.parser.parse("2019-12-31T24:00:00", "YYYY-MM-DDTHH:mm:ss"), datetime(2020, 1, 1, 0, 0, 0, 0), ) self.assertEqual( self.parser.parse("2019-12-31T23:59:59.9999999", "YYYY-MM-DDTHH:mm:ss.S"), datetime(2020, 1, 1, 0, 0, 0, 0), ) with self.assertRaises(ParserError): self.parser.parse("2019-12-31T24:01:00", "YYYY-MM-DDTHH:mm:ss") with self.assertRaises(ParserError): self.parser.parse("2019-12-31T24:00:01", "YYYY-MM-DDTHH:mm:ss") with self.assertRaises(ParserError): self.parser.parse("2019-12-31T24:00:00.1", "YYYY-MM-DDTHH:mm:ss.S") with self.assertRaises(ParserError): self.parser.parse("2019-12-31T24:00:00.999999", "YYYY-MM-DDTHH:mm:ss.S") class DateTimeParserRegexTests(Chai): def setUp(self): super(DateTimeParserRegexTests, self).setUp() self.format_regex = parser.DateTimeParser._FORMAT_RE def test_format_year(self): self.assertEqual(self.format_regex.findall("YYYY-YY"), ["YYYY", "YY"]) def test_format_month(self): self.assertEqual( self.format_regex.findall("MMMM-MMM-MM-M"), ["MMMM", "MMM", "MM", "M"] ) def test_format_day(self): self.assertEqual( self.format_regex.findall("DDDD-DDD-DD-D"), ["DDDD", "DDD", "DD", "D"] ) def test_format_hour(self): self.assertEqual(self.format_regex.findall("HH-H-hh-h"), ["HH", "H", "hh", "h"]) def test_format_minute(self): self.assertEqual(self.format_regex.findall("mm-m"), ["mm", "m"]) def test_format_second(self): self.assertEqual(self.format_regex.findall("ss-s"), ["ss", "s"]) def test_format_subsecond(self): self.assertEqual( self.format_regex.findall("SSSSSS-SSSSS-SSSS-SSS-SS-S"), ["SSSSSS", "SSSSS", "SSSS", "SSS", "SS", "S"], ) def test_format_tz(self): self.assertEqual(self.format_regex.findall("ZZZ-ZZ-Z"), ["ZZZ", "ZZ", "Z"]) def test_format_am_pm(self): self.assertEqual(self.format_regex.findall("A-a"), ["A", "a"]) def test_format_timestamp(self): self.assertEqual(self.format_regex.findall("X"), ["X"]) def test_format_timestamp_milli(self): self.assertEqual(self.format_regex.findall("x"), ["x"]) def test_escape(self): escape_regex = parser.DateTimeParser._ESCAPE_RE self.assertEqual( 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) self.assertEqual(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) self.assertEqual(result, calendar.month_abbr[1:]) def test_digits(self): self.assertEqual( parser.DateTimeParser._ONE_OR_TWO_DIGIT_RE.findall("4-56"), ["4", "56"] ) self.assertEqual( parser.DateTimeParser._ONE_OR_TWO_OR_THREE_DIGIT_RE.findall("4-56-789"), ["4", "56", "789"], ) self.assertEqual( parser.DateTimeParser._ONE_OR_MORE_DIGIT_RE.findall("4-56-789-1234-12345"), ["4", "56", "789", "1234", "12345"], ) self.assertEqual( parser.DateTimeParser._TWO_DIGIT_RE.findall("12-3-45"), ["12", "45"] ) self.assertEqual( parser.DateTimeParser._THREE_DIGIT_RE.findall("123-4-56"), ["123"] ) self.assertEqual( parser.DateTimeParser._FOUR_DIGIT_RE.findall("1234-56"), ["1234"] ) def test_tz(self): tz_z_re = parser.DateTimeParser._TZ_Z_RE self.assertEqual(tz_z_re.findall("-0700"), [("-", "07", "00")]) self.assertEqual(tz_z_re.findall("+07"), [("+", "07", "")]) self.assertTrue(tz_z_re.search("15/01/2019T04:05:06.789120Z") is not None) self.assertTrue(tz_z_re.search("15/01/2019T04:05:06.789120") is None) tz_zz_re = parser.DateTimeParser._TZ_ZZ_RE self.assertEqual(tz_zz_re.findall("-07:00"), [("-", "07", "00")]) self.assertEqual(tz_zz_re.findall("+07"), [("+", "07", "")]) self.assertTrue(tz_zz_re.search("15/01/2019T04:05:06.789120Z") is not None) self.assertTrue(tz_zz_re.search("15/01/2019T04:05:06.789120") is None) tz_name_re = parser.DateTimeParser._TZ_NAME_RE self.assertEqual(tz_name_re.findall("Europe/Warsaw"), ["Europe/Warsaw"]) self.assertEqual(tz_name_re.findall("GMT"), ["GMT"]) def test_timestamp(self): timestamp_re = parser.DateTimeParser._TIMESTAMP_RE self.assertEqual( timestamp_re.findall("1565707550.452729"), ["1565707550.452729"] ) self.assertEqual( timestamp_re.findall("-1565707550.452729"), ["-1565707550.452729"] ) self.assertEqual(timestamp_re.findall("-1565707550"), ["-1565707550"]) self.assertEqual(timestamp_re.findall("1565707550"), ["1565707550"]) self.assertEqual(timestamp_re.findall("1565707550."), []) self.assertEqual(timestamp_re.findall(".1565707550"), []) def test_timestamp_milli(self): timestamp_expanded_re = parser.DateTimeParser._TIMESTAMP_EXPANDED_RE self.assertEqual(timestamp_expanded_re.findall("-1565707550"), ["-1565707550"]) self.assertEqual(timestamp_expanded_re.findall("1565707550"), ["1565707550"]) self.assertEqual(timestamp_expanded_re.findall("1565707550.452729"), []) self.assertEqual(timestamp_expanded_re.findall("1565707550."), []) self.assertEqual(timestamp_expanded_re.findall(".1565707550"), []) def test_time(self): time_re = parser.DateTimeParser._TIME_RE time_seperators = [":", ""] for sep in time_seperators: self.assertEqual(time_re.findall("12"), [("12", "", "", "", "")]) self.assertEqual( time_re.findall("12{sep}35".format(sep=sep)), [("12", "35", "", "", "")] ) self.assertEqual( time_re.findall("12{sep}35{sep}46".format(sep=sep)), [("12", "35", "46", "", "")], ) self.assertEqual( time_re.findall("12{sep}35{sep}46.952313".format(sep=sep)), [("12", "35", "46", ".", "952313")], ) self.assertEqual( time_re.findall("12{sep}35{sep}46,952313".format(sep=sep)), [("12", "35", "46", ",", "952313")], ) self.assertEqual(time_re.findall("12:"), []) self.assertEqual(time_re.findall("12:35:46."), []) self.assertEqual(time_re.findall("12:35:46,"), []) class DateTimeParserISOTests(Chai): def setUp(self): super(DateTimeParserISOTests, self).setUp() self.parser = parser.DateTimeParser("en_us") def test_YYYY(self): self.assertEqual(self.parser.parse_iso("2013"), datetime(2013, 1, 1)) def test_YYYY_DDDD(self): self.assertEqual(self.parser.parse_iso("1998-136"), datetime(1998, 5, 16)) self.assertEqual(self.parser.parse_iso("1998-006"), datetime(1998, 1, 6)) with self.assertRaises(ParserError): self.parser.parse_iso("1998-456") # 2016 is a leap year, so Feb 29 exists (leap day) self.assertEqual(self.parser.parse_iso("2016-059"), datetime(2016, 2, 28)) self.assertEqual(self.parser.parse_iso("2016-060"), datetime(2016, 2, 29)) self.assertEqual(self.parser.parse_iso("2016-061"), datetime(2016, 3, 1)) # 2017 is not a leap year, so Feb 29 does not exist self.assertEqual(self.parser.parse_iso("2017-059"), datetime(2017, 2, 28)) self.assertEqual(self.parser.parse_iso("2017-060"), datetime(2017, 3, 1)) self.assertEqual(self.parser.parse_iso("2017-061"), datetime(2017, 3, 2)) # Since 2016 is a leap year, the 366th day falls in the same year self.assertEqual(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 self.assertEqual(self.parser.parse_iso("2017-366"), datetime(2018, 1, 1)) def test_YYYY_DDDD_HH_mm_ssZ(self): self.assertEqual( self.parser.parse_iso("2013-036 04:05:06+01:00"), datetime(2013, 2, 5, 4, 5, 6, tzinfo=tz.tzoffset(None, 3600)), ) self.assertEqual( 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 self.assertRaises(ParserError): self.parser.parse_iso("2014-05-125") def test_YYYY_MM(self): for separator in DateTimeParser.SEPARATORS: self.assertEqual( self.parser.parse_iso(separator.join(("2013", "02"))), datetime(2013, 2, 1), ) def test_YYYY_MM_DD(self): for separator in DateTimeParser.SEPARATORS: self.assertEqual( self.parser.parse_iso(separator.join(("2013", "02", "03"))), datetime(2013, 2, 3), ) def test_YYYY_MM_DDTHH_mmZ(self): self.assertEqual( 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): self.assertEqual( self.parser.parse_iso("2013-02-03T04:05"), datetime(2013, 2, 3, 4, 5) ) def test_YYYY_MM_DDTHH(self): self.assertEqual( self.parser.parse_iso("2013-02-03T04"), datetime(2013, 2, 3, 4) ) def test_YYYY_MM_DDTHHZ(self): self.assertEqual( 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): self.assertEqual( 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): self.assertEqual( self.parser.parse_iso("2013-02-03T04:05:06"), datetime(2013, 2, 3, 4, 5, 6) ) def test_YYYY_MM_DD_HH_mmZ(self): self.assertEqual( 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): self.assertEqual( self.parser.parse_iso("2013-02-03 04:05"), datetime(2013, 2, 3, 4, 5) ) def test_YYYY_MM_DD_HH(self): self.assertEqual( self.parser.parse_iso("2013-02-03 04"), datetime(2013, 2, 3, 4) ) def test_invalid_time(self): with self.assertRaises(ParserError): self.parser.parse_iso("2013-02-03T") with self.assertRaises(ParserError): self.parser.parse_iso("2013-02-03 044") with self.assertRaises(ParserError): self.parser.parse_iso("2013-02-03 04:05:06.") def test_YYYY_MM_DD_HH_mm_ssZ(self): self.assertEqual( 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): self.assertEqual( 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): self.assertEqual( self.parser.parse_iso("2013-02-03T04:05:06.7"), datetime(2013, 2, 3, 4, 5, 6, 700000), ) self.assertEqual( self.parser.parse_iso("2013-02-03T04:05:06.78"), datetime(2013, 2, 3, 4, 5, 6, 780000), ) self.assertEqual( self.parser.parse_iso("2013-02-03T04:05:06.789"), datetime(2013, 2, 3, 4, 5, 6, 789000), ) self.assertEqual( self.parser.parse_iso("2013-02-03T04:05:06.7891"), datetime(2013, 2, 3, 4, 5, 6, 789100), ) self.assertEqual( 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. self.assertEqual( 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 self.assertEqual( 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): self.assertEqual( 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)), ) self.assertEqual( 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)), ) self.assertEqual( 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)), ) self.assertEqual( 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)), ) self.assertEqual( 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)), ) self.assertEqual( self.parser.parse_iso("2013-02-03 04:05:06.78912Z"), datetime(2013, 2, 3, 4, 5, 6, 789120, tzinfo=tz.tzutc()), ) def test_invalid_Z(self): with self.assertRaises(ParserError): self.parser.parse_iso("2013-02-03T04:05:06.78912z") with self.assertRaises(ParserError): self.parser.parse_iso("2013-02-03T04:05:06.78912zz") with self.assertRaises(ParserError): self.parser.parse_iso("2013-02-03T04:05:06.78912Zz") with self.assertRaises(ParserError): self.parser.parse_iso("2013-02-03T04:05:06.78912ZZ") with self.assertRaises(ParserError): self.parser.parse_iso("2013-02-03T04:05:06.78912+Z") with self.assertRaises(ParserError): self.parser.parse_iso("2013-02-03T04:05:06.78912-Z") with self.assertRaises(ParserError): self.parser.parse_iso("2013-02-03T04:05:06.78912 Z") def test_parse_subsecond(self): # TODO: make both test_parse_subsecond functions in Parse and ParseISO # tests use the same expected objects (use pytest fixtures) self.expected = datetime(2013, 1, 1, 12, 30, 45, 900000) self.assertEqual(self.parser.parse_iso("2013-01-01 12:30:45.9"), self.expected) self.expected = datetime(2013, 1, 1, 12, 30, 45, 980000) self.assertEqual(self.parser.parse_iso("2013-01-01 12:30:45.98"), self.expected) self.expected = datetime(2013, 1, 1, 12, 30, 45, 987000) self.assertEqual( self.parser.parse_iso("2013-01-01 12:30:45.987"), self.expected ) self.expected = datetime(2013, 1, 1, 12, 30, 45, 987600) self.assertEqual( self.parser.parse_iso("2013-01-01 12:30:45.9876"), self.expected ) self.expected = datetime(2013, 1, 1, 12, 30, 45, 987650) self.assertEqual( self.parser.parse_iso("2013-01-01 12:30:45.98765"), self.expected ) self.expected = datetime(2013, 1, 1, 12, 30, 45, 987654) self.assertEqual( 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) self.assertEqual( 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(1) """ # date -Ins self.assertEqual( 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 self.assertEqual( 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() self.assertEqual(self.parser.parse_iso(dt.isoformat()), dt) def test_parse_iso_with_leading_and_trailing_whitespace(self): datetime_string = " 2016-11-15T06:37:19.123456" with self.assertRaises(ParserError): self.parser.parse_iso(datetime_string) datetime_string = " 2016-11-15T06:37:19.123456 " with self.assertRaises(ParserError): self.parser.parse_iso(datetime_string) datetime_string = "2016-11-15T06:37:19.123456 " with self.assertRaises(ParserError): self.parser.parse_iso(datetime_string) datetime_string = "2016-11-15T 06:37:19.123456" with self.assertRaises(ParserError): self.parser.parse_iso(datetime_string) # leading whitespace datetime_string = " 2016-11-15 06:37:19.123456" with self.assertRaises(ParserError): self.parser.parse_iso(datetime_string) # trailing whitespace datetime_string = "2016-11-15 06:37:19.123456 " with self.assertRaises(ParserError): self.parser.parse_iso(datetime_string) datetime_string = " 2016-11-15 06:37:19.123456 " with self.assertRaises(ParserError): self.parser.parse_iso(datetime_string) # two dividing spaces datetime_string = "2016-11-15 06:37:19.123456" with self.assertRaises(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 self.assertRaises(ParserError): self.parser.parse_iso(ti) def test_iso8601_basic_format(self): self.assertEqual(self.parser.parse_iso("20180517"), datetime(2018, 5, 17)) self.assertEqual( self.parser.parse_iso("20180517T10"), datetime(2018, 5, 17, 10) ) self.assertEqual( self.parser.parse_iso("20180517T105513.843456"), datetime(2018, 5, 17, 10, 55, 13, 843456), ) self.assertEqual( self.parser.parse_iso("20180517T105513Z"), datetime(2018, 5, 17, 10, 55, 13, tzinfo=tz.tzutc()), ) self.assertEqual( self.parser.parse_iso("20180517T105513.843456-0700"), datetime(2018, 5, 17, 10, 55, 13, 843456, tzinfo=tz.tzoffset(None, -25200)), ) self.assertEqual( self.parser.parse_iso("20180517T105513-0700"), datetime(2018, 5, 17, 10, 55, 13, tzinfo=tz.tzoffset(None, -25200)), ) self.assertEqual( self.parser.parse_iso("20180517T105513-07"), datetime(2018, 5, 17, 10, 55, 13, tzinfo=tz.tzoffset(None, -25200)), ) # ordinal in basic format: YYYYDDDD self.assertEqual(self.parser.parse_iso("1998136"), datetime(1998, 5, 16)) # timezone requires +- seperator with self.assertRaises(ParserError): self.parser.parse_iso("20180517T1055130700") with self.assertRaises(ParserError): self.parser.parse_iso("20180517T10551307") # too many digits in date with self.assertRaises(ParserError): self.parser.parse_iso("201860517T105513Z") # too many digits in time with self.assertRaises(ParserError): self.parser.parse_iso("20180517T1055213Z") def test_midnight_end_day(self): self.assertEqual( self.parser.parse_iso("2019-10-30T24:00:00"), datetime(2019, 10, 31, 0, 0, 0, 0), ) self.assertEqual( self.parser.parse_iso("2019-10-30T24:00"), datetime(2019, 10, 31, 0, 0, 0, 0), ) self.assertEqual( self.parser.parse_iso("2019-10-30T24:00:00.0"), datetime(2019, 10, 31, 0, 0, 0, 0), ) self.assertEqual( self.parser.parse_iso("2019-10-31T24:00:00"), datetime(2019, 11, 1, 0, 0, 0, 0), ) self.assertEqual( self.parser.parse_iso("2019-12-31T24:00:00"), datetime(2020, 1, 1, 0, 0, 0, 0), ) self.assertEqual( self.parser.parse_iso("2019-12-31T23:59:59.9999999"), datetime(2020, 1, 1, 0, 0, 0, 0), ) with self.assertRaises(ParserError): self.parser.parse_iso("2019-12-31T24:01:00") with self.assertRaises(ParserError): self.parser.parse_iso("2019-12-31T24:00:01") with self.assertRaises(ParserError): self.parser.parse_iso("2019-12-31T24:00:00.1") with self.assertRaises(ParserError): self.parser.parse_iso("2019-12-31T24:00:00.999999") class TzinfoParserTests(Chai): def setUp(self): super(TzinfoParserTests, self).setUp() self.parser = parser.TzinfoParser() def test_parse_local(self): self.assertEqual(self.parser.parse("local"), tz.tzlocal()) def test_parse_utc(self): self.assertEqual(self.parser.parse("utc"), tz.tzutc()) self.assertEqual(self.parser.parse("UTC"), tz.tzutc()) def test_parse_iso(self): self.assertEqual(self.parser.parse("01:00"), tz.tzoffset(None, 3600)) self.assertEqual( self.parser.parse("11:35"), tz.tzoffset(None, 11 * 3600 + 2100) ) self.assertEqual(self.parser.parse("+01:00"), tz.tzoffset(None, 3600)) self.assertEqual(self.parser.parse("-01:00"), tz.tzoffset(None, -3600)) self.assertEqual(self.parser.parse("0100"), tz.tzoffset(None, 3600)) self.assertEqual(self.parser.parse("+0100"), tz.tzoffset(None, 3600)) self.assertEqual(self.parser.parse("-0100"), tz.tzoffset(None, -3600)) self.assertEqual(self.parser.parse("01"), tz.tzoffset(None, 3600)) self.assertEqual(self.parser.parse("+01"), tz.tzoffset(None, 3600)) self.assertEqual(self.parser.parse("-01"), tz.tzoffset(None, -3600)) def test_parse_str(self): self.assertEqual(self.parser.parse("US/Pacific"), tz.gettz("US/Pacific")) def test_parse_fails(self): with self.assertRaises(parser.ParserError): self.parser.parse("fail") class DateTimeParserMonthNameTests(Chai): def setUp(self): super(DateTimeParserMonthNameTests, self).setUp() self.parser = parser.DateTimeParser("en_us") def test_shortmonth_capitalized(self): self.assertEqual( self.parser.parse("2013-Jan-01", "YYYY-MMM-DD"), datetime(2013, 1, 1) ) def test_shortmonth_allupper(self): self.assertEqual( self.parser.parse("2013-JAN-01", "YYYY-MMM-DD"), datetime(2013, 1, 1) ) def test_shortmonth_alllower(self): self.assertEqual( self.parser.parse("2013-jan-01", "YYYY-MMM-DD"), datetime(2013, 1, 1) ) def test_month_capitalized(self): self.assertEqual( self.parser.parse("2013-January-01", "YYYY-MMMM-DD"), datetime(2013, 1, 1) ) def test_month_allupper(self): self.assertEqual( self.parser.parse("2013-JANUARY-01", "YYYY-MMMM-DD"), datetime(2013, 1, 1) ) def test_month_alllower(self): self.assertEqual( self.parser.parse("2013-january-01", "YYYY-MMMM-DD"), datetime(2013, 1, 1) ) def test_localized_month_name(self): parser_ = parser.DateTimeParser("fr_fr") self.assertEqual( parser_.parse("2013-Janvier-01", "YYYY-MMMM-DD"), datetime(2013, 1, 1) ) def test_localized_month_abbreviation(self): parser_ = parser.DateTimeParser("it_it") self.assertEqual( parser_.parse("2013-Gen-01", "YYYY-MMM-DD"), datetime(2013, 1, 1) ) class DateTimeParserMeridiansTests(Chai): def setUp(self): super(DateTimeParserMeridiansTests, self).setUp() self.parser = parser.DateTimeParser("en_us") def test_meridians_lowercase(self): self.assertEqual( self.parser.parse("2013-01-01 5am", "YYYY-MM-DD ha"), datetime(2013, 1, 1, 5), ) self.assertEqual( self.parser.parse("2013-01-01 5pm", "YYYY-MM-DD ha"), datetime(2013, 1, 1, 17), ) def test_meridians_capitalized(self): self.assertEqual( self.parser.parse("2013-01-01 5AM", "YYYY-MM-DD hA"), datetime(2013, 1, 1, 5), ) self.assertEqual( 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") self.assertEqual( parser_.parse("2013-01-01 5 de", "YYYY-MM-DD h a"), datetime(2013, 1, 1, 5) ) self.assertEqual( 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") self.assertEqual( parser_.parse("2013-01-01 5 DE", "YYYY-MM-DD h A"), datetime(2013, 1, 1, 5) ) self.assertEqual( 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") self.assertEqual( parser_.parse("Junio 30, 2019 - 08:00 pm", "MMMM DD, YYYY - hh:mm a"), datetime(2019, 6, 30, 20, 0), ) with self.assertRaises(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 self.assertRaises(ParserError): parser_.parse("Janvier 30, 2019 - 08:00 pm", "MMMM DD, YYYY - hh:mm a") class DateTimeParserMonthOrdinalDayTests(Chai): def setUp(self): super(DateTimeParserMonthOrdinalDayTests, self).setUp() self.parser = parser.DateTimeParser("en_us") def test_english(self): parser_ = parser.DateTimeParser("en_us") self.assertEqual( parser_.parse("January 1st, 2013", "MMMM Do, YYYY"), datetime(2013, 1, 1) ) self.assertEqual( parser_.parse("January 2nd, 2013", "MMMM Do, YYYY"), datetime(2013, 1, 2) ) self.assertEqual( parser_.parse("January 3rd, 2013", "MMMM Do, YYYY"), datetime(2013, 1, 3) ) self.assertEqual( parser_.parse("January 4th, 2013", "MMMM Do, YYYY"), datetime(2013, 1, 4) ) self.assertEqual( parser_.parse("January 11th, 2013", "MMMM Do, YYYY"), datetime(2013, 1, 11) ) self.assertEqual( parser_.parse("January 12th, 2013", "MMMM Do, YYYY"), datetime(2013, 1, 12) ) self.assertEqual( parser_.parse("January 13th, 2013", "MMMM Do, YYYY"), datetime(2013, 1, 13) ) self.assertEqual( parser_.parse("January 21st, 2013", "MMMM Do, YYYY"), datetime(2013, 1, 21) ) self.assertEqual( parser_.parse("January 31st, 2013", "MMMM Do, YYYY"), datetime(2013, 1, 31) ) with self.assertRaises(ParserError): parser_.parse("January 1th, 2013", "MMMM Do, YYYY") with self.assertRaises(ParserError): parser_.parse("January 11st, 2013", "MMMM Do, YYYY") def test_italian(self): parser_ = parser.DateTimeParser("it_it") self.assertEqual( parser_.parse("Gennaio 1º, 2013", "MMMM Do, YYYY"), datetime(2013, 1, 1) ) def test_spanish(self): parser_ = parser.DateTimeParser("es_es") self.assertEqual( parser_.parse("Enero 1º, 2013", "MMMM Do, YYYY"), datetime(2013, 1, 1) ) def test_french(self): parser_ = parser.DateTimeParser("fr_fr") self.assertEqual( parser_.parse("Janvier 1er, 2013", "MMMM Do, YYYY"), datetime(2013, 1, 1) ) self.assertEqual( parser_.parse("Janvier 2e, 2013", "MMMM Do, YYYY"), datetime(2013, 1, 2) ) self.assertEqual( parser_.parse("Janvier 11e, 2013", "MMMM Do, YYYY"), datetime(2013, 1, 11) ) class DateTimeParserSearchDateTests(Chai): def setUp(self): super(DateTimeParserSearchDateTests, self).setUp() self.parser = parser.DateTimeParser() def test_parse_search(self): self.assertEqual( 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): self.assertEqual( 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), ) self.assertEqual( 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): self.assertEqual( 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") self.assertEqual( p.parse("Jan föddes den 31 Dec 1980", "DD MMM YYYY"), datetime(1980, 12, 31) ) self.assertEqual( p.parse("Jag föddes den 25 Augusti 1975", "DD MMMM YYYY"), datetime(1975, 8, 25), ) def test_parse_search_fails(self): with self.assertRaises(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" self.assertEqual( 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" self.assertEqual( 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]" self.assertEqual( 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]" self.assertEqual( 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!]" self.assertEqual( self.parser.parse("I'm entirely escaped, weee!", format), datetime(1, 1, 1) ) # Special RegEx characters format = "MMM DD, YYYY |^${}().*+?<>-& h:mm A" self.assertEqual( self.parser.parse("Dec 31, 2017 |^${}().*+?<>-& 2:00 AM", format), datetime(2017, 12, 31, 2, 0), ) arrow-0.15.5/tests/util_tests.py0000664000175000017500000000226113603422516017116 0ustar chrischris00000000000000# -*- coding: utf-8 -*- import time from datetime import datetime from chai import Chai from arrow import util class UtilTests(Chai): def test_total_seconds(self): td = datetime(2019, 1, 1) - datetime(2018, 1, 1) self.assertEqual(util.total_seconds(td), td.total_seconds()) def test_is_timestamp(self): timestamp_float = time.time() timestamp_int = int(timestamp_float) self.assertTrue(util.is_timestamp(timestamp_int)) self.assertTrue(util.is_timestamp(timestamp_float)) self.assertTrue(util.is_timestamp(str(timestamp_int))) self.assertTrue(util.is_timestamp(str(timestamp_float))) self.assertFalse(util.is_timestamp(True)) self.assertFalse(util.is_timestamp(False)) class InvalidTimestamp: pass self.assertFalse(util.is_timestamp(InvalidTimestamp())) full_datetime = "2019-06-23T13:12:42" self.assertFalse(util.is_timestamp(full_datetime)) def test_iso_gregorian(self): with self.assertRaises(ValueError): util.iso_to_gregorian(2013, 0, 5) with self.assertRaises(ValueError): util.iso_to_gregorian(2013, 8, 0) arrow-0.15.5/tests/utils.py0000664000175000017500000000040013577744544016072 0ustar chrischris00000000000000# -*- coding: utf-8 -*- import 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)