mirror of
https://github.com/kennethreitz/maya.git
synced 2026-06-05 23:00:18 +00:00
Compare commits
168 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9766619d00 | |||
| bcc4885f99 | |||
| 564fc5a49e | |||
| 722eb258b0 | |||
| 66ec91e6ce | |||
| 46ab6ac552 | |||
| cd4b4e7e8b | |||
| cde6097142 | |||
| d9cd563d1b | |||
| 87776b19ae | |||
| 6da33e920d | |||
| b0b5bb7ff6 | |||
| 2a69de0a47 | |||
| b127699de3 | |||
| f3f95a92ee | |||
| bf16792875 | |||
| be42580883 | |||
| 03540c48c1 | |||
| 2845e5dee0 | |||
| 882a6aead7 | |||
| c0bf037d2e | |||
| b12a8dad11 | |||
| 0a7061c125 | |||
| d0d3b0136e | |||
| ff2a417198 | |||
| 669ef888e8 | |||
| 11c0d683d7 | |||
| 8493dd7e84 | |||
| d900ed2d2c | |||
| 42eb6fb387 | |||
| be1bea2dcf | |||
| a4432eef31 | |||
| 260f0d54eb | |||
| 70af3c8054 | |||
| 1f12c2c6e4 | |||
| c5dae59909 | |||
| 774b141d91 | |||
| e3bc3516be | |||
| 08c0fb5071 | |||
| 7ddf2db33c | |||
| 27ee872a7f | |||
| 4e58e6455e | |||
| 42cc61e7a8 | |||
| 9abd618083 | |||
| 2c2a8d03c8 | |||
| 3220a7fc32 | |||
| d95ed97e73 | |||
| bdf2bb332b | |||
| 6522525d7c | |||
| 3a83f10988 | |||
| 05eb0d0051 | |||
| 61eabc54e0 | |||
| 6bb380e7c5 | |||
| cc698817b4 | |||
| 9964e9305e | |||
| d87236a30d | |||
| d44a1dd2f6 | |||
| ed5d72b34c | |||
| 9c1e94f956 | |||
| 6755968f69 | |||
| 3760af2c41 | |||
| df09625e74 | |||
| 2d848bd62a | |||
| a99cddf76f | |||
| 1b432d6626 | |||
| f09d6eec63 | |||
| 62a6283200 | |||
| 9ef43a29d9 | |||
| a2bff42439 | |||
| 5525beda31 | |||
| eeb07d46db | |||
| 4056d1a9aa | |||
| 7a750a1cff | |||
| bc06315abd | |||
| 7cc767781a | |||
| 8468dd2ead | |||
| 046f005ca7 | |||
| cd0b2300d7 | |||
| d3ddb39d9d | |||
| 3c8fe4478c | |||
| a0983132bb | |||
| 9612d70707 | |||
| 363fae1aaf | |||
| be2cd96132 | |||
| 771a2b6ce2 | |||
| 8e644f655a | |||
| 6903f6eb63 | |||
| ca076ff625 | |||
| e0e33cc29f | |||
| bd34915f96 | |||
| 16690cfceb | |||
| 97af369f05 | |||
| 367dac4e62 | |||
| 59ec275ef6 | |||
| 76f99bc781 | |||
| bf56321545 | |||
| ee602ea56b | |||
| df3bdf231d | |||
| 1a58d4f710 | |||
| d0d5a8f75d | |||
| 00a0ceb3c8 | |||
| ee0fe3b810 | |||
| 0e0815c45a | |||
| 04a82f3078 | |||
| 5f6b5fc66d | |||
| e9592a3146 | |||
| 15a5c9eedb | |||
| 7c5d5871d3 | |||
| b05ca8707c | |||
| b411d5e6d1 | |||
| 5458b0bf15 | |||
| 02f91de523 | |||
| 00d174c9e2 | |||
| f87f705a53 | |||
| 9a850b4212 | |||
| 84a5f700c3 | |||
| 13e9f2d896 | |||
| 0c206ad0db | |||
| aa87723a0c | |||
| d51a6a57ec | |||
| 3cb65f227c | |||
| 0108ebe07e | |||
| a7d89be3c7 | |||
| 99ffb773bb | |||
| b3293f898e | |||
| 2d8015a13b | |||
| 9066ee862d | |||
| 5f362a8968 | |||
| 565fdd6a9e | |||
| 24063b5e1b | |||
| b57f4a6775 | |||
| 91e7f499e2 | |||
| be3a349b16 | |||
| f69a93b110 | |||
| 86586e733c | |||
| cd7b5b4aae | |||
| b70884a1ec | |||
| 773cdecab5 | |||
| ca865cd840 | |||
| 5cf40b2d2e | |||
| 93152fa7f4 | |||
| baa0660a9b | |||
| fd62815ce5 | |||
| c0092e74ae | |||
| 79d017fdcf | |||
| 7331d0d855 | |||
| 9c90c0534a | |||
| b9c501b0e4 | |||
| 3a71ff0174 | |||
| 43cc1d5946 | |||
| aecb643beb | |||
| fa966900e1 | |||
| 892e589ef2 | |||
| 0f795738c0 | |||
| b333489081 | |||
| 17a450eb5d | |||
| 8cd2158567 | |||
| 883b1b9b92 | |||
| 414df5f3f5 | |||
| 1694ed7cf8 | |||
| cbe9f6bae7 | |||
| 8bdd5c65fa | |||
| 4d96d06d70 | |||
| bbdb9b8762 | |||
| 28ecad81bd | |||
| c56c552184 | |||
| d5e4853886 | |||
| 28b3a849a9 |
+12
@@ -0,0 +1,12 @@
|
||||
[run]
|
||||
branch = True
|
||||
source =
|
||||
maya
|
||||
|
||||
[paths]
|
||||
source =
|
||||
src
|
||||
.tox/*/site-packages
|
||||
|
||||
[report]
|
||||
show_missing = True
|
||||
@@ -0,0 +1,46 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at tuxtimo@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
@@ -0,0 +1,45 @@
|
||||
# Contributing
|
||||
|
||||
Thank you for helping maya to get a better piece of software.
|
||||
|
||||
## Support
|
||||
|
||||
If you have any questions regarding the usage of maya please use the Question Issue Template or ask on [StackOverflow](https://stackoverflow.com).
|
||||
|
||||
## Reporting Issues / Proposing Features
|
||||
|
||||
Before you submit an Issue or proposing a Feature check the existing Issues in order to avoid duplicates. <br>
|
||||
Please make sure you provide enough information to work on your submitted Issue or proposed Feature:
|
||||
|
||||
* Which version of maya are you using?
|
||||
* Which version of python are you using?
|
||||
* On which platform are you running maya?
|
||||
|
||||
Make sure to use the GitHub Template when reporting an issue.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
We are very happy to receive Pull Requests considering:
|
||||
|
||||
* Style Guide. Follow the rules of [PEP8](http://legacy.python.org/dev/peps/pep-0008/), but you may ignore *too-long-lines* and similar warnings. There is a *pylintrc* file for more information.
|
||||
* Tests. If our change affects python code inside the source code directory, please make sure your code is covered by an automated test case.
|
||||
|
||||
### Testing
|
||||
|
||||
To test the maya source code against all supported python versions you should use *tox*:
|
||||
|
||||
```bash
|
||||
cd ~/work/maya
|
||||
pip install tox
|
||||
tox
|
||||
```
|
||||
|
||||
However, if you want to test your code on certain circumstances you can create a *virtualenv*:
|
||||
|
||||
```
|
||||
cd ~/work/maya
|
||||
virtualenv env
|
||||
source env/bin/activate
|
||||
pip install -e '.[dev]'
|
||||
commands = coverage run --parallel -m pytest -s --failed-first
|
||||
```
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Create a bug report to help us improve maya
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Important notices**
|
||||
Before you add a new report, we ask you kindly to acknowledge the following:
|
||||
|
||||
[-] I have read the contributing guide lines at https://github.com/timofurrer/maya/blob/master/.github/CONTRIBUTING.md
|
||||
|
||||
[-] I have read and respect the code of conduct at https://github.com/timofurrer/maya/blob/master/.github/CODE_OF_CONDUCT.md
|
||||
|
||||
[-] I have searched the existing issues and I'm convinced that mine is new.
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Environment and Version**
|
||||
* OS (incl. terminal and shell used): ...
|
||||
* Python Version: ...
|
||||
* maya Version: ...
|
||||
* Your timezone: ...
|
||||
|
||||
**To Reproduce**
|
||||
A clear and concise description of steps to reproduce the behavior
|
||||
you are experiencing.
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Relevant log files**
|
||||
If applicable, information from log files supporting your claim.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest an idea for a new feature or enhancement for maya
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Important notices**
|
||||
Before you add a new request, we ask you kindly to acknowledge the following:
|
||||
|
||||
[-] I have read the contributing guide lines at https://github.com/timofurrer/maya/blob/master/.github/CONTRIBUTING.md
|
||||
|
||||
[-] I have read and respect the code of conduct at https://github.com/timofurrer/maya/blob/master/.github/CODE_OF_CONDUCT.md
|
||||
|
||||
[-] I have searched the existing issues and I'm convinced that mine is new.
|
||||
|
||||
**Is your Feature Request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the Feature Request here.
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Ask a Question
|
||||
about: Ask a Question about maya (Usage, Development, ...)
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Important notices**
|
||||
Before you add a new report, we ask you kindly to acknowledge the following:
|
||||
|
||||
[-] I have read the contributing guide lines at https://github.com/timofurrer/maya/blob/master/.github/CONTRIBUTING.md
|
||||
|
||||
[-] I have read and respect the code of conduct at https://github.com/timofurrer/maya/blob/master/.github/CODE_OF_CONDUCT.md
|
||||
|
||||
[-] I have searched the existing issues and I'm convinced that mine is new.
|
||||
|
||||
**Ask your Question**
|
||||
Ask your question here! If the question is related to a particular environment or behavior please make sure to add some context.
|
||||
@@ -0,0 +1,79 @@
|
||||
name: Continuous Integration and Deployment
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"]
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4.2.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Setup build and test environment
|
||||
run: |
|
||||
python -m pip install --upgrade pip setuptools wheel
|
||||
- name: Build Python Package
|
||||
run: |
|
||||
python -m pip install ".[tests]"
|
||||
- name: Lint with flake8
|
||||
run: |
|
||||
pip install flake8
|
||||
flake8 --show-source src/ tests/
|
||||
- name: Check Manifest
|
||||
run: |
|
||||
pip install check-manifest
|
||||
check-manifest
|
||||
- name: Test with pytest
|
||||
run: |
|
||||
coverage run --parallel -m pytest
|
||||
- name: Report code coverage
|
||||
run: |
|
||||
coverage combine
|
||||
coverage report
|
||||
coverage xml
|
||||
|
||||
docs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python 3.7
|
||||
uses: actions/setup-python@v4.2.0
|
||||
with:
|
||||
python-version: 3.7
|
||||
- name: Setup docs environment
|
||||
run: |
|
||||
python -m pip install ".[docs]"
|
||||
- name: Build documentation with sphinx
|
||||
run: |
|
||||
sphinx-build -W -b html -d doctrees docs/source docs/_build/html
|
||||
|
||||
publish:
|
||||
needs: [build, docs]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
if: startsWith(github.event.ref, 'refs/tags') && github.ref == 'refs/heads/master'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python 3.7
|
||||
uses: actions/setup-python@v4.2.0
|
||||
with:
|
||||
python-version: 3.7
|
||||
- name: Build Package
|
||||
run: |
|
||||
python -m pip install --upgrade pip setuptools wheel
|
||||
python setup.py sdist bdist_wheel --universal
|
||||
- name: Publish Package on PyPI
|
||||
uses: pypa/gh-action-pypi-publish@master
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.pypi_token }}
|
||||
@@ -24,6 +24,7 @@ wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
Pipfile.lock
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
repos:
|
||||
- repo: https://github.com/ambv/black
|
||||
rev: 19.3b0
|
||||
hooks:
|
||||
- id: black
|
||||
language_version: python3.7
|
||||
# override until resolved: https://github.com/ambv/black/issues/402
|
||||
files: \.pyi?$
|
||||
types: []
|
||||
|
||||
- repo: https://gitlab.com/pycqa/flake8
|
||||
rev: 3.7.7
|
||||
hooks:
|
||||
- id: flake8
|
||||
language_version: python3.7
|
||||
|
||||
#- repo: https://github.com/asottile/seed-isort-config
|
||||
#rev: v1.9.1
|
||||
#hooks:
|
||||
#- id: seed-isort-config
|
||||
|
||||
#- repo: https://github.com/pre-commit/mirrors-isort
|
||||
#rev: v4.3.20
|
||||
#hooks:
|
||||
#- id: isort
|
||||
#language_version: python3.7
|
||||
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v2.2.3
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
@@ -1,9 +0,0 @@
|
||||
language: python
|
||||
python:
|
||||
- "2.7"
|
||||
- "3.6"
|
||||
|
||||
# command to install dependencies
|
||||
install: pip install pipenv; pipenv lock; pipenv install --dev
|
||||
# command to run tests
|
||||
script: pipenv run pytest tests/
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
Contributions to the maya project
|
||||
=================================
|
||||
|
||||
Creator & Maintainer
|
||||
--------------------
|
||||
|
||||
- Kenneth Reitz <me@kennethreitz.org> `@kennethreitz <https://github.com/kennethreitz>`_
|
||||
|
||||
|
||||
Contributors
|
||||
------------
|
||||
|
||||
In chronological order:
|
||||
|
||||
- Adam Nelson <adam@varud.com> (`@adamn <https://github.com/adamn>`_)
|
||||
- Timo Furrer <tuxtimo@gmail.com> (`@timofurrer <https://github.com/timofurrer>`_)
|
||||
- Moinuddin Quadri <moin18@gmail.com> (`@moin18 <https://github.com/moin18>`_)
|
||||
- Grigouze <grigouze@yahoo.fr> (`@grigouze <https://github.com/grigouze>`_)
|
||||
- Tzu-ping Chung <uranusjr@gmail.com> (`@uranusjr <https://github.com/uranusjr>`_)
|
||||
- aaronjeline (`@aaronjeline <https://github.com/aaronjeline>`_)
|
||||
- jerry2yu (`@jerry2yu <https://github.com/jerry2yu>`_)
|
||||
- Joshua Li <joshua.r.li.98@gmail.com> (`@JoshuaRLi <https://github.com/JoshuaRLi>`_)
|
||||
- Sébastien Eustace <sebastien@eustace.io> (`@sdispater <https://github.com/sdispater>`_)
|
||||
- Evan Mattiza <emattiza@gmail.com> (`@emattiza <https://github.com/emattiza>`_)
|
||||
+507
@@ -0,0 +1,507 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Maya: Datetimes for Humans™"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Datetimes are very frustrating to work with in Python, especially when dealing with different locales on different systems. This library exists to make the simple things much easier, while admitting that time is an illusion (timezones doubly so).\n",
|
||||
"\n",
|
||||
"Datetimes should be interacted with via an API written for humans.\n",
|
||||
"\n",
|
||||
"Maya is mostly built around the headaches and use-cases around parsing datetime data from websites."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Basic Usage of Maya"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 33,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import maya"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 34,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<MayaDT epoch=1544809514.491009>"
|
||||
]
|
||||
},
|
||||
"execution_count": 34,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"maya.now()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 35,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<MayaDT epoch=1544895914.872356>"
|
||||
]
|
||||
},
|
||||
"execution_count": 35,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"tomorrow = maya.when('tomorrow')\n",
|
||||
"tomorrow"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 36,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'tomorrow'"
|
||||
]
|
||||
},
|
||||
"execution_count": 36,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"tomorrow.slang_date()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 37,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'in 23 hours'"
|
||||
]
|
||||
},
|
||||
"execution_count": 37,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"tomorrow.slang_time()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 38,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'2018-12-15T17:45:14.872356Z'"
|
||||
]
|
||||
},
|
||||
"execution_count": 38,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Also: MayaDT.from_iso8601(...)\n",
|
||||
"tomorrow.iso8601()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 39,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'Sat, 15 Dec 2018 17:45:14 GMT'"
|
||||
]
|
||||
},
|
||||
"execution_count": 39,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Also: MayaDT.from_rfc2822(...)\n",
|
||||
"tomorrow.rfc2822()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 40,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'2018-12-15T17:45:14.8Z'"
|
||||
]
|
||||
},
|
||||
"execution_count": 40,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Also: MayaDT.from_rfc3339(...)\n",
|
||||
"tomorrow.rfc3339()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 41,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"datetime.datetime(2018, 12, 15, 17, 45, 14, 872356, tzinfo=<UTC>)"
|
||||
]
|
||||
},
|
||||
"execution_count": 41,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"tomorrow.datetime()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 42,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"datetime.datetime(2016, 12, 16, 13, 23, 45, 423992)"
|
||||
]
|
||||
},
|
||||
"execution_count": 42,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Automatically parse datetime strings and generate naive datetimes.\n",
|
||||
"scraped = '2016-12-16 18:23:45.423992+00:00'\n",
|
||||
"maya.parse(scraped).datetime(to_timezone='US/Eastern', naive=True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 43,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"rand_day = maya.when('2011-02-07', timezone='US/Eastern')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 44,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<MayaDT epoch=1544809518.275386>"
|
||||
]
|
||||
},
|
||||
"execution_count": 44,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Maya speaks Python.\n",
|
||||
"from datetime import datetime\n",
|
||||
"\n",
|
||||
"maya.MayaDT.from_datetime(datetime.utcnow())"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 45,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<MayaDT epoch=1544809518.0>"
|
||||
]
|
||||
},
|
||||
"execution_count": 45,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import time\n",
|
||||
"\n",
|
||||
"maya.MayaDT.from_struct(time.gmtime())"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 46,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<MayaDT epoch=1544809520.426774>"
|
||||
]
|
||||
},
|
||||
"execution_count": 46,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"maya.MayaDT(time.time())"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 47,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"7"
|
||||
]
|
||||
},
|
||||
"execution_count": 47,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"rand_day.day"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 48,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"17"
|
||||
]
|
||||
},
|
||||
"execution_count": 48,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"rand_day.add(days=10).day"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 49,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'UTC'"
|
||||
]
|
||||
},
|
||||
"execution_count": 49,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Always.\n",
|
||||
"rand_day.timezone"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 50,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<generator object intervals at 0x10c54a308>"
|
||||
]
|
||||
},
|
||||
"execution_count": 50,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Range of hours in a day:\n",
|
||||
"maya.intervals(start=maya.now(), end=maya.now().add(days=1), interval=60*60)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 51,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'Mon, 21 Feb 1994 03:00:00 GMT'"
|
||||
]
|
||||
},
|
||||
"execution_count": 51,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# snap modifiers\n",
|
||||
"dt = maya.when('Mon, 21 Feb 1994 21:21:42 GMT')\n",
|
||||
"dt.snap('@d+3h').rfc2822()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Advanced Usage of Maya"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"In addition to timestamps, Maya also includes a wonderfully powerful MayaInterval class, which represents a range of time (e.g. an event). With this class, you can perform a multitude of advanced calendar calculations with finesse and ease.\n",
|
||||
"\n",
|
||||
"For example:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 52,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from maya import MayaInterval"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 53,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Create an event that is one hour long, starting now.\n",
|
||||
"event_start = maya.now()\n",
|
||||
"event_end = event_start.add(hours=1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 54,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<MayaInterval start=<MayaDT epoch=1544809524.395196> end=<MayaDT epoch=1544813124.395196>>"
|
||||
]
|
||||
},
|
||||
"execution_count": 54,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"MayaInterval(start=event_start, end=event_end)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"From here, there are a number of methods available to you, which you can use to compare this event to another event."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Do your own experiments here...\n",
|
||||
"\n",
|
||||
"Try `maya` youself by adding your code below and running your own experiments 👇"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import maya\n",
|
||||
"\n",
|
||||
"# your code here\n",
|
||||
"maya."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.6.5"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
# Additional package data
|
||||
|
||||
# Metadata
|
||||
include LICENSE *.md *.rst *.toml *.yml *.yaml
|
||||
graft .github
|
||||
|
||||
# Jupyter Notebooks
|
||||
include *.ipynb
|
||||
|
||||
# Stubs
|
||||
recursive-include src *.pyi
|
||||
|
||||
# Tests
|
||||
include tox.ini .coveragerc conftest.py
|
||||
recursive-include tests *.py
|
||||
|
||||
# Documentation
|
||||
include docs/Makefile docs/docutils.conf
|
||||
recursive-include docs *.bat
|
||||
recursive-include docs *.png
|
||||
recursive-include docs *.svg
|
||||
recursive-include docs *.py
|
||||
recursive-include docs *.rst
|
||||
recursive-include docs *.ico
|
||||
prune docs/_build
|
||||
|
||||
# Just to keep check-manifest happy; on releases those files are gone.
|
||||
# Last rule wins!
|
||||
exclude changelog.d/*.rst
|
||||
include changelog.d/towncrier_template.rst
|
||||
@@ -1,11 +0,0 @@
|
||||
[dev-packages]
|
||||
pytest = "*"
|
||||
Sphinx = "*"
|
||||
|
||||
[packages]
|
||||
humanize = "*"
|
||||
pytz = "*"
|
||||
dateparser = "*"
|
||||
"ruamel.yaml" = "*"
|
||||
tzlocal = "*"
|
||||
pendulum = ">=1.0"
|
||||
Generated
-72
@@ -1,72 +0,0 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "5617ff73ba51e60721267b24dc01e83f33d2a3692870b60e394b0f75ed2dc313"
|
||||
},
|
||||
"requires": {},
|
||||
"sources": [
|
||||
{
|
||||
"url": "https://pypi.python.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"dateparser": {
|
||||
"version": "==0.6.0"
|
||||
},
|
||||
"humanize": {
|
||||
"version": "==0.5.1"
|
||||
},
|
||||
"pendulum": {
|
||||
"version": "==1.2.0"
|
||||
},
|
||||
"python-dateutil": {
|
||||
"version": "==2.6.0"
|
||||
},
|
||||
"pytz": {
|
||||
"version": "==2017.2"
|
||||
},
|
||||
"pytzdata": {
|
||||
"version": "==2017.2"
|
||||
},
|
||||
"regex": {
|
||||
"version": "==2017.04.29"
|
||||
},
|
||||
"ruamel.ordereddict": {
|
||||
"version": "==0.4.9"
|
||||
},
|
||||
"ruamel.yaml": {
|
||||
"version": "==0.14.12"
|
||||
},
|
||||
"six": {
|
||||
"version": "==1.10.0"
|
||||
},
|
||||
"tzlocal": {
|
||||
"version": "==1.4"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"appdirs": {
|
||||
"version": "==1.4.3"
|
||||
},
|
||||
"packaging": {
|
||||
"version": "==16.8"
|
||||
},
|
||||
"py": {
|
||||
"version": "==1.4.33"
|
||||
},
|
||||
"pyparsing": {
|
||||
"version": "==2.2.0"
|
||||
},
|
||||
"pytest": {
|
||||
"version": "==3.0.7"
|
||||
},
|
||||
"setuptools": {
|
||||
"version": "==35.0.2"
|
||||
},
|
||||
"six": {
|
||||
"version": "==1.10.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
-27
@@ -4,13 +4,9 @@ Maya: Datetimes for Humans™
|
||||
.. image:: https://img.shields.io/pypi/v/maya.svg
|
||||
:target: https://pypi.python.org/pypi/maya
|
||||
|
||||
.. image:: https://travis-ci.org/kennethreitz/maya.svg?branch=master
|
||||
:target: https://travis-ci.org/kennethreitz/maya
|
||||
.. image:: https://github.com/timofurrer/maya/workflows/Continuous%20Integration%20and%20Deployment/badge.svg
|
||||
:target: https://github.com/timofurrer/maya/actions
|
||||
|
||||
.. image:: https://img.shields.io/badge/SayThanks-!-1EAEDB.svg
|
||||
:target: https://saythanks.io/to/kennethreitz
|
||||
|
||||
test
|
||||
|
||||
Datetimes are very frustrating to work with in Python, especially when dealing
|
||||
with different locales on different systems. This library exists to make the
|
||||
@@ -64,6 +60,19 @@ Behold, datetimes for humans!
|
||||
>>> rand_day = maya.when('2011-02-07', timezone='US/Eastern')
|
||||
<MayaDT epoch=1297036800.0>
|
||||
|
||||
# Maya speaks Python.
|
||||
>>> m = maya.MayaDT.from_datetime(datetime.utcnow())
|
||||
>>> print(m)
|
||||
Wed, 20 Sep 2017 17:24:32 GMT
|
||||
|
||||
>>> m = maya.MayaDT.from_struct(time.gmtime())
|
||||
>>> print(m)
|
||||
Wed, 20 Sep 2017 17:24:32 GMT
|
||||
|
||||
>>> m = maya.MayaDT(time.time())
|
||||
>>> print(m)
|
||||
Wed, 20 Sep 2017 17:24:32 GMT
|
||||
|
||||
>>> rand_day.day
|
||||
7
|
||||
|
||||
@@ -75,13 +84,23 @@ Behold, datetimes for humans!
|
||||
UTC
|
||||
|
||||
# Range of hours in a day:
|
||||
>>> maya.interval(start=maya.now(), end=maya.now().add(days=1), interval=60*60)
|
||||
>>> maya.intervals(start=maya.now(), end=maya.now().add(days=1), interval=60*60)
|
||||
<generator object intervals at 0x105ba5820>
|
||||
|
||||
# snap modifiers
|
||||
>>> dt = maya.when('Mon, 21 Feb 1994 21:21:42 GMT')
|
||||
>>> dt.snap('@d+3h').rfc2822()
|
||||
'Mon, 21 Feb 1994 03:00:00 GMT'
|
||||
|
||||
# snap modifiers within a timezone
|
||||
>>> dt = maya.when('Mon, 21 Feb 1994 21:21:42 GMT')
|
||||
>>> dt.snap_tz('+3h@d', 'Australia/Perth').rfc2822()
|
||||
'Mon, 21 Feb 1994 16:00:00 GMT'
|
||||
|
||||
☤ Advanced Usage of Maya
|
||||
------------------------
|
||||
|
||||
In addition to timestamps, Maya also includes a wonderfuly powerful ``MayaInterval`` class, which represents a range of time (e.g. an event). With this class, you can perform a multitude of advanced calendar calculations with finese and ease.
|
||||
In addition to timestamps, Maya also includes a wonderfully powerful ``MayaInterval`` class, which represents a range of time (e.g. an event). With this class, you can perform a multitude of advanced calendar calculations with finesse and ease.
|
||||
|
||||
For example:
|
||||
|
||||
@@ -89,15 +108,15 @@ For example:
|
||||
|
||||
>>> from maya import MayaInterval
|
||||
|
||||
# Create an event that is one hour long, starting now.
|
||||
# Create an event that is one hour long, starting now.
|
||||
>>> event_start = maya.now()
|
||||
>>> event_end = event_start.add(hours=1)
|
||||
|
||||
|
||||
>>> event = MayaInterval(start=event_start, end=event_end)
|
||||
|
||||
From here, there a a number of methods available to you, which you can use to compare this event to another event.
|
||||
|
||||
|
||||
From here, there are a number of methods available to you, which you can use to compare this event to another event.
|
||||
|
||||
|
||||
|
||||
☤ Why is this useful?
|
||||
---------------------
|
||||
@@ -111,10 +130,10 @@ From here, there a a number of methods available to you, which you can use to co
|
||||
- Maya never panics, and always carries a towel.
|
||||
|
||||
|
||||
☤ What about Delorean, Arrow, & Pendulum?
|
||||
-----------------------------------------
|
||||
☤ What about Delorean_, Arrow_, & Pendulum_?
|
||||
--------------------------------------------
|
||||
|
||||
All these project complement eachother, and are friends. Pendulum, for example, helps power Maya's parsing.
|
||||
All these projects complement each other, and are friends. Pendulum, for example, helps power Maya's parsing.
|
||||
|
||||
Arrow, for example, is a fantastic library, but isn't what I wanted in a datetime library. In many ways, it's better than Maya for certain things. In some ways, in my opinion, it's not.
|
||||
|
||||
@@ -122,21 +141,18 @@ I simply desire a sane API for datetimes that made sense to me for all the thing
|
||||
|
||||
I think these projects complement each-other, personally. Maya is great for parsing websites, and dealing with calendar events!
|
||||
|
||||
.. _Delorean: https://delorean.readthedocs.io/
|
||||
.. _Arrow: https://arrow.readthedocs.io/
|
||||
.. _Pendulum: https://pendulum.eustace.io/
|
||||
|
||||
|
||||
☤ Installing Maya
|
||||
-----------------
|
||||
|
||||
Installation is easy, with pip::
|
||||
Installation is easy, with:
|
||||
|
||||
$ pip install maya
|
||||
|
||||
✨🍰✨
|
||||
|
||||
☤ Like it?
|
||||
----------
|
||||
|
||||
`Say Thanks <https://saythanks.io/to/kennethreitz>`_!
|
||||
|
||||
|
||||
How to Contribute
|
||||
-----------------
|
||||
@@ -144,7 +160,6 @@ How to Contribute
|
||||
#. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug.
|
||||
#. Fork `the repository`_ on GitHub to start making your changes to the **master** branch (or branch off of it).
|
||||
#. Write a test which shows that the bug was fixed or that the feature works as expected.
|
||||
#. Send a pull request and bug the maintainer until it gets merged and published. :) Make sure to add yourself to AUTHORS_.
|
||||
#. Send a pull request and bug the maintainer until it gets merged and published. :)
|
||||
|
||||
.. _`the repository`: http://github.com/kennethreitz/maya
|
||||
.. _AUTHORS: https://github.com/kennethreitz/maya/blob/master/AUTHORS.rst
|
||||
.. _`the repository`: http://github.com/timofurrer/maya
|
||||
|
||||
+33
-34
@@ -19,11 +19,12 @@
|
||||
#
|
||||
import os
|
||||
import sys
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
sys.path.insert(0, os.path.abspath('_themes'))
|
||||
|
||||
import maya
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
sys.path.insert(0, os.path.abspath(".."))
|
||||
sys.path.insert(0, os.path.abspath("_themes"))
|
||||
|
||||
import maya # noqa
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
@@ -34,27 +35,29 @@ import maya
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = ['sphinx.ext.autodoc',
|
||||
'sphinx.ext.todo',
|
||||
'sphinx.ext.coverage',
|
||||
'sphinx.ext.viewcode']
|
||||
extensions = [
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.todo",
|
||||
"sphinx.ext.coverage",
|
||||
"sphinx.ext.viewcode",
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
templates_path = ["_templates"]
|
||||
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
#
|
||||
# source_suffix = ['.rst', '.md']
|
||||
source_suffix = '.rst'
|
||||
source_suffix = ".rst"
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
master_doc = "index"
|
||||
|
||||
# General information about the project.
|
||||
project = 'maya'
|
||||
copyright = '2017, Kenneth Reitz'
|
||||
author = 'Kenneth Reitz'
|
||||
project = "maya"
|
||||
copyright = "2017, Kenneth Reitz"
|
||||
author = "Kenneth Reitz"
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
@@ -70,7 +73,7 @@ release = maya.__version__
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = None
|
||||
language = "en"
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
@@ -78,7 +81,7 @@ language = None
|
||||
exclude_patterns = []
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
pygments_style = "sphinx"
|
||||
|
||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||
todo_include_todos = True
|
||||
@@ -89,7 +92,7 @@ todo_include_todos = True
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = 'alabaster'
|
||||
html_theme = "alabaster"
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
@@ -100,13 +103,13 @@ html_theme = 'alabaster'
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
# html_static_path = ["_static"]
|
||||
|
||||
|
||||
# -- Options for HTMLHelp output ------------------------------------------
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'mayadoc'
|
||||
htmlhelp_basename = "mayadoc"
|
||||
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
@@ -115,15 +118,12 @@ latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#
|
||||
# 'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#
|
||||
# 'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#
|
||||
# 'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#
|
||||
# 'figure_align': 'htbp',
|
||||
@@ -133,8 +133,7 @@ latex_elements = {
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'maya.tex', 'maya Documentation',
|
||||
'Kenneth Reitz', 'manual'),
|
||||
(master_doc, "maya.tex", "maya Documentation", "Kenneth Reitz", "manual")
|
||||
]
|
||||
|
||||
|
||||
@@ -142,10 +141,7 @@ latex_documents = [
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'maya', 'maya Documentation',
|
||||
[author], 1)
|
||||
]
|
||||
man_pages = [(master_doc, "maya", "maya Documentation", [author], 1)]
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
@@ -154,10 +150,13 @@ man_pages = [
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'maya', 'maya Documentation',
|
||||
author, 'maya', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
(
|
||||
master_doc,
|
||||
"maya",
|
||||
"maya Documentation",
|
||||
author,
|
||||
"maya",
|
||||
"One line description of project.",
|
||||
"Miscellaneous",
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -80,4 +80,3 @@ Table of Contents
|
||||
|
||||
user/install
|
||||
user/quickstart
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
.. _install:
|
||||
|
||||
Installation
|
||||
=============
|
||||
============
|
||||
|
||||
Pip Install Maya
|
||||
----------------
|
||||
@@ -26,5 +27,3 @@ Once you have a copy of the source, you can embed it in your own Python
|
||||
package, or install it into your site-packages easily::
|
||||
|
||||
$ python setup.py install
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
.. _quickstart:
|
||||
|
||||
Quickstart
|
||||
==========
|
||||
|
||||
@@ -7,12 +8,6 @@ Quickstart
|
||||
Ready for a simple datetime tool? This doc provides some tools to use in your
|
||||
busy workflow.
|
||||
|
||||
First, make sure that Maya is:
|
||||
|
||||
- :ref:`Installed <install>`
|
||||
- :ref:`Up to date <update>`
|
||||
|
||||
|
||||
Parse a Date
|
||||
------------
|
||||
Parsing a date from a string with Maya is 🍰!
|
||||
@@ -33,6 +28,3 @@ Use as follows::
|
||||
>>> recent_win = maya.parse('2016-11-02T20:00PM')
|
||||
>>> old_win = maya.when('October 14, 1908')
|
||||
>>> grandpas_date = maya.when('108 years ago')
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
.. _update:
|
||||
Community Updates
|
||||
=================
|
||||
|
||||
If you'd like to stay up to date on the community and development of Maya,
|
||||
there are several options:
|
||||
|
||||
|
||||
GitHub
|
||||
------
|
||||
|
||||
The best way to track the development of Maya is through
|
||||
`the GitHub repo <https://github.com/kennethreitz/Maya>`_.
|
||||
|
||||
Twitter
|
||||
-------
|
||||
|
||||
The author, Kenneth Reitz, often tweets about new features and releases of Maya.
|
||||
|
||||
Follow `@kennethreitz <https://twitter.com/kennethreitz>`_ for updates.
|
||||
|
||||
|
||||
|
||||
Release and Version History
|
||||
===========================
|
||||
|
||||
.. include:: ../../HISTORY.rst
|
||||
@@ -1,2 +0,0 @@
|
||||
from .core import *
|
||||
from .__version__ import __version__
|
||||
@@ -1 +0,0 @@
|
||||
__version__ = '0.3.2'
|
||||
@@ -1,2 +1,18 @@
|
||||
[bdist_wheel]
|
||||
universal = 1
|
||||
|
||||
[metadata]
|
||||
# ensure LICENSE is included in wheel metadata
|
||||
license_file = LICENSE
|
||||
|
||||
[flake8]
|
||||
max-line-length = 100
|
||||
ignore = E203
|
||||
|
||||
[tool:pytest]
|
||||
testpaths = tests/
|
||||
|
||||
[isort]
|
||||
known_first_party=maya
|
||||
known_third_party=humanize,pytz,dateparser,tzlocal,pendulum,snaptime
|
||||
multi_line_output=3
|
||||
|
||||
@@ -1,70 +1,92 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import sys
|
||||
import codecs
|
||||
import os
|
||||
import re
|
||||
|
||||
from setuptools import setup
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
try:
|
||||
# Python 3
|
||||
from os import dirname
|
||||
except ImportError:
|
||||
# Python 2
|
||||
from os.path import dirname
|
||||
|
||||
here = os.path.abspath(dirname(__file__))
|
||||
|
||||
with codecs.open(os.path.join(here, 'README.rst'), encoding='utf-8') as f:
|
||||
long_description = '\n' + f.read()
|
||||
|
||||
|
||||
if sys.argv[-1] == "publish":
|
||||
os.system("python setup.py sdist bdist_wheel upload")
|
||||
sys.exit()
|
||||
|
||||
required = [
|
||||
'humanize',
|
||||
'pytz',
|
||||
'dateparser',
|
||||
'ruamel.yaml',
|
||||
'tzlocal',
|
||||
'pendulum'
|
||||
#: Holds a list of packages to install with the binary distribution
|
||||
PACKAGES = find_packages(where="src")
|
||||
META_FILE = os.path.abspath("src/maya/__init__.py")
|
||||
KEYWORDS = ["datetime", "timezone", "scrape", "web"]
|
||||
CLASSIFIERS = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Natural Language :: English",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 2",
|
||||
"Programming Language :: Python :: 2.7",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: Implementation",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
]
|
||||
|
||||
packages = [
|
||||
'maya',
|
||||
#: Holds the runtime requirements for the end user
|
||||
INSTALL_REQUIRES = [
|
||||
"humanize",
|
||||
"pytz",
|
||||
"dateparser>=0.7.0",
|
||||
"tzlocal",
|
||||
"pendulum>=2.0.2",
|
||||
"snaptime",
|
||||
]
|
||||
#: Holds runtime requirements and development requirements
|
||||
EXTRAS_REQUIRES = {
|
||||
# extras for contributors
|
||||
"docs": ["sphinx"],
|
||||
"tests": ["freezegun", "coverage", "pytest", "pytest-mock"],
|
||||
}
|
||||
EXTRAS_REQUIRES["dev"] = (
|
||||
EXTRAS_REQUIRES["tests"] + EXTRAS_REQUIRES["docs"] + ["pre-commit"]
|
||||
)
|
||||
|
||||
#: Holds the contents of the README file
|
||||
with codecs.open("README.rst", encoding="utf-8") as readme:
|
||||
__README_CONTENTS__ = readme.read()
|
||||
|
||||
|
||||
def read(metafile):
|
||||
"""
|
||||
Return the contents of the given meta data file assuming UTF-8 encoding.
|
||||
"""
|
||||
with codecs.open(str(metafile), encoding="utf-8") as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def get_meta(meta, metafile):
|
||||
"""
|
||||
Extract __*meta*__ from the given metafile.
|
||||
"""
|
||||
contents = read(metafile)
|
||||
meta_match = re.search(
|
||||
r"^__{meta}__ = ['\"]([^'\"]*)['\"]".format(meta=meta), contents, re.M
|
||||
)
|
||||
if meta_match:
|
||||
return meta_match.group(1)
|
||||
raise RuntimeError("Unable to find __{meta}__ string.".format(meta=meta))
|
||||
|
||||
# About dict to store version and package info
|
||||
about = dict()
|
||||
with open(os.path.join(here, 'maya', '__version__.py'), 'r', 'utf-8') as f:
|
||||
exec(f.read(), about)
|
||||
|
||||
setup(
|
||||
name='maya',
|
||||
version=about['__version__'],
|
||||
description='Datetimes for Humans.',
|
||||
long_description=long_description,
|
||||
author='Kenneth Reitz',
|
||||
author_email='me@kennethreitz.org',
|
||||
url='https://github.com/kennethreitz/maya',
|
||||
packages=packages,
|
||||
install_requires=required,
|
||||
license='MIT',
|
||||
classifiers=(
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Natural Language :: English',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: Implementation',
|
||||
'Programming Language :: Python :: Implementation :: CPython',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules'
|
||||
),
|
||||
name="maya",
|
||||
version=get_meta("version", META_FILE),
|
||||
license=get_meta("license", META_FILE),
|
||||
description=get_meta("description", META_FILE),
|
||||
long_description=__README_CONTENTS__,
|
||||
author=get_meta("author", META_FILE),
|
||||
author_email=get_meta("author_email", META_FILE),
|
||||
maintainer=get_meta("author", META_FILE),
|
||||
maintainer_email=get_meta("author_email", META_FILE),
|
||||
platforms=["Linux", "Windows", "MAC OS X"],
|
||||
url=get_meta("url", META_FILE),
|
||||
download_url=get_meta("download_url", META_FILE),
|
||||
bugtrack_url=get_meta("bugtrack_url", META_FILE),
|
||||
packages=PACKAGES,
|
||||
package_dir={"": "src"},
|
||||
include_package_data=True,
|
||||
install_requires=INSTALL_REQUIRES,
|
||||
extras_require=EXTRAS_REQUIRES,
|
||||
keywords=KEYWORDS,
|
||||
classifiers=CLASSIFIERS,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
__description__ = "Datetimes for Humans™"
|
||||
__license__ = "MIT"
|
||||
__version__ = "0.6.0a1"
|
||||
__author__ = "Timo Furrer"
|
||||
__author_email__ = "tuxtimo@gmail.com"
|
||||
__url__ = "http://github.com/timofurrer/maya"
|
||||
__download_url__ = "https://github.com/timofurrer/maya"
|
||||
__bugtrack_url__ = "https://github.com/timofurrer/maya/issues"
|
||||
|
||||
from .core import * # noqa
|
||||
@@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
maya.compat
|
||||
~~~~~~~~~~~~~~~
|
||||
@@ -12,24 +11,20 @@ import sys
|
||||
# -------
|
||||
# Pythons
|
||||
# -------
|
||||
|
||||
# Syntax sugar.
|
||||
_ver = sys.version_info
|
||||
|
||||
#: Python 2.x?
|
||||
is_py2 = (_ver[0] == 2)
|
||||
|
||||
#: Python 3.x?
|
||||
is_py3 = (_ver[0] == 3)
|
||||
|
||||
# : Python 2.x?
|
||||
is_py2 = _ver[0] == 2
|
||||
# : Python 3.x?
|
||||
is_py3 = _ver[0] == 3
|
||||
# ---------
|
||||
# Specifics
|
||||
# ---------
|
||||
|
||||
if is_py2:
|
||||
cmp = cmp
|
||||
cmp = cmp # noqa
|
||||
|
||||
elif is_py3:
|
||||
|
||||
def cmp(a, b):
|
||||
"""
|
||||
Compare two objects.
|
||||
@@ -38,8 +33,10 @@ elif is_py3:
|
||||
"""
|
||||
if a < b:
|
||||
return -1
|
||||
|
||||
elif a == b:
|
||||
return 0
|
||||
|
||||
else:
|
||||
return 1
|
||||
|
||||
@@ -59,36 +56,42 @@ def comparable(klass):
|
||||
c = self.__cmp__(other)
|
||||
if c is NotImplemented:
|
||||
return c
|
||||
|
||||
return c == 0
|
||||
|
||||
def __ne__(self, other):
|
||||
c = self.__cmp__(other)
|
||||
if c is NotImplemented:
|
||||
return c
|
||||
|
||||
return c != 0
|
||||
|
||||
def __lt__(self, other):
|
||||
c = self.__cmp__(other)
|
||||
if c is NotImplemented:
|
||||
return c
|
||||
|
||||
return c < 0
|
||||
|
||||
def __le__(self, other):
|
||||
c = self.__cmp__(other)
|
||||
if c is NotImplemented:
|
||||
return c
|
||||
|
||||
return c <= 0
|
||||
|
||||
def __gt__(self, other):
|
||||
c = self.__cmp__(other)
|
||||
if c is NotImplemented:
|
||||
return c
|
||||
|
||||
return c > 0
|
||||
|
||||
def __ge__(self, other):
|
||||
c = self.__cmp__(other)
|
||||
if c is NotImplemented:
|
||||
return c
|
||||
|
||||
return c >= 0
|
||||
|
||||
klass.__lt__ = __lt__
|
||||
+336
-151
@@ -1,28 +1,23 @@
|
||||
# ___ __ ___ _ _ ___
|
||||
# || \/ | ||=|| \\// ||=||
|
||||
# || | || || // || ||
|
||||
|
||||
# Ignore warnings for yaml usage.
|
||||
import warnings
|
||||
import ruamel.yaml
|
||||
|
||||
warnings.simplefilter('ignore', ruamel.yaml.error.UnsafeLoaderWarning)
|
||||
|
||||
import email.utils
|
||||
import time
|
||||
import functools
|
||||
from datetime import timedelta, datetime as Datetime
|
||||
|
||||
import functools
|
||||
import re
|
||||
import pytz
|
||||
import humanize
|
||||
import dateparser
|
||||
import pendulum
|
||||
import snaptime
|
||||
from tzlocal import get_localzone
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from dateparser.languages.loader import default_loader
|
||||
|
||||
from .compat import cmp, comparable
|
||||
|
||||
_EPOCH_START = (1970, 1, 1)
|
||||
|
||||
|
||||
def validate_class_type_arguments(operator):
|
||||
"""
|
||||
@@ -34,11 +29,16 @@ def validate_class_type_arguments(operator):
|
||||
def wrapper(self, *args, **kwargs):
|
||||
for arg in args + tuple(kwargs.values()):
|
||||
if not isinstance(arg, self.__class__):
|
||||
raise TypeError('unorderable types: {}() {} {}()'.format(
|
||||
type(self).__name__, operator, type(arg).__name__))
|
||||
raise TypeError(
|
||||
"unorderable types: {}() {} {}()".format(
|
||||
type(self).__name__, operator, type(arg).__name__
|
||||
)
|
||||
)
|
||||
|
||||
return function(self, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
@@ -51,33 +51,37 @@ def validate_arguments_type_of_function(param_type=None):
|
||||
|
||||
Note: Use this decorator on the functions of the class.
|
||||
"""
|
||||
|
||||
def inner(function):
|
||||
def wrapper(self, *args, **kwargs):
|
||||
type_ = param_type or type(self)
|
||||
for arg in args + tuple(kwargs.values()):
|
||||
if not isinstance(arg, type_):
|
||||
raise TypeError(('Invalid Type: {}.{}() accepts only the '
|
||||
'arguments of type "<{}>"').format(
|
||||
type(self).__name__,
|
||||
function.__name__,
|
||||
type_.__name__,
|
||||
)
|
||||
)
|
||||
raise TypeError(
|
||||
(
|
||||
"Invalid Type: {}.{}() accepts only the "
|
||||
'arguments of type "<{}>"'
|
||||
).format(type(self).__name__, function.__name__, type_.__name__)
|
||||
)
|
||||
|
||||
return function(self, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
class MayaDT(object):
|
||||
"""The Maya Datetime object."""
|
||||
|
||||
__EPOCH_START = (1970, 1, 1)
|
||||
|
||||
def __init__(self, epoch):
|
||||
super(MayaDT, self).__init__()
|
||||
self._epoch = epoch
|
||||
|
||||
def __repr__(self):
|
||||
return '<MayaDT epoch={}>'.format(self._epoch)
|
||||
return "<MayaDT epoch={}>".format(self._epoch)
|
||||
|
||||
def __str__(self):
|
||||
return self.rfc2822()
|
||||
@@ -86,27 +90,27 @@ class MayaDT(object):
|
||||
"""Return's the datetime's format"""
|
||||
return format(self.datetime(), *args, **kwargs)
|
||||
|
||||
@validate_class_type_arguments('==')
|
||||
@validate_class_type_arguments("==")
|
||||
def __eq__(self, maya_dt):
|
||||
return int(self._epoch) == int(maya_dt._epoch)
|
||||
|
||||
@validate_class_type_arguments('!=')
|
||||
@validate_class_type_arguments("!=")
|
||||
def __ne__(self, maya_dt):
|
||||
return int(self._epoch) != int(maya_dt._epoch)
|
||||
|
||||
@validate_class_type_arguments('<')
|
||||
@validate_class_type_arguments("<")
|
||||
def __lt__(self, maya_dt):
|
||||
return int(self._epoch) < int(maya_dt._epoch)
|
||||
|
||||
@validate_class_type_arguments('<=')
|
||||
@validate_class_type_arguments("<=")
|
||||
def __le__(self, maya_dt):
|
||||
return int(self._epoch) <= int(maya_dt._epoch)
|
||||
|
||||
@validate_class_type_arguments('>')
|
||||
@validate_class_type_arguments(">")
|
||||
def __gt__(self, maya_dt):
|
||||
return int(self._epoch) > int(maya_dt._epoch)
|
||||
|
||||
@validate_class_type_arguments('>=')
|
||||
@validate_class_type_arguments(">=")
|
||||
def __ge__(self, maya_dt):
|
||||
return int(self._epoch) >= int(maya_dt._epoch)
|
||||
|
||||
@@ -114,30 +118,58 @@ class MayaDT(object):
|
||||
return hash(int(self.epoch))
|
||||
|
||||
def __add__(self, duration):
|
||||
return self.add(seconds=seconds_or_timedelta(duration).total_seconds())
|
||||
return self.add(seconds=_seconds_or_timedelta(duration).total_seconds())
|
||||
|
||||
def __radd__(self, duration):
|
||||
return self + duration
|
||||
|
||||
def __sub__(self, duration):
|
||||
return self.subtract(
|
||||
seconds=seconds_or_timedelta(duration).total_seconds())
|
||||
def __sub__(self, duration_or_date):
|
||||
if isinstance(duration_or_date, MayaDT):
|
||||
return self.subtract_date(dt=duration_or_date)
|
||||
|
||||
else:
|
||||
return self.subtract(
|
||||
seconds=_seconds_or_timedelta(duration_or_date).total_seconds()
|
||||
)
|
||||
|
||||
def add(self, **kwargs):
|
||||
""""Returns a new MayaDT object with the given offsets."""
|
||||
"""Returns a new MayaDT object with the given offsets."""
|
||||
return self.from_datetime(pendulum.instance(self.datetime()).add(**kwargs))
|
||||
|
||||
def subtract(self, **kwargs):
|
||||
""""Returns a new MayaDT object with the given offsets."""
|
||||
"""Returns a new MayaDT object with the given offsets."""
|
||||
return self.from_datetime(pendulum.instance(self.datetime()).subtract(**kwargs))
|
||||
|
||||
def subtract_date(self, **kwargs):
|
||||
"""Returns a timedelta object with the duration between the dates"""
|
||||
return timedelta(seconds=self.epoch - kwargs["dt"].epoch)
|
||||
|
||||
def snap(self, instruction):
|
||||
"""
|
||||
Returns a new MayaDT object modified by the given instruction.
|
||||
|
||||
Powered by snaptime. See https://github.com/zartstrom/snaptime
|
||||
for a complete documentation about the snaptime instructions.
|
||||
"""
|
||||
return self.from_datetime(snaptime.snap(self.datetime(), instruction))
|
||||
|
||||
def snap_tz(self, instruction, in_timezone):
|
||||
"""
|
||||
Returns a new MayaDT object modified by the given instruction.
|
||||
The modifications happen in the given timezone.
|
||||
|
||||
Powered by snaptime. See https://github.com/zartstrom/snaptime
|
||||
for a complete documentation about the snaptime instructions.
|
||||
"""
|
||||
dt_tz = self.datetime(to_timezone=in_timezone)
|
||||
return self.from_datetime(snaptime.snap_tz(dt_tz, instruction, dt_tz.tzinfo))
|
||||
|
||||
# Timezone Crap
|
||||
# -------------
|
||||
|
||||
@property
|
||||
def timezone(self):
|
||||
"""Returns the UTC tzinfo name. It's always UTC. Always."""
|
||||
return 'UTC'
|
||||
return "UTC"
|
||||
|
||||
@property
|
||||
def _tz(self):
|
||||
@@ -146,8 +178,11 @@ class MayaDT(object):
|
||||
|
||||
@property
|
||||
def local_timezone(self):
|
||||
"""Returns the name of the local timezone, for informational purposes."""
|
||||
return self._local_tz.zone
|
||||
"""Returns the name of the local timezone."""
|
||||
if self._local_tz.zone in pytz.all_timezones:
|
||||
return self._local_tz.zone
|
||||
|
||||
return self.timezone
|
||||
|
||||
@property
|
||||
def _local_tz(self):
|
||||
@@ -158,28 +193,46 @@ class MayaDT(object):
|
||||
@validate_arguments_type_of_function(Datetime)
|
||||
def __dt_to_epoch(dt):
|
||||
"""Converts a datetime into an epoch."""
|
||||
|
||||
# Assume UTC if no datetime is provided.
|
||||
if dt.tzinfo is None:
|
||||
dt = dt.replace(tzinfo=pytz.utc)
|
||||
|
||||
epoch_start = Datetime(*_EPOCH_START, tzinfo=pytz.timezone('UTC'))
|
||||
epoch_start = Datetime(*MayaDT.__EPOCH_START, tzinfo=pytz.timezone("UTC"))
|
||||
return (dt - epoch_start).total_seconds()
|
||||
|
||||
# Importers
|
||||
# ---------
|
||||
|
||||
@classmethod
|
||||
@validate_arguments_type_of_function(Datetime)
|
||||
def from_datetime(klass, dt):
|
||||
"""Returns MayaDT instance from datetime."""
|
||||
return klass(klass.__dt_to_epoch(dt))
|
||||
|
||||
@classmethod
|
||||
@validate_arguments_type_of_function(time.struct_time)
|
||||
def from_struct(klass, struct, timezone=pytz.UTC):
|
||||
"""Returns MayaDT instance from a 9-tuple struct
|
||||
|
||||
It's assumed to be from gmtime().
|
||||
"""
|
||||
struct_time = time.mktime(struct) - utc_offset(struct)
|
||||
dt = Datetime.fromtimestamp(struct_time, timezone)
|
||||
return klass(klass.__dt_to_epoch(dt))
|
||||
|
||||
@classmethod
|
||||
def from_iso8601(klass, iso8601_string):
|
||||
"""Returns MayaDT instance from iso8601 string."""
|
||||
return parse(iso8601_string)
|
||||
|
||||
@classmethod
|
||||
def from_long_count(klass, long_count_string):
|
||||
"""Returns MayaDT instance from Maya Long Count string."""
|
||||
days_since_creation = -1856305
|
||||
factors = (144000, 7200, 360, 20, 1)
|
||||
for i, value in enumerate(long_count_string.split('.')):
|
||||
days_since_creation += int(value) * factors[i]
|
||||
days_since_creation *= 3600 * 24
|
||||
return klass(epoch=days_since_creation)
|
||||
|
||||
@staticmethod
|
||||
def from_rfc2822(rfc2822_string):
|
||||
"""Returns MayaDT instance from rfc2822 string."""
|
||||
@@ -192,35 +245,45 @@ class MayaDT(object):
|
||||
|
||||
# Exporters
|
||||
# ---------
|
||||
|
||||
def datetime(self, to_timezone=None, naive=False):
|
||||
"""Returns a timezone-aware datetime...
|
||||
Defaulting to UTC (as it should).
|
||||
|
||||
Keyword Arguments:
|
||||
to_timezone {string} -- timezone to convert to (default: None/UTC)
|
||||
naive {boolean} -- if True, the tzinfo is simply dropped (default: False)
|
||||
to_timezone {str} -- timezone to convert to (default: None/UTC)
|
||||
naive {bool} -- if True,
|
||||
the tzinfo is simply dropped (default: False)
|
||||
"""
|
||||
if to_timezone:
|
||||
dt = self.datetime().astimezone(pytz.timezone(to_timezone))
|
||||
else:
|
||||
dt = Datetime.utcfromtimestamp(self._epoch)
|
||||
try:
|
||||
dt = Datetime.utcfromtimestamp(self._epoch)
|
||||
except: # Fallback for before year 1970 issue
|
||||
dt = Datetime.utcfromtimestamp(0) + timedelta(microseconds=self._epoch*1000000)
|
||||
dt.replace(tzinfo=self._tz)
|
||||
|
||||
# Strip the timezone info if requested to do so.
|
||||
if naive:
|
||||
return dt.replace(tzinfo=None)
|
||||
|
||||
else:
|
||||
if dt.tzinfo is None:
|
||||
dt = dt.replace(tzinfo=self._tz)
|
||||
|
||||
return dt
|
||||
|
||||
def local_datetime(self):
|
||||
"""Returns a local timezone-aware datetime object
|
||||
|
||||
It's the same as:
|
||||
mayaDt.datetime(to_timezone=mayaDt.local_timezone)
|
||||
"""
|
||||
return self.datetime(to_timezone=self.local_timezone, naive=False)
|
||||
|
||||
def iso8601(self):
|
||||
"""Returns an ISO 8601 representation of the MayaDT."""
|
||||
# Get a timezone-naive datetime.
|
||||
dt = self.datetime(naive=True)
|
||||
return '{}Z'.format(dt.isoformat())
|
||||
return "{}Z".format(dt.isoformat())
|
||||
|
||||
def rfc2822(self):
|
||||
"""Returns an RFC 2822 representation of the MayaDT."""
|
||||
@@ -228,11 +291,26 @@ class MayaDT(object):
|
||||
|
||||
def rfc3339(self):
|
||||
"""Returns an RFC 3339 representation of the MayaDT."""
|
||||
return self.datetime().strftime("%Y-%m-%dT%H:%M:%S.%f")[:-4] + "Z"
|
||||
return self.datetime().strftime("%Y-%m-%dT%H:%M:%S.%f")[:-5] + "Z"
|
||||
|
||||
def long_count(self):
|
||||
"""Returns a Mayan Long Count representation of the Maya DT."""
|
||||
# Creation (0.0.0.0.0) occurred on -3114-08-11
|
||||
# 1856305 is distance (in days) between Creation and UNIX epoch
|
||||
days_since_creation = int(1856305 + self._epoch / (3600 * 24))
|
||||
caps = (0, 20, 20, 18, 20)
|
||||
lc_date = [0, 0, 0, 0, days_since_creation]
|
||||
for i in range(4, 0, -1):
|
||||
if lc_date[i] >= caps[i]:
|
||||
lc_date[i - 1] += int(lc_date[i] / caps[i])
|
||||
lc_date[i] %= caps[i]
|
||||
elif lc_date[i] < 0:
|
||||
lc_date[i - 1] += int(lc_date[i] / caps[i])
|
||||
lc_date[i] = 0
|
||||
return '.'.join(str(i) for i in lc_date)
|
||||
|
||||
# Properties
|
||||
# ----------
|
||||
|
||||
@property
|
||||
def year(self):
|
||||
return self.datetime().year
|
||||
@@ -245,13 +323,20 @@ class MayaDT(object):
|
||||
def day(self):
|
||||
return self.datetime().day
|
||||
|
||||
@property
|
||||
def date(self):
|
||||
return self.datetime().date()
|
||||
|
||||
@property
|
||||
def week(self):
|
||||
return self.datetime().isocalendar()[1]
|
||||
|
||||
@property
|
||||
def weekday(self):
|
||||
"""Return the day of the week as an integer. Monday is 1 and Sunday is 7"""
|
||||
"""Return the day of the week as an integer.
|
||||
|
||||
Monday is 1 and Sunday is 7.
|
||||
"""
|
||||
return self.datetime().isoweekday()
|
||||
|
||||
@property
|
||||
@@ -276,98 +361,171 @@ class MayaDT(object):
|
||||
|
||||
# Human Slang Extras
|
||||
# ------------------
|
||||
def slang_date(self, locale="en"):
|
||||
""""Returns human slang representation of date.
|
||||
|
||||
def slang_date(self):
|
||||
""""Returns human slang representation of date."""
|
||||
dt = self.datetime(naive=True, to_timezone=self.local_timezone)
|
||||
return humanize.naturaldate(dt)
|
||||
Keyword Arguments:
|
||||
locale -- locale to translate to, e.g. 'fr' for french.
|
||||
(default: 'en' - English)
|
||||
"""
|
||||
dt = pendulum.instance(self.datetime())
|
||||
|
||||
def slang_time(self):
|
||||
""""Returns human slang representation of time."""
|
||||
dt = self.datetime(naive=True, to_timezone=self.local_timezone)
|
||||
return humanize.naturaltime(dt)
|
||||
try:
|
||||
return _translate(dt, locale)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
delta = humanize.time._abs_timedelta(
|
||||
timedelta(seconds=(self.epoch - now().epoch))
|
||||
)
|
||||
|
||||
format_string = "DD MMM"
|
||||
if delta.days >= 365:
|
||||
format_string += " YYYY"
|
||||
|
||||
return dt.format(format_string, locale=locale).title()
|
||||
|
||||
def slang_time(self, locale="en"):
|
||||
""""Returns human slang representation of time.
|
||||
|
||||
Keyword Arguments:
|
||||
locale -- locale to translate to, e.g. 'fr' for french.
|
||||
(default: 'en' - English)
|
||||
"""
|
||||
dt = self.datetime()
|
||||
return pendulum.instance(dt).diff_for_humans(locale=locale)
|
||||
|
||||
|
||||
def utc_offset(time_struct=None):
|
||||
"""
|
||||
Returns the time offset from UTC accounting for DST
|
||||
|
||||
Keyword Arguments:
|
||||
time_struct {time.struct_time} -- the struct time for which to
|
||||
return the UTC offset.
|
||||
If None, use current local time.
|
||||
"""
|
||||
if time_struct:
|
||||
ts = time_struct
|
||||
else:
|
||||
ts = time.localtime()
|
||||
|
||||
if ts[-1]:
|
||||
offset = time.altzone
|
||||
else:
|
||||
offset = time.timezone
|
||||
return offset
|
||||
|
||||
|
||||
def to_utc_offset_naive(dt):
|
||||
if dt.tzinfo is None:
|
||||
return dt
|
||||
|
||||
return dt.astimezone(pytz.utc).replace(tzinfo=None)
|
||||
|
||||
|
||||
def to_utc_offset_aware(dt):
|
||||
if dt.tzinfo is not None:
|
||||
return dt
|
||||
|
||||
return pytz.utc.localize(dt)
|
||||
|
||||
|
||||
def to_iso8601(dt):
|
||||
return to_utc_offset_naive(dt).isoformat() + 'Z'
|
||||
return to_utc_offset_naive(dt).isoformat() + "Z"
|
||||
|
||||
|
||||
def end_of_day_midnight(dt):
|
||||
return dt if dt.time() == time.min else \
|
||||
(dt.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=1))
|
||||
if dt.time() == time.min:
|
||||
return dt
|
||||
|
||||
else:
|
||||
return dt.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=1)
|
||||
|
||||
|
||||
@comparable
|
||||
class MayaInterval(object):
|
||||
"""
|
||||
A MayaInterval represents a range between two datetimes, inclusive of the start
|
||||
and exclusive of the end.
|
||||
A MayaInterval represents a range between two datetimes,
|
||||
inclusive of the start and exclusive of the end.
|
||||
"""
|
||||
|
||||
def __init__(self, start=None, end=None, duration=None):
|
||||
try:
|
||||
# Ensure that proper arguments were passed.
|
||||
assert any((
|
||||
(start and end),
|
||||
(start and duration is not None),
|
||||
(end and duration is not None),
|
||||
))
|
||||
assert any(
|
||||
(
|
||||
(start and end),
|
||||
(start and duration is not None),
|
||||
(end and duration is not None),
|
||||
)
|
||||
)
|
||||
assert not all((start, end, duration is not None))
|
||||
except AssertionError:
|
||||
raise ValueError(
|
||||
'Exactly 2 of start, end, and duration must be specified')
|
||||
raise ValueError("Exactly 2 of start, end, and duration must be specified")
|
||||
|
||||
# Convert duration to timedelta if seconds were provided.
|
||||
if duration:
|
||||
duration = seconds_or_timedelta(duration)
|
||||
|
||||
duration = _seconds_or_timedelta(duration)
|
||||
if not start:
|
||||
start = end - duration
|
||||
|
||||
if not end:
|
||||
end = start + duration
|
||||
|
||||
if start > end:
|
||||
raise ValueError('MayaInterval cannot end before it starts')
|
||||
raise ValueError("MayaInterval cannot end before it starts")
|
||||
|
||||
self.start = start
|
||||
self.end = end
|
||||
|
||||
def __repr__(self):
|
||||
return '<MayaInterval start={0!r} end={1!r}>'.format(self.start, self.end)
|
||||
return "<MayaInterval start={0!r} end={1!r}>".format(self.start, self.end)
|
||||
|
||||
def iso8601(self):
|
||||
"""Returns an ISO 8601 representation of the MayaInterval."""
|
||||
return '{0}/{1}'.format(self.start.iso6801, self.end.iso8601)
|
||||
return "{0}/{1}".format(self.start.iso8601(), self.end.iso8601())
|
||||
|
||||
@classmethod
|
||||
def parse_iso8601_duration(cls, duration, start=None, end=None):
|
||||
match = re.match(
|
||||
r"(?:P(?P<weeks>\d+)W)|(?:P(?:(?:(?P<years>\d+)Y)?(?:(?P<months>\d+)M)?(?:(?P<days>\d+)D))?(?:T(?:(?P<hours>\d+)H)?(?:(?P<minutes>\d+)M)?(?:(?P<seconds>\d+)S)?)?)", # noqa
|
||||
duration,
|
||||
)
|
||||
|
||||
time_components = {}
|
||||
if match:
|
||||
time_components = match.groupdict(0)
|
||||
for key, value in time_components.items():
|
||||
time_components[key] = int(value)
|
||||
|
||||
duration = relativedelta(**time_components)
|
||||
|
||||
if start:
|
||||
return parse(start.datetime() + duration)
|
||||
|
||||
if end:
|
||||
return parse(end.datetime() - duration)
|
||||
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def from_iso8601(cls, s):
|
||||
# # Start and end, such as "2007-03-01T13:00:00Z/2008-05-11T15:30:00Z"
|
||||
# start, end = s.split('/')
|
||||
# try:
|
||||
# start = parse(start)
|
||||
# except pendulum.parsing.exceptions.ParserError:
|
||||
# start = self._parse_iso8601_duration(start)
|
||||
# try:
|
||||
# end = parse(start)
|
||||
# except pendulum.parsing.exceptions.ParserError as e:
|
||||
# end = self._parse_iso8601_duration(start)
|
||||
start, end = s.split("/")
|
||||
try:
|
||||
start = parse(start)
|
||||
except pendulum.parsing.exceptions.ParserError:
|
||||
# start = self._parse_iso8601_duration(start, end=end)
|
||||
raise NotImplementedError()
|
||||
|
||||
try:
|
||||
end = parse(end)
|
||||
except (pendulum.parsing.exceptions.ParserError, TypeError):
|
||||
end = cls.parse_iso8601_duration(end, start=start)
|
||||
|
||||
return cls(start=start, end=end)
|
||||
|
||||
# # Start and duration, such as "2007-03-01T13:00:00Z/P1Y2M10DT2H30M"
|
||||
# # Duration and end, such as "P1Y2M10DT2H30M/2008-05-11T15:30:00Z"
|
||||
raise NotImplementedError()
|
||||
|
||||
@validate_arguments_type_of_function()
|
||||
def __and__(self, maya_interval):
|
||||
@@ -379,10 +537,7 @@ class MayaInterval(object):
|
||||
|
||||
@validate_arguments_type_of_function()
|
||||
def __eq__(self, maya_interval):
|
||||
return (
|
||||
self.start == maya_interval.start and
|
||||
self.end == maya_interval.end
|
||||
)
|
||||
return self.start == maya_interval.start and self.end == maya_interval.end
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.start, self.end))
|
||||
@@ -393,10 +548,7 @@ class MayaInterval(object):
|
||||
|
||||
@validate_arguments_type_of_function()
|
||||
def __cmp__(self, maya_interval):
|
||||
return (
|
||||
cmp(self.start, maya_interval.start) or
|
||||
cmp(self.end, maya_interval.end)
|
||||
)
|
||||
return cmp(self.start, maya_interval.start) or cmp(self.end, maya_interval.end)
|
||||
|
||||
@property
|
||||
def duration(self):
|
||||
@@ -426,8 +578,9 @@ class MayaInterval(object):
|
||||
MayaInterval(
|
||||
interval_list[0].start,
|
||||
max(interval_list[0].end, interval_list[1].end),
|
||||
),
|
||||
)
|
||||
]
|
||||
|
||||
return interval_list
|
||||
|
||||
@validate_arguments_type_of_function()
|
||||
@@ -435,6 +588,7 @@ class MayaInterval(object):
|
||||
""""Removes the given interval."""
|
||||
if not self & maya_interval:
|
||||
return [self]
|
||||
|
||||
elif maya_interval.contains(self):
|
||||
return []
|
||||
|
||||
@@ -446,49 +600,41 @@ class MayaInterval(object):
|
||||
return interval_list
|
||||
|
||||
def split(self, duration, include_remainder=True):
|
||||
|
||||
# Convert seconds to timedelta, if appropriate.
|
||||
duration = seconds_or_timedelta(duration)
|
||||
|
||||
duration = _seconds_or_timedelta(duration)
|
||||
if duration <= timedelta(seconds=0):
|
||||
raise ValueError('cannot call split with a non-positive timedelta')
|
||||
raise ValueError("cannot call split with a non-positive timedelta")
|
||||
|
||||
start = self.start
|
||||
while start < self.end:
|
||||
if start + duration <= self.end:
|
||||
yield MayaInterval(start, start + duration)
|
||||
|
||||
elif include_remainder:
|
||||
yield MayaInterval(start, self.end)
|
||||
|
||||
start += duration
|
||||
|
||||
def quantize(self, duration, snap_out=False, timezone='UTC'):
|
||||
"""Returns a quanitzed interval."""
|
||||
|
||||
def quantize(self, duration, snap_out=False, timezone="UTC"):
|
||||
"""Returns a quantized interval."""
|
||||
# Convert seconds to timedelta, if appropriate.
|
||||
duration = seconds_or_timedelta(duration)
|
||||
duration = _seconds_or_timedelta(duration)
|
||||
timezone = pytz.timezone(timezone)
|
||||
|
||||
|
||||
if duration <= timedelta(seconds=0):
|
||||
raise ValueError('cannot quantize by non-positive timedelta')
|
||||
raise ValueError("cannot quantize by non-positive timedelta")
|
||||
|
||||
epoch = timezone.localize(Datetime(1970, 1, 1))
|
||||
seconds = int(duration.total_seconds())
|
||||
|
||||
start_seconds = int((self.start.datetime(naive=False) - epoch).total_seconds())
|
||||
end_seconds = int((self.end.datetime(naive=False) - epoch).total_seconds())
|
||||
|
||||
if start_seconds % seconds and not snap_out:
|
||||
start_seconds += seconds
|
||||
if end_seconds % seconds and snap_out:
|
||||
end_seconds += seconds
|
||||
|
||||
start_seconds -= start_seconds % seconds
|
||||
end_seconds -= end_seconds % seconds
|
||||
|
||||
if start_seconds > end_seconds:
|
||||
start_seconds = end_seconds
|
||||
|
||||
return MayaInterval(
|
||||
start=MayaDT.from_datetime(epoch).add(seconds=start_seconds),
|
||||
end=MayaDT.from_datetime(epoch).add(seconds=end_seconds),
|
||||
@@ -497,24 +643,16 @@ class MayaInterval(object):
|
||||
@validate_arguments_type_of_function()
|
||||
def intersection(self, maya_interval):
|
||||
"""Returns the intersection between two intervals."""
|
||||
|
||||
start = max(self.start, maya_interval.start)
|
||||
end = min(self.end, maya_interval.end)
|
||||
|
||||
either_instant = self.is_instant or maya_interval.is_instant
|
||||
instant_overlap = (
|
||||
self.start == maya_interval.start or
|
||||
start <= end
|
||||
)
|
||||
instant_overlap = self.start == maya_interval.start or start <= end
|
||||
if (either_instant and instant_overlap) or (start < end):
|
||||
return MayaInterval(start, end)
|
||||
|
||||
@validate_arguments_type_of_function()
|
||||
def contains(self, maya_interval):
|
||||
return (
|
||||
self.start <= maya_interval.start and
|
||||
self.end >= maya_interval.end
|
||||
)
|
||||
return self.start <= maya_interval.start and self.end >= maya_interval.end
|
||||
|
||||
def __contains__(self, maya_dt):
|
||||
if isinstance(maya_dt, MayaDT):
|
||||
@@ -527,15 +665,13 @@ class MayaInterval(object):
|
||||
|
||||
@validate_arguments_type_of_function()
|
||||
def is_adjacent(self, maya_interval):
|
||||
return (
|
||||
self.start == maya_interval.end or
|
||||
self.end == maya_interval.start
|
||||
)
|
||||
return self.start == maya_interval.end or self.end == maya_interval.start
|
||||
|
||||
@property
|
||||
def icalendar(self):
|
||||
ical_dt_format = '%Y%m%dT%H%M%SZ'
|
||||
return """
|
||||
ical_dt_format = "%Y%m%dT%H%M%SZ"
|
||||
return (
|
||||
"""
|
||||
BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
@@ -544,16 +680,25 @@ class MayaInterval(object):
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
""".format(
|
||||
self.start.datetime().strftime(ical_dt_format),
|
||||
self.end.datetime().strftime(ical_dt_format),
|
||||
).replace(' ', '').strip('\r\n').replace('\n', '\r\n')
|
||||
self.start.datetime().strftime(ical_dt_format),
|
||||
self.end.datetime().strftime(ical_dt_format),
|
||||
)
|
||||
.replace(" ", "")
|
||||
.strip("\r\n")
|
||||
.replace("\n", "\r\n")
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def flatten(interval_list):
|
||||
return functools.reduce(lambda reduced, maya_interval: (
|
||||
(reduced[:-1] + maya_interval.combine(reduced[-1]))
|
||||
if reduced else [maya_interval]
|
||||
), sorted(interval_list), [])
|
||||
return functools.reduce(
|
||||
lambda reduced, maya_interval: (
|
||||
(reduced[:-1] + maya_interval.combine(reduced[-1]))
|
||||
if reduced
|
||||
else [maya_interval]
|
||||
),
|
||||
sorted(interval_list),
|
||||
[],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_datetime(cls, start_dt=None, end_dt=None, duration=None):
|
||||
@@ -568,7 +713,7 @@ def now():
|
||||
return MayaDT(epoch=epoch)
|
||||
|
||||
|
||||
def when(string, timezone='UTC'):
|
||||
def when(string, timezone="UTC", prefer_dates_from="current_period"):
|
||||
""""Returns a MayaDT instance for the human moment specified.
|
||||
|
||||
Powered by dateparser. Useful for scraping websites.
|
||||
@@ -579,31 +724,56 @@ def when(string, timezone='UTC'):
|
||||
Keyword Arguments:
|
||||
string -- string to be parsed
|
||||
timezone -- timezone referenced from (default: 'UTC')
|
||||
prefer_dates_from -- what dates are preferred when `string` is ambiguous.
|
||||
options are 'past', 'future', and 'current_period'
|
||||
(default: 'current_period'). see: [1]
|
||||
|
||||
Reference:
|
||||
[1] dateparser.readthedocs.io/en/latest/usage.html#handling-incomplete-dates
|
||||
"""
|
||||
dt = dateparser.parse(string,
|
||||
settings={'TIMEZONE': timezone, 'RETURN_AS_TIMEZONE_AWARE': True, 'TO_TIMEZONE': 'UTC'})
|
||||
settings = {
|
||||
"TIMEZONE": timezone,
|
||||
"RETURN_AS_TIMEZONE_AWARE": True,
|
||||
"TO_TIMEZONE": "UTC",
|
||||
"PREFER_DATES_FROM": prefer_dates_from,
|
||||
}
|
||||
|
||||
dt = dateparser.parse(string, settings=settings)
|
||||
if dt is None:
|
||||
raise ValueError('invalid datetime input specified.')
|
||||
raise ValueError("invalid datetime input specified.")
|
||||
|
||||
return MayaDT.from_datetime(dt)
|
||||
|
||||
|
||||
def parse(string, day_first=False):
|
||||
def parse(string, timezone="UTC", day_first=False, year_first=True, strict=False):
|
||||
""""Returns a MayaDT instance for the machine-produced moment specified.
|
||||
|
||||
Powered by pendulum. Accepts most known formats. Useful for working with data.
|
||||
Powered by pendulum.
|
||||
Accepts most known formats. Useful for working with data.
|
||||
|
||||
Keyword Arguments:
|
||||
string -- string to be parsed
|
||||
day_first -- if true, the first value (e.g. 01/05/2016) is parsed as day (default: False)
|
||||
timezone -- timezone referenced from (default: 'UTC')
|
||||
day_first -- if true, the first value (e.g. 01/05/2016)
|
||||
is parsed as day.
|
||||
if year_first is set to True, this distinguishes
|
||||
between YDM and YMD. (default: False)
|
||||
year_first -- if true, the first value (e.g. 2016/05/01)
|
||||
is parsed as year (default: True)
|
||||
strict -- if False, allow pendulum to fall back on datetime parsing
|
||||
if pendulum's own parsing fails
|
||||
"""
|
||||
dt = pendulum.parse(string, day_first=day_first)
|
||||
options = {}
|
||||
options["tz"] = timezone
|
||||
options["day_first"] = day_first
|
||||
options["year_first"] = year_first
|
||||
options["strict"] = strict
|
||||
|
||||
dt = pendulum.parse(str(string), **options)
|
||||
return MayaDT.from_datetime(dt)
|
||||
|
||||
|
||||
def seconds_or_timedelta(duration):
|
||||
def _seconds_or_timedelta(duration):
|
||||
"""Returns `datetime.timedelta` object for the passed duration.
|
||||
|
||||
Keyword Arguments:
|
||||
@@ -614,17 +784,32 @@ def seconds_or_timedelta(duration):
|
||||
elif isinstance(duration, timedelta):
|
||||
dt_timedelta = duration
|
||||
else:
|
||||
raise TypeError('Expects argument as `datetime.timedelta` object '
|
||||
'or seconds in `int` format')
|
||||
raise TypeError(
|
||||
"Expects argument as `datetime.timedelta` object "
|
||||
"or seconds in `int` format"
|
||||
)
|
||||
|
||||
return dt_timedelta
|
||||
|
||||
|
||||
def _translate(dt, target_locale):
|
||||
en = default_loader.get_locale("en")
|
||||
target = default_loader.get_locale(target_locale)
|
||||
naturaldate = humanize.naturaldate(dt)
|
||||
|
||||
base = en.translate(naturaldate, settings=dateparser.conf.settings)
|
||||
|
||||
return target.info["relative-type"][base][-1]
|
||||
|
||||
|
||||
def intervals(start, end, interval):
|
||||
"""Yields MayaDT objects between the start and end MayaDTs given, at a given interval (seconds or timedelta)."""
|
||||
|
||||
interval = seconds_or_timedelta(interval)
|
||||
|
||||
"""
|
||||
Yields MayaDT objects between the start and end MayaDTs given,
|
||||
at a given interval (seconds or timedelta).
|
||||
"""
|
||||
interval = _seconds_or_timedelta(interval)
|
||||
current_timestamp = start
|
||||
while current_timestamp.epoch < end.epoch:
|
||||
yield current_timestamp
|
||||
current_timestamp = current_timestamp.add(seconds=interval.seconds)
|
||||
|
||||
current_timestamp = current_timestamp.add(seconds=interval.total_seconds())
|
||||
@@ -0,0 +1,98 @@
|
||||
import datetime
|
||||
|
||||
import pytz
|
||||
from freezegun import freeze_time
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(
|
||||
params=[
|
||||
("2018-03-25T00:00:00", 2),
|
||||
("2018-03-25T01:00:00", 2),
|
||||
("2018-03-25T02:00:00", 2),
|
||||
("2018-03-25T02:30:00", 2),
|
||||
("2018-03-25T03:00:00", 2),
|
||||
("2018-03-25T04:00:00", 2),
|
||||
("2018-10-28T00:00:00", 2),
|
||||
("2018-10-28T01:00:00", 2),
|
||||
("2018-10-28T02:00:00", 2),
|
||||
("2018-10-28T02:30:00", 2),
|
||||
("2018-10-28T03:00:00", 2),
|
||||
("2018-10-28T04:00:00", 2),
|
||||
],
|
||||
ids=lambda x: x[0] + "_off_" + str(x[1]),
|
||||
)
|
||||
def frozen_now(request):
|
||||
now_string, tz_offset = request.param
|
||||
with freeze_time(now_string, tz_offset=tz_offset):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture(params=[
|
||||
datetime.datetime(2020, 8, 10, 22, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 8, 10, 23, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 8, 11, 0, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 8, 11, 1, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 8, 11, 2, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 8, 11, 3, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 8, 11, 4, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 8, 11, 5, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 8, 11, 6, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 8, 11, 7, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 8, 11, 8, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 8, 11, 9, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 8, 11, 10, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 8, 11, 11, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 8, 11, 12, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 8, 11, 13, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 8, 11, 14, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 8, 11, 15, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 8, 11, 16, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 8, 11, 17, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 8, 11, 18, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 8, 11, 19, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 8, 11, 20, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 8, 11, 21, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
], ids=str)
|
||||
def frozen_2020_08_11_in_paris(request):
|
||||
"""
|
||||
fixture setting datetime.now() to every hour of the 11th of august 2020 in Paris
|
||||
(summer time, GMT+2)
|
||||
"""
|
||||
with freeze_time(request.param):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture(params=[
|
||||
datetime.datetime(2020, 2, 10, 23, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 2, 11, 0, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 2, 11, 1, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 2, 11, 2, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 2, 11, 3, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 2, 11, 4, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 2, 11, 5, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 2, 11, 6, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 2, 11, 7, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 2, 11, 8, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 2, 11, 9, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 2, 11, 10, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 2, 11, 11, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 2, 11, 12, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 2, 11, 13, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 2, 11, 14, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 2, 11, 15, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 2, 11, 16, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 2, 11, 17, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 2, 11, 18, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 2, 11, 19, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 2, 11, 20, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 2, 11, 21, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
datetime.datetime(2020, 2, 11, 22, 2, 0, tzinfo=pytz.timezone('UTC')),
|
||||
], ids=str)
|
||||
def frozen_2020_02_11_in_paris(request):
|
||||
"""
|
||||
fixture setting datetime.now() to every hour of the 11th of february 2020 in Paris
|
||||
(winter time, GMT+1)
|
||||
"""
|
||||
with freeze_time(request.param):
|
||||
yield
|
||||
+315
-92
@@ -1,102 +1,182 @@
|
||||
import pytest
|
||||
import copy
|
||||
from datetime import timedelta
|
||||
import time
|
||||
import calendar
|
||||
from datetime import timedelta, datetime as Datetime
|
||||
|
||||
import pytz
|
||||
import pytest
|
||||
|
||||
import maya
|
||||
from maya.core import _seconds_or_timedelta # import private function
|
||||
|
||||
|
||||
def test_rfc2822():
|
||||
r = maya.parse('February 21, 1994').rfc2822()
|
||||
@pytest.mark.parametrize(
|
||||
"string,expected", [("February 21, 1994", "Mon, 21 Feb 1994 00:00:00 GMT")]
|
||||
)
|
||||
def test_rfc2822(string, expected):
|
||||
r = maya.parse(string).rfc2822()
|
||||
d = maya.MayaDT.from_rfc2822(r)
|
||||
assert r == 'Mon, 21 Feb 1994 00:00:00 GMT'
|
||||
assert r == expected
|
||||
assert r == d.rfc2822()
|
||||
|
||||
|
||||
def test_iso8601():
|
||||
r = maya.parse('February 21, 1994').iso8601()
|
||||
@pytest.mark.parametrize(
|
||||
"string,expected", [("February 21, 1994", "1994-02-21T00:00:00Z")]
|
||||
)
|
||||
def test_iso8601(string, expected):
|
||||
r = maya.parse(string).iso8601()
|
||||
d = maya.MayaDT.from_iso8601(r)
|
||||
assert r == '1994-02-21T00:00:00Z'
|
||||
assert r == expected
|
||||
assert r == d.iso8601()
|
||||
|
||||
|
||||
def test_parse_iso8601():
|
||||
string = '20161001T1430.4+05:30'
|
||||
expected = '2016-10-01T09:00:00.400000Z'
|
||||
d = maya.MayaDT.from_iso8601(string)
|
||||
@pytest.mark.parametrize(
|
||||
"string,expected",
|
||||
[
|
||||
('January 1, 1970', "12.17.16.7.5"),
|
||||
('December 21, 2012', "13.0.0.0.0"),
|
||||
('March 4, 1900', "12.14.5.10.0"),
|
||||
],
|
||||
)
|
||||
def test_long_count(string, expected):
|
||||
r = maya.parse(string).long_count()
|
||||
d = maya.MayaDT.from_long_count(r)
|
||||
assert r == expected
|
||||
assert r == d.long_count()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"string,expected",
|
||||
[
|
||||
("20161001T1430.4+05:30", "2016-10-01T09:00:00.400000Z"),
|
||||
("2016T14", "2016-01-01T14:00:00Z"),
|
||||
("2016-10T14", "2016-10-01T14:00:00Z"),
|
||||
("2012W05", "2012-01-30T00:00:00Z"),
|
||||
("2012W055", "2012-02-03T00:00:00Z"),
|
||||
("2012007", "2012-01-07T00:00:00Z"),
|
||||
("2016-W07T09", "2016-02-15T09:00:00Z"),
|
||||
],
|
||||
)
|
||||
def test_parse_iso8601(string, expected):
|
||||
d = maya.MayaDT.from_iso8601(string)
|
||||
assert expected == d.iso8601()
|
||||
|
||||
string = '2016T14'
|
||||
expected = '2016-01-01T14:00:00Z'
|
||||
d = maya.MayaDT.from_iso8601(string)
|
||||
|
||||
assert expected == d.iso8601()
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_struct():
|
||||
now = round(time.time())
|
||||
ts = time.gmtime(now)
|
||||
m = maya.MayaDT.from_struct(ts)
|
||||
dt = Datetime.fromtimestamp(now, pytz.UTC)
|
||||
assert m._epoch is not None
|
||||
assert m.datetime() == dt
|
||||
ts = time.localtime(now)
|
||||
m = maya.MayaDT.from_struct(ts)
|
||||
dt = Datetime.fromtimestamp(time.mktime(ts) - maya.core.utc_offset(ts), pytz.UTC)
|
||||
assert m._epoch is not None
|
||||
assert m.datetime() == dt
|
||||
|
||||
string = '2016-10T14'
|
||||
expected = '2016-10-01T14:00:00Z'
|
||||
d = maya.MayaDT.from_iso8601(string)
|
||||
|
||||
assert expected == d.iso8601()
|
||||
|
||||
string = '2012W05'
|
||||
expected = '2012-01-30T00:00:00Z'
|
||||
d = maya.MayaDT.from_iso8601(string)
|
||||
|
||||
assert expected == d.iso8601()
|
||||
|
||||
string = '2012W055'
|
||||
expected = '2012-02-03T00:00:00Z'
|
||||
d = maya.MayaDT.from_iso8601(string)
|
||||
|
||||
assert expected == d.iso8601()
|
||||
|
||||
string = '2012007'
|
||||
expected = '2012-01-07T00:00:00Z'
|
||||
d = maya.MayaDT.from_iso8601(string)
|
||||
|
||||
assert expected == d.iso8601()
|
||||
|
||||
string = '2016-W07T09'
|
||||
expected = '2016-02-15T09:00:00Z'
|
||||
d = maya.MayaDT.from_iso8601(string)
|
||||
|
||||
assert expected == d.iso8601()
|
||||
def test_issue_104():
|
||||
e = 1507756331
|
||||
t = Datetime.utcfromtimestamp(e)
|
||||
t = maya.MayaDT.from_datetime(t)
|
||||
assert str(t) == "Wed, 11 Oct 2017 21:12:11 GMT"
|
||||
t = time.gmtime(e)
|
||||
t = maya.MayaDT.from_struct(t)
|
||||
assert str(t) == "Wed, 11 Oct 2017 21:12:11 GMT"
|
||||
|
||||
def test_before_1970():
|
||||
d1 = maya.when("1899-17-11 08:09:10")
|
||||
assert d1.year == 1899
|
||||
assert d1.month == 11
|
||||
assert d1.day == 17
|
||||
assert d1.week == 46
|
||||
assert d1.weekday == 5
|
||||
assert d1.hour == 8
|
||||
assert d1.minute == 9
|
||||
assert d1.second == 10
|
||||
assert d1.microsecond == 0
|
||||
# Test properties for maya.parse()
|
||||
d2 = maya.parse("February 29, 1904 13:12:34")
|
||||
assert d2.year == 1904
|
||||
assert d2.month == 2
|
||||
assert d2.day == 29
|
||||
assert d2.week == 9
|
||||
assert d2.weekday == 1
|
||||
assert d2.hour == 13
|
||||
assert d2.minute == 12
|
||||
assert d2.second == 34
|
||||
assert d2.microsecond == 0
|
||||
|
||||
def test_human_when():
|
||||
r1 = maya.when('yesterday')
|
||||
r2 = maya.when('today')
|
||||
|
||||
r1 = maya.when("yesterday")
|
||||
r2 = maya.when("today")
|
||||
assert (r2.day - r1.day) in (1, -30, -29, -28, -27)
|
||||
|
||||
|
||||
def test_machine_parse():
|
||||
r1 = maya.parse('August 14, 2015')
|
||||
assert r1.day == 14
|
||||
@pytest.mark.usefixtures("frozen_2020_08_11_in_paris")
|
||||
def test_human_when_today_with_timezone_summer_time():
|
||||
d = maya.when("today", timezone='Europe/Paris')
|
||||
assert str(d.datetime(to_timezone="Europe/Paris").date()) == '2020-08-11'
|
||||
|
||||
r2 = maya.parse('August 15, 2015')
|
||||
|
||||
@pytest.mark.usefixtures("frozen_2020_02_11_in_paris")
|
||||
def test_human_when_today_with_timezone_winter_time():
|
||||
d = maya.when("today", timezone='Europe/Paris')
|
||||
assert str(d.datetime(to_timezone="Europe/Paris").date()) == '2020-02-11'
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_2020_08_11_in_paris")
|
||||
def test_human_when_yesterday_with_timezone_summer_time():
|
||||
d = maya.when("yesterday", timezone='Europe/Paris')
|
||||
assert str(d.datetime(to_timezone="Europe/Paris").date()) == '2020-08-10'
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_2020_02_11_in_paris")
|
||||
def test_human_when_yesterday_with_timezone_winter_time():
|
||||
d = maya.when("yesterday", timezone='Europe/Paris')
|
||||
assert str(d.datetime(to_timezone="Europe/Paris").date()) == '2020-02-10'
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_2020_08_11_in_paris")
|
||||
def test_human_when_midnight_with_timezone_summer_time():
|
||||
d = maya.when("midnight", timezone='Europe/Paris')
|
||||
assert str(d.datetime(to_timezone="Europe/Paris")) == '2020-08-11 00:00:00+02:00'
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_2020_02_11_in_paris")
|
||||
def test_human_when_midnight_with_timezone_winter_time():
|
||||
d = maya.when("midnight", timezone='Europe/Paris')
|
||||
assert str(d.datetime(to_timezone="Europe/Paris")) == '2020-02-11 00:00:00+01:00'
|
||||
|
||||
|
||||
def test_machine_parse():
|
||||
r1 = maya.parse("August 14, 2015")
|
||||
assert r1.day == 14
|
||||
r2 = maya.parse("August 15, 2015")
|
||||
assert r2.day == 15
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_dt_tz_translation():
|
||||
d1 = maya.now().datetime()
|
||||
d2 = maya.now().datetime(to_timezone='EST')
|
||||
d2 = maya.now().datetime(to_timezone="EST")
|
||||
assert (d1.hour - d2.hour) % 24 == 5
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_dt_tz_naive():
|
||||
d1 = maya.now().datetime(naive=True)
|
||||
assert d1.tzinfo is None
|
||||
|
||||
d2 = maya.now().datetime(to_timezone='EST', naive=True)
|
||||
d2 = maya.now().datetime(to_timezone="EST", naive=True)
|
||||
assert d2.tzinfo is None
|
||||
assert (d1.hour - d2.hour) % 24 == 5
|
||||
|
||||
|
||||
def test_random_date():
|
||||
|
||||
# Test properties for maya.when()
|
||||
d1 = maya.when('11-17-11 08:09:10')
|
||||
d1 = maya.when("11-17-11 08:09:10")
|
||||
assert d1.year == 2011
|
||||
assert d1.month == 11
|
||||
assert d1.day == 17
|
||||
@@ -106,9 +186,8 @@ def test_random_date():
|
||||
assert d1.minute == 9
|
||||
assert d1.second == 10
|
||||
assert d1.microsecond == 0
|
||||
|
||||
# Test properties for maya.parse()
|
||||
d2 = maya.parse('February 29, 1992 13:12:34')
|
||||
d2 = maya.parse("February 29, 1992 13:12:34")
|
||||
assert d2.year == 1992
|
||||
assert d2.month == 2
|
||||
assert d2.day == 29
|
||||
@@ -121,75 +200,139 @@ def test_random_date():
|
||||
|
||||
|
||||
def test_print_date(capsys):
|
||||
d = maya.when('11-17-11')
|
||||
|
||||
d = maya.when("11-17-11")
|
||||
print(d)
|
||||
out, err = capsys.readouterr()
|
||||
|
||||
assert out == 'Thu, 17 Nov 2011 00:00:00 GMT\n'
|
||||
assert repr(d) == '<MayaDT epoch=1321488000.0>'
|
||||
assert out == "Thu, 17 Nov 2011 00:00:00 GMT\n"
|
||||
assert repr(d) == "<MayaDT epoch=1321488000.0>"
|
||||
|
||||
|
||||
def test_invalid_date():
|
||||
with pytest.raises(ValueError):
|
||||
maya.when('another day')
|
||||
maya.when("another day")
|
||||
|
||||
|
||||
def test_slang_date():
|
||||
d = maya.when('tomorrow')
|
||||
assert d.slang_date() == 'tomorrow'
|
||||
d = maya.when("tomorrow")
|
||||
assert d.slang_date() == "tomorrow"
|
||||
|
||||
|
||||
def test_slang_date_locale():
|
||||
d = maya.when("tomorrow")
|
||||
assert d.slang_date(locale="fr") == "demain"
|
||||
|
||||
|
||||
def test_slang_time():
|
||||
d = maya.when('one hour ago')
|
||||
assert d.slang_time() == 'an hour ago'
|
||||
d = maya.when("1 hour ago")
|
||||
assert d.slang_time() == "1 hour ago"
|
||||
|
||||
|
||||
def test_parse():
|
||||
d = maya.parse('February 21, 1994')
|
||||
assert format(d) == '1994-02-21 00:00:00+00:00'
|
||||
def test_slang_time_locale():
|
||||
d = maya.when("1 hour ago")
|
||||
assert d.slang_time(locale="de") == "vor 1 Stunde"
|
||||
|
||||
d = maya.parse('01/05/2016')
|
||||
assert format(d) == '2016-01-05 00:00:00+00:00'
|
||||
|
||||
d = maya.parse('01/05/2016', day_first=True)
|
||||
assert format(d) == '2016-05-01 00:00:00+00:00'
|
||||
@pytest.mark.parametrize(
|
||||
"string,kwds,expected",
|
||||
[
|
||||
("February 21, 1994", {}, "1994-02-21 00:00:00+00:00"),
|
||||
("01/05/2016", {}, "2016-01-05 00:00:00+00:00"),
|
||||
("01/05/2016", dict(day_first=True), "2016-05-01 00:00:00+00:00"),
|
||||
(
|
||||
"2016/05/01",
|
||||
dict(year_first=True, day_first=False),
|
||||
"2016-05-01 00:00:00+00:00",
|
||||
),
|
||||
(
|
||||
"2016/01/05",
|
||||
dict(year_first=True, day_first=True),
|
||||
"2016-05-01 00:00:00+00:00",
|
||||
),
|
||||
("01/05/2016", dict(timezone="UTC"), "2016-01-05 00:00:00+00:00"),
|
||||
("01/05/2016", dict(timezone="US/Central"), "2016-01-05 06:00:00+00:00"),
|
||||
],
|
||||
)
|
||||
def test_parse(string, kwds, expected):
|
||||
d = maya.parse(string, **kwds)
|
||||
assert format(d) == expected
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_when_past():
|
||||
two_days_away = maya.now().add(days=2)
|
||||
|
||||
past_date = maya.when(two_days_away.slang_date(), prefer_dates_from="past")
|
||||
|
||||
assert past_date < maya.now()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_when_future():
|
||||
two_days_away = maya.now().add(days=2)
|
||||
|
||||
future_date = maya.when(two_days_away.slang_date(), prefer_dates_from="future")
|
||||
|
||||
assert future_date > maya.now()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_when_past_day_name():
|
||||
two_days_away = maya.now().add(days=2)
|
||||
|
||||
past_date = maya.when(
|
||||
calendar.day_name[two_days_away.weekday], prefer_dates_from="past"
|
||||
)
|
||||
|
||||
assert past_date < maya.now()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_when_future_day_name():
|
||||
two_days_away = maya.now().add(days=2)
|
||||
|
||||
future_date = maya.when(
|
||||
calendar.day_name[two_days_away.weekday], prefer_dates_from="future"
|
||||
)
|
||||
|
||||
assert future_date > maya.now()
|
||||
|
||||
|
||||
def test_datetime_to_timezone():
|
||||
dt = maya.when('2016-01-01').datetime(to_timezone='US/Eastern')
|
||||
assert dt.tzinfo.zone == 'US/Eastern'
|
||||
dt = maya.when("2016-01-01").datetime(to_timezone="US/Eastern")
|
||||
assert dt.tzinfo.zone == "US/Eastern"
|
||||
|
||||
def test_rfc3339():
|
||||
mdt = maya.when('2016-01-01')
|
||||
|
||||
def test_rfc3339_epoch():
|
||||
mdt = maya.when("2016-01-01")
|
||||
out = mdt.rfc3339()
|
||||
mdt2 = maya.MayaDT.from_rfc3339(out)
|
||||
assert mdt.epoch == mdt2.epoch
|
||||
|
||||
|
||||
def test_rfc3339_format():
|
||||
rfc3339 = maya.MayaDT.rfc3339(maya.when("2016-01-01T12:03:03Z"))
|
||||
# it's important that the string has got a "max 1-digit millis" fragment
|
||||
# as per https://tools.ietf.org/html/rfc3339#section-5.6
|
||||
assert rfc3339 == "2016-01-01T12:03:03.0Z"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_comparison_operations():
|
||||
now = maya.now()
|
||||
now_copy = copy.deepcopy(now)
|
||||
tomorrow = maya.when('tomorrow')
|
||||
|
||||
tomorrow = maya.when("tomorrow")
|
||||
assert (now == now_copy) is True
|
||||
assert (now == tomorrow) is False
|
||||
|
||||
assert (now != now_copy) is False
|
||||
assert (now != tomorrow) is True
|
||||
|
||||
assert (now < now_copy) is False
|
||||
assert (now < tomorrow) is True
|
||||
|
||||
assert (now <= now_copy) is True
|
||||
assert (now <= tomorrow) is True
|
||||
|
||||
assert (now > now_copy) is False
|
||||
assert (now > tomorrow) is False
|
||||
|
||||
assert (now >= now_copy) is True
|
||||
assert (now >= tomorrow) is False
|
||||
|
||||
# Check Exceptions
|
||||
with pytest.raises(TypeError):
|
||||
now == 1
|
||||
@@ -207,34 +350,114 @@ def test_comparison_operations():
|
||||
|
||||
def test_seconds_or_timedelta():
|
||||
# test for value in seconds
|
||||
assert maya.seconds_or_timedelta(1234) == timedelta(0, 1234)
|
||||
assert _seconds_or_timedelta(1234) == timedelta(0, 1234)
|
||||
# test for value as `datetime.timedelta`
|
||||
assert maya.seconds_or_timedelta(timedelta(0, 1234)) == timedelta(0, 1234)
|
||||
assert _seconds_or_timedelta(timedelta(0, 1234)) == timedelta(0, 1234)
|
||||
# test for invalid value
|
||||
with pytest.raises(TypeError):
|
||||
maya.seconds_or_timedelta('invalid interval')
|
||||
_seconds_or_timedelta("invalid interval")
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_intervals():
|
||||
now = maya.now()
|
||||
tomorrow = now.add(days=1)
|
||||
|
||||
assert len(list(maya.intervals(now, tomorrow, 60*60))) == 24
|
||||
assert len(list(maya.intervals(now, tomorrow, 60 * 60))) == 24
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_dunder_add():
|
||||
now = maya.now()
|
||||
assert now + 1 == now.add(seconds=1)
|
||||
assert now + timedelta(seconds=1) == now.add(seconds=1)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_dunder_radd():
|
||||
now = maya.now()
|
||||
assert now.add(seconds=1) == now + 1
|
||||
assert now.add(seconds=1) == now + timedelta(seconds=1)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_dunder_sub():
|
||||
now = maya.now()
|
||||
assert now - 1 == now.subtract(seconds=1)
|
||||
assert now - timedelta(seconds=1) == now.subtract(seconds=1)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_mayaDT_sub():
|
||||
now = maya.now()
|
||||
then = now.add(days=1)
|
||||
assert then - now == timedelta(seconds=24 * 60 * 60)
|
||||
assert now - then == timedelta(seconds=-24 * 60 * 60)
|
||||
|
||||
|
||||
def test_core_local_timezone(monkeypatch):
|
||||
@property
|
||||
def mock_local_tz(self):
|
||||
class StaticTzInfo(object):
|
||||
zone = "local"
|
||||
|
||||
def __repr__(self):
|
||||
return "<StaticTzInfo 'local'>"
|
||||
|
||||
return StaticTzInfo()
|
||||
|
||||
monkeypatch.setattr(maya.MayaDT, "_local_tz", mock_local_tz)
|
||||
mdt = maya.MayaDT(0)
|
||||
assert mdt.local_timezone == "UTC"
|
||||
|
||||
|
||||
def test_getting_datetime_for_local_timezone(monkeypatch):
|
||||
@property
|
||||
def mock_local_tz(self):
|
||||
class StaticTzInfo(object):
|
||||
zone = "Europe/Zurich"
|
||||
|
||||
def __repr__(self):
|
||||
return "<StaticTzInfo 'Europe/Zurich'>"
|
||||
|
||||
return StaticTzInfo()
|
||||
|
||||
monkeypatch.setattr(maya.MayaDT, "_local_tz", mock_local_tz)
|
||||
|
||||
d = maya.parse("1994-02-21T12:00:00+05:30")
|
||||
|
||||
dt = pytz.timezone("Europe/Zurich").localize(Datetime(1994, 2, 21, 7, 30))
|
||||
|
||||
assert d.local_datetime() == dt
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"when_str,snap_str,expected_when",
|
||||
[("Mon, 21 Feb 1994 21:21:42 GMT", "@d", "Mon, 21 Feb 1994 00:00:00 GMT")],
|
||||
)
|
||||
def test_snaptime(when_str, snap_str, expected_when):
|
||||
# given
|
||||
dt = maya.when(when_str)
|
||||
# when
|
||||
dt = dt.snap(snap_str)
|
||||
# then
|
||||
assert dt == maya.when(expected_when)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"when_str,snap_str,timezone,expected_when",
|
||||
[
|
||||
(
|
||||
"Mon, 21 Feb 1994 21:21:42 GMT",
|
||||
"@d",
|
||||
"Australia/Perth",
|
||||
"Mon, 21 Feb 1994 16:00:00 GMT",
|
||||
)
|
||||
],
|
||||
)
|
||||
def test_snaptime_tz(when_str, snap_str, timezone, expected_when):
|
||||
# given
|
||||
dt = maya.when(when_str)
|
||||
# when
|
||||
dt = dt.snap_tz(snap_str, timezone)
|
||||
# then
|
||||
assert dt == maya.when(expected_when)
|
||||
|
||||
Executable → Regular
+279
-302
@@ -7,34 +7,28 @@ import pytz
|
||||
import maya
|
||||
from maya.compat import cmp
|
||||
|
||||
Los_Angeles = pytz.timezone('America/Los_Angeles')
|
||||
New_York = pytz.timezone('America/New_York')
|
||||
Melbourne = pytz.timezone('Australia/Melbourne')
|
||||
Los_Angeles = pytz.timezone("America/Los_Angeles")
|
||||
New_York = pytz.timezone("America/New_York")
|
||||
Melbourne = pytz.timezone("Australia/Melbourne")
|
||||
|
||||
|
||||
def test_interval_requires_2_of_start_end_duration():
|
||||
start = maya.now()
|
||||
end = start.add(hours=1)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
maya.MayaInterval(start=start)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
maya.MayaInterval(end=end)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
maya.MayaInterval(duration=60)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
maya.MayaInterval(start=start, end=end, duration=60)
|
||||
|
||||
maya.MayaInterval(start=start, end=end)
|
||||
maya.MayaInterval(start=start, duration=60)
|
||||
maya.MayaInterval(end=end, duration=60)
|
||||
|
||||
|
||||
def test_interval_requires_end_time_after_or_on_start_time():
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
maya.MayaInterval(start=maya.now(), duration=0)
|
||||
maya.MayaInterval(start=maya.now(), duration=-1)
|
||||
@@ -64,7 +58,9 @@ def test_interval_init_end_duration():
|
||||
assert interval.start == end.subtract(seconds=duration)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start_doy1,end_doy1,start_doy2,end_doy2,intersection_doys', (
|
||||
@pytest.mark.parametrize(
|
||||
"start_doy1,end_doy1,start_doy2,end_doy2,intersection_doys",
|
||||
(
|
||||
(0, 2, 1, 3, (1, 2)),
|
||||
(0, 2, 3, 4, None),
|
||||
(0, 2, 2, 3, None),
|
||||
@@ -76,98 +72,82 @@ def test_interval_init_end_duration():
|
||||
(1, 3, 1, 1, (1, 1)),
|
||||
(2, 3, 1, 1, None),
|
||||
(1, 3, 2, 2, (2, 2)),
|
||||
), ids=(
|
||||
'overlapping',
|
||||
'non-overlapping',
|
||||
'adjacent',
|
||||
'equal',
|
||||
'instant overlapping start only',
|
||||
'instant equal',
|
||||
'instant disjoint',
|
||||
'instant overlapping',
|
||||
'instant overlapping start only (left)',
|
||||
'instant disjoint (left)',
|
||||
'instant overlapping (left)'
|
||||
))
|
||||
),
|
||||
ids=(
|
||||
"overlapping",
|
||||
"non-overlapping",
|
||||
"adjacent",
|
||||
"equal",
|
||||
"instant overlapping start only",
|
||||
"instant equal",
|
||||
"instant disjoint",
|
||||
"instant overlapping",
|
||||
"instant overlapping start only (left)",
|
||||
"instant disjoint (left)",
|
||||
"instant overlapping (left)",
|
||||
),
|
||||
)
|
||||
def test_interval_intersection(
|
||||
start_doy1, end_doy1, start_doy2, end_doy2, intersection_doys
|
||||
start_doy1, end_doy1, start_doy2, end_doy2, intersection_doys
|
||||
):
|
||||
base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
|
||||
interval1 = maya.MayaInterval(
|
||||
base.add(days=start_doy1),
|
||||
base.add(days=end_doy1),
|
||||
)
|
||||
interval2 = maya.MayaInterval(
|
||||
base.add(days=start_doy2),
|
||||
base.add(days=end_doy2),
|
||||
)
|
||||
|
||||
interval1 = maya.MayaInterval(base.add(days=start_doy1), base.add(days=end_doy1))
|
||||
interval2 = maya.MayaInterval(base.add(days=start_doy2), base.add(days=end_doy2))
|
||||
if intersection_doys:
|
||||
start_doy_intersection, end_doy_intersection = intersection_doys
|
||||
assert interval1 & interval2 == maya.MayaInterval(
|
||||
base.add(days=start_doy_intersection),
|
||||
base.add(days=end_doy_intersection),
|
||||
base.add(days=start_doy_intersection), base.add(days=end_doy_intersection)
|
||||
)
|
||||
else:
|
||||
assert (interval1 & interval2) is None
|
||||
|
||||
# check invalid argument
|
||||
with pytest.raises(TypeError):
|
||||
interval1 & 'invalid type'
|
||||
interval1 & "invalid type"
|
||||
|
||||
|
||||
def test_interval_intersects():
|
||||
base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
|
||||
interval = maya.MayaInterval(base, base.add(days=1))
|
||||
|
||||
assert interval.intersects(interval)
|
||||
assert not interval.intersects(maya.MayaInterval(
|
||||
base.add(days=2),
|
||||
base.add(days=3),
|
||||
))
|
||||
|
||||
assert not interval.intersects(
|
||||
maya.MayaInterval(base.add(days=2), base.add(days=3))
|
||||
)
|
||||
# check invalid argument
|
||||
with pytest.raises(TypeError):
|
||||
interval.intersects('invalid type')
|
||||
interval.intersects("invalid type")
|
||||
|
||||
|
||||
def test_and_operator():
|
||||
base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
|
||||
interval1 = maya.MayaInterval(base, base.add(days=2))
|
||||
interval2 = maya.MayaInterval(base.add(days=1), base.add(days=3))
|
||||
|
||||
assert (
|
||||
interval1 & interval2 ==
|
||||
interval2 & interval1 ==
|
||||
interval1.intersection(interval2)
|
||||
interval1 & interval2
|
||||
== interval2 & interval1 # noqa
|
||||
== interval1.intersection(interval2) # noqa
|
||||
)
|
||||
|
||||
# check invalid argument
|
||||
with pytest.raises(TypeError):
|
||||
interval1.intersection('invalid type')
|
||||
interval1.intersection("invalid type")
|
||||
|
||||
|
||||
def test_interval_eq_operator():
|
||||
start = maya.now()
|
||||
end = start.add(hours=1)
|
||||
interval = maya.MayaInterval(start=start, end=end)
|
||||
|
||||
assert interval == maya.MayaInterval(start=start, end=end)
|
||||
assert interval != maya.MayaInterval(start=start, end=end.add(days=1))
|
||||
|
||||
# check invalid argument
|
||||
with pytest.raises(TypeError):
|
||||
interval == 'invalid type'
|
||||
|
||||
interval == "invalid type"
|
||||
with pytest.raises(TypeError):
|
||||
interval != 'invalid type'
|
||||
interval != "invalid type"
|
||||
|
||||
|
||||
def test_interval_timedelta():
|
||||
start = maya.now()
|
||||
delta = timedelta(hours=1)
|
||||
interval = maya.MayaInterval(start=start, duration=delta)
|
||||
|
||||
assert interval.timedelta == delta
|
||||
|
||||
|
||||
@@ -175,305 +155,241 @@ def test_interval_duration():
|
||||
start = maya.now()
|
||||
delta = timedelta(hours=1)
|
||||
interval = maya.MayaInterval(start=start, duration=delta)
|
||||
|
||||
assert interval.duration == delta.total_seconds()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start_doy1,end_doy1,start_doy2,end_doy2,expected', (
|
||||
@pytest.mark.parametrize(
|
||||
"start_doy1,end_doy1,start_doy2,end_doy2,expected",
|
||||
(
|
||||
(0, 2, 1, 3, False),
|
||||
(0, 2, 3, 4, False),
|
||||
(0, 2, 2, 3, False),
|
||||
(0, 1, 0, 1, True),
|
||||
(0, 3, 1, 2, True),
|
||||
), ids=(
|
||||
'overlapping',
|
||||
'non-overlapping',
|
||||
'adjacent',
|
||||
'equal',
|
||||
'subset',
|
||||
))
|
||||
def test_interval_contains(
|
||||
start_doy1, end_doy1, start_doy2, end_doy2, expected
|
||||
):
|
||||
),
|
||||
ids=("overlapping", "non-overlapping", "adjacent", "equal", "subset"),
|
||||
)
|
||||
def test_interval_contains(start_doy1, end_doy1, start_doy2, end_doy2, expected):
|
||||
base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
|
||||
interval1 = maya.MayaInterval(
|
||||
base.add(days=start_doy1),
|
||||
base.add(days=end_doy1),
|
||||
)
|
||||
interval2 = maya.MayaInterval(
|
||||
base.add(days=start_doy2),
|
||||
base.add(days=end_doy2),
|
||||
)
|
||||
|
||||
interval1 = maya.MayaInterval(base.add(days=start_doy1), base.add(days=end_doy1))
|
||||
interval2 = maya.MayaInterval(base.add(days=start_doy2), base.add(days=end_doy2))
|
||||
assert interval1.contains(interval2) is expected
|
||||
assert (interval2 in interval1) is expected
|
||||
|
||||
# check invalid argument
|
||||
with pytest.raises(TypeError):
|
||||
interval1.contains('invalid type')
|
||||
interval1.contains("invalid type")
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start_doy,end_doy,dt_doy,expected', (
|
||||
@pytest.mark.parametrize(
|
||||
"start_doy,end_doy,dt_doy,expected",
|
||||
(
|
||||
(2, 4, 1, False),
|
||||
(2, 4, 2, True),
|
||||
(2, 4, 3, True),
|
||||
(2, 4, 4, False),
|
||||
(2, 4, 5, False),
|
||||
), ids=(
|
||||
'before-start',
|
||||
'on-start',
|
||||
'during',
|
||||
'on-end',
|
||||
'after-end',
|
||||
))
|
||||
def test_interval_in_operator_maya_dt(
|
||||
start_doy, end_doy, dt_doy, expected
|
||||
):
|
||||
),
|
||||
ids=("before-start", "on-start", "during", "on-end", "after-end"),
|
||||
)
|
||||
def test_interval_in_operator_maya_dt(start_doy, end_doy, dt_doy, expected):
|
||||
base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
|
||||
interval = maya.MayaInterval(
|
||||
start=base.add(days=start_doy),
|
||||
end=base.add(days=end_doy),
|
||||
start=base.add(days=start_doy), end=base.add(days=end_doy)
|
||||
)
|
||||
dt = base.add(days=dt_doy)
|
||||
|
||||
assert (dt in interval) is expected
|
||||
|
||||
# check invalid argument
|
||||
with pytest.raises(TypeError):
|
||||
'invalid type' in interval
|
||||
"invalid type" in interval
|
||||
|
||||
|
||||
def test_interval_hash():
|
||||
start = maya.now()
|
||||
end = start.add(hours=1)
|
||||
interval = maya.MayaInterval(start=start, end=end)
|
||||
|
||||
assert hash(interval) == hash(maya.MayaInterval(start=start, end=end))
|
||||
assert hash(interval) != hash(maya.MayaInterval(
|
||||
start=start, end=end.add(days=1)))
|
||||
assert hash(interval) != hash(maya.MayaInterval(start=start, end=end.add(days=1)))
|
||||
|
||||
|
||||
def test_interval_iter():
|
||||
start = maya.now()
|
||||
end = start.add(days=1)
|
||||
|
||||
assert tuple(maya.MayaInterval(start=start, end=end)) == (start, end)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start1,end1,start2,end2,expected', [
|
||||
(1, 2, 1, 2, 0),
|
||||
(1, 3, 2, 4, -1),
|
||||
(2, 4, 1, 3, 1),
|
||||
(1, 2, 1, 3, -1),
|
||||
], ids=(
|
||||
'equal',
|
||||
'less-than',
|
||||
'greater-than',
|
||||
'use-end-time-if-start-time-identical',
|
||||
))
|
||||
@pytest.mark.parametrize(
|
||||
"start1,end1,start2,end2,expected",
|
||||
[(1, 2, 1, 2, 0), (1, 3, 2, 4, -1), (2, 4, 1, 3, 1), (1, 2, 1, 3, -1)],
|
||||
ids=("equal", "less-than", "greater-than", "use-end-time-if-start-time-identical"),
|
||||
)
|
||||
def test_interval_cmp(start1, end1, start2, end2, expected):
|
||||
base = maya.now()
|
||||
interval1 = maya.MayaInterval(
|
||||
start=base.add(days=start1),
|
||||
end=base.add(days=end1),
|
||||
)
|
||||
interval2 = maya.MayaInterval(
|
||||
start=base.add(days=start2),
|
||||
end=base.add(days=end2),
|
||||
)
|
||||
interval1 = maya.MayaInterval(start=base.add(days=start1), end=base.add(days=end1))
|
||||
interval2 = maya.MayaInterval(start=base.add(days=start2), end=base.add(days=end2))
|
||||
assert cmp(interval1, interval2) == expected
|
||||
|
||||
# check invalid argument
|
||||
with pytest.raises(TypeError):
|
||||
cmp(interval1, 'invalid type')
|
||||
cmp(interval1, "invalid type")
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start1,end1,start2,end2,expected', [
|
||||
(1, 2, 2, 3, [(1, 3)]),
|
||||
(1, 3, 2, 4, [(1, 4)]),
|
||||
(1, 2, 3, 4, [(1, 2), (3, 4)]),
|
||||
(1, 5, 2, 3, [(1, 5)]),
|
||||
], ids=(
|
||||
'adjacent',
|
||||
'overlapping',
|
||||
'non-overlapping',
|
||||
'contains',
|
||||
))
|
||||
@pytest.mark.parametrize(
|
||||
"start1,end1,start2,end2,expected",
|
||||
[
|
||||
(1, 2, 2, 3, [(1, 3)]),
|
||||
(1, 3, 2, 4, [(1, 4)]),
|
||||
(1, 2, 3, 4, [(1, 2), (3, 4)]),
|
||||
(1, 5, 2, 3, [(1, 5)]),
|
||||
],
|
||||
ids=("adjacent", "overlapping", "non-overlapping", "contains"),
|
||||
)
|
||||
def test_interval_combine(start1, end1, start2, end2, expected):
|
||||
base = maya.now()
|
||||
interval1 = maya.MayaInterval(
|
||||
start=base.add(days=start1),
|
||||
end=base.add(days=end1),
|
||||
)
|
||||
interval2 = maya.MayaInterval(
|
||||
start=base.add(days=start2),
|
||||
end=base.add(days=end2),
|
||||
)
|
||||
expected_intervals = [maya.MayaInterval(
|
||||
start=base.add(days=start),
|
||||
end=base.add(days=end),
|
||||
) for start, end in expected]
|
||||
|
||||
interval1 = maya.MayaInterval(start=base.add(days=start1), end=base.add(days=end1))
|
||||
interval2 = maya.MayaInterval(start=base.add(days=start2), end=base.add(days=end2))
|
||||
expected_intervals = [
|
||||
maya.MayaInterval(start=base.add(days=start), end=base.add(days=end))
|
||||
for start, end in expected
|
||||
]
|
||||
assert interval1.combine(interval2) == expected_intervals
|
||||
assert interval2.combine(interval1) == expected_intervals
|
||||
|
||||
# check invalid argument
|
||||
with pytest.raises(TypeError):
|
||||
interval2.combine('invalid type')
|
||||
interval2.combine("invalid type")
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start1,end1,start2,end2,expected', [
|
||||
(1, 2, 3, 4, [(1, 2)]),
|
||||
(1, 2, 2, 4, [(1, 2)]),
|
||||
(2, 3, 1, 4, []),
|
||||
(1, 4, 2, 3, [(1, 2), (3, 4)]),
|
||||
(1, 4, 0, 2, [(2, 4)]),
|
||||
(1, 4, 3, 5, [(1, 3)]),
|
||||
(1, 4, 1, 2, [(2, 4)]),
|
||||
(1, 4, 3, 4, [(1, 3)]),
|
||||
], ids=(
|
||||
'non-overlapping',
|
||||
'adjacent',
|
||||
'contains',
|
||||
'splits',
|
||||
'overlaps-left',
|
||||
'overlaps-right',
|
||||
'overlaps-left-identical-start',
|
||||
'overlaps-right-identical-end',
|
||||
))
|
||||
@pytest.mark.parametrize(
|
||||
"start1,end1,start2,end2,expected",
|
||||
[
|
||||
(1, 2, 3, 4, [(1, 2)]),
|
||||
(1, 2, 2, 4, [(1, 2)]),
|
||||
(2, 3, 1, 4, []),
|
||||
(1, 4, 2, 3, [(1, 2), (3, 4)]),
|
||||
(1, 4, 0, 2, [(2, 4)]),
|
||||
(1, 4, 3, 5, [(1, 3)]),
|
||||
(1, 4, 1, 2, [(2, 4)]),
|
||||
(1, 4, 3, 4, [(1, 3)]),
|
||||
],
|
||||
ids=(
|
||||
"non-overlapping",
|
||||
"adjacent",
|
||||
"contains",
|
||||
"splits",
|
||||
"overlaps-left",
|
||||
"overlaps-right",
|
||||
"overlaps-left-identical-start",
|
||||
"overlaps-right-identical-end",
|
||||
),
|
||||
)
|
||||
def test_interval_subtract(start1, end1, start2, end2, expected):
|
||||
base = maya.now()
|
||||
interval1 = maya.MayaInterval(
|
||||
start=base.add(days=start1),
|
||||
end=base.add(days=end1),
|
||||
)
|
||||
interval2 = maya.MayaInterval(
|
||||
start=base.add(days=start2),
|
||||
end=base.add(days=end2),
|
||||
)
|
||||
expected_intervals = [maya.MayaInterval(
|
||||
start=base.add(days=start),
|
||||
end=base.add(days=end),
|
||||
) for start, end in expected]
|
||||
|
||||
interval1 = maya.MayaInterval(start=base.add(days=start1), end=base.add(days=end1))
|
||||
interval2 = maya.MayaInterval(start=base.add(days=start2), end=base.add(days=end2))
|
||||
expected_intervals = [
|
||||
maya.MayaInterval(start=base.add(days=start), end=base.add(days=end))
|
||||
for start, end in expected
|
||||
]
|
||||
assert interval1.subtract(interval2) == expected_intervals
|
||||
|
||||
# check invalid argument
|
||||
with pytest.raises(TypeError):
|
||||
interval1.subtract('invalid type')
|
||||
interval1.subtract("invalid type")
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start1,end1,start2,end2,expected', [
|
||||
(1, 2, 2, 3, True),
|
||||
(2, 3, 1, 2, True),
|
||||
(1, 3, 2, 3, False),
|
||||
(2, 3, 4, 5, False),
|
||||
], ids=(
|
||||
'adjacent-right',
|
||||
'adjacent-left',
|
||||
'overlapping',
|
||||
'non-overlapping',
|
||||
))
|
||||
@pytest.mark.parametrize(
|
||||
"start1,end1,start2,end2,expected",
|
||||
[(1, 2, 2, 3, True), (2, 3, 1, 2, True), (1, 3, 2, 3, False), (2, 3, 4, 5, False)],
|
||||
ids=("adjacent-right", "adjacent-left", "overlapping", "non-overlapping"),
|
||||
)
|
||||
def test_interval_is_adjacent(start1, end1, start2, end2, expected):
|
||||
base = maya.now()
|
||||
interval1 = maya.MayaInterval(
|
||||
start=base.add(days=start1),
|
||||
end=base.add(days=end1),
|
||||
)
|
||||
interval2 = maya.MayaInterval(
|
||||
start=base.add(days=start2),
|
||||
end=base.add(days=end2),
|
||||
)
|
||||
interval1 = maya.MayaInterval(start=base.add(days=start1), end=base.add(days=end1))
|
||||
interval2 = maya.MayaInterval(start=base.add(days=start2), end=base.add(days=end2))
|
||||
assert interval1.is_adjacent(interval2) == expected
|
||||
|
||||
# check invalid argument
|
||||
with pytest.raises(TypeError):
|
||||
interval1.is_adjacent('invalid type')
|
||||
interval1.is_adjacent("invalid type")
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start,end,delta,include_remainder,expected', [
|
||||
(0, 10, 5, False, [(0, 5), (5, 10)]),
|
||||
(0, 10, 5, True, [(0, 5), (5, 10)]),
|
||||
(0, 10, 3, False, [(0, 3), (3, 6), (6, 9)]),
|
||||
(0, 10, 3, True, [(0, 3), (3, 6), (6, 9), (9, 10)]),
|
||||
(0, 2, 5, False, []),
|
||||
(0, 2, 5, True, [(0, 2)]),
|
||||
], ids=(
|
||||
'even-split',
|
||||
'even-split-include-partial',
|
||||
'uneven-split-do-not-include-partial',
|
||||
'uneven-split-include-partial',
|
||||
'delta-larger-than-timepsan-do-not-include-partial',
|
||||
'delta-larger-than-timepsan-include-partial',
|
||||
))
|
||||
@pytest.mark.parametrize(
|
||||
"start,end,delta,include_remainder,expected",
|
||||
[
|
||||
(0, 10, 5, False, [(0, 5), (5, 10)]),
|
||||
(0, 10, 5, True, [(0, 5), (5, 10)]),
|
||||
(0, 10, 3, False, [(0, 3), (3, 6), (6, 9)]),
|
||||
(0, 10, 3, True, [(0, 3), (3, 6), (6, 9), (9, 10)]),
|
||||
(0, 2, 5, False, []),
|
||||
(0, 2, 5, True, [(0, 2)]),
|
||||
],
|
||||
ids=(
|
||||
"even-split",
|
||||
"even-split-include-partial",
|
||||
"uneven-split-do-not-include-partial",
|
||||
"uneven-split-include-partial",
|
||||
"delta-larger-than-timepsan-do-not-include-partial",
|
||||
"delta-larger-than-timepsan-include-partial",
|
||||
),
|
||||
)
|
||||
def test_interval_split(start, end, delta, include_remainder, expected):
|
||||
base = maya.now()
|
||||
interval = maya.MayaInterval(
|
||||
start=base.add(days=start),
|
||||
end=base.add(days=end),
|
||||
)
|
||||
interval = maya.MayaInterval(start=base.add(days=start), end=base.add(days=end))
|
||||
delta = timedelta(days=delta)
|
||||
|
||||
expected_intervals = [
|
||||
maya.MayaInterval(
|
||||
start=base.add(days=s),
|
||||
end=base.add(days=e),
|
||||
) for s, e in expected
|
||||
maya.MayaInterval(start=base.add(days=s), end=base.add(days=e))
|
||||
for s, e in expected
|
||||
]
|
||||
|
||||
assert expected_intervals == list(interval.split(
|
||||
delta, include_remainder=include_remainder))
|
||||
assert expected_intervals == list(
|
||||
interval.split(delta, include_remainder=include_remainder)
|
||||
)
|
||||
|
||||
|
||||
def test_interval_split_non_positive_delta():
|
||||
start = maya.now()
|
||||
end = start.add(days=1)
|
||||
interval = maya.MayaInterval(start=start, end=end)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
list(interval.split(timedelta(seconds=0)))
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
list(interval.split(timedelta(seconds=-10)))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start,end,minutes,timezone,snap_out,expected_start,expected_end', [
|
||||
((5, 12), (8, 48), 30, None, False, (5, 30), (8, 30)),
|
||||
((5, 12), (8, 48), 30, None, True, (5, 0), (9, 0)),
|
||||
((5, 15), (9, 0), 15, None, False, (5, 15), (9, 0)),
|
||||
((5, 15), (9, 0), 15, None, True, (5, 15), (9, 0)),
|
||||
((6, 50), (9, 15), 60, 'America/New_York', False, (7, 0), (9, 0)),
|
||||
((6, 50), (9, 15), 60, 'America/New_York', True, (6, 0), (10, 0)),
|
||||
((6, 20), (6, 50), 60, None, False, (6, 0), (6, 0)),
|
||||
((6, 20), (6, 50), 60, None, True, (6, 0), (7, 0)),
|
||||
((6, 20), (6, 50), 60, 'America/Chicago', False, (6, 0), (6, 0)),
|
||||
((6, 20), (6, 50), 60, 'America/Chicago', True, (6, 0), (7, 0)),
|
||||
], ids=(
|
||||
'normal',
|
||||
'normal-snap_out',
|
||||
'already-quantized',
|
||||
'already-quantized-snap_out',
|
||||
'with-timezone',
|
||||
'with-timezone-snap_out',
|
||||
'too-small',
|
||||
'too-small-snap_out',
|
||||
'too-small-with-timezone',
|
||||
'too-small-with-timezone-snap_out',
|
||||
))
|
||||
def test_quantize(start, end, minutes, timezone, snap_out, expected_start, expected_end):
|
||||
@pytest.mark.parametrize(
|
||||
"start,end,minutes,timezone,snap_out,expected_start,expected_end",
|
||||
[
|
||||
((5, 12), (8, 48), 30, None, False, (5, 30), (8, 30)),
|
||||
((5, 12), (8, 48), 30, None, True, (5, 0), (9, 0)),
|
||||
((5, 15), (9, 0), 15, None, False, (5, 15), (9, 0)),
|
||||
((5, 15), (9, 0), 15, None, True, (5, 15), (9, 0)),
|
||||
((6, 50), (9, 15), 60, "America/New_York", False, (7, 0), (9, 0)),
|
||||
((6, 50), (9, 15), 60, "America/New_York", True, (6, 0), (10, 0)),
|
||||
((6, 20), (6, 50), 60, None, False, (6, 0), (6, 0)),
|
||||
((6, 20), (6, 50), 60, None, True, (6, 0), (7, 0)),
|
||||
((6, 20), (6, 50), 60, "America/Chicago", False, (6, 0), (6, 0)),
|
||||
((6, 20), (6, 50), 60, "America/Chicago", True, (6, 0), (7, 0)),
|
||||
],
|
||||
ids=(
|
||||
"normal",
|
||||
"normal-snap_out",
|
||||
"already-quantized",
|
||||
"already-quantized-snap_out",
|
||||
"with-timezone",
|
||||
"with-timezone-snap_out",
|
||||
"too-small",
|
||||
"too-small-snap_out",
|
||||
"too-small-with-timezone",
|
||||
"too-small-with-timezone-snap_out",
|
||||
),
|
||||
)
|
||||
def test_quantize(
|
||||
start, end, minutes, timezone, snap_out, expected_start, expected_end
|
||||
):
|
||||
base = maya.MayaDT.from_datetime(datetime(2017, 1, 1))
|
||||
interval = maya.MayaInterval(
|
||||
start=base.add(hours=start[0], minutes=start[1]),
|
||||
end=base.add(hours=end[0], minutes=end[1]),
|
||||
)
|
||||
|
||||
kwargs = {'timezone': timezone} if timezone is not None else {}
|
||||
kwargs = {"timezone": timezone} if timezone is not None else {}
|
||||
quantized_interval = interval.quantize(
|
||||
timedelta(minutes=minutes),
|
||||
snap_out=snap_out,
|
||||
**kwargs
|
||||
timedelta(minutes=minutes), snap_out=snap_out, **kwargs
|
||||
)
|
||||
|
||||
assert quantized_interval == maya.MayaInterval(
|
||||
start=base.add(hours=expected_start[0], minutes=expected_start[1]),
|
||||
end=base.add(hours=expected_end[0], minutes=expected_end[1]),
|
||||
@@ -484,7 +400,6 @@ def test_quantize_invalid_delta():
|
||||
start = maya.now()
|
||||
end = start.add(days=1)
|
||||
interval = maya.MayaInterval(start=start, end=end)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
interval.quantize(timedelta(minutes=0))
|
||||
with pytest.raises(ValueError):
|
||||
@@ -495,48 +410,44 @@ def test_interval_flatten_non_overlapping():
|
||||
step = 2
|
||||
max_hour = 20
|
||||
base = maya.now()
|
||||
intervals = [maya.MayaInterval(
|
||||
start=base.add(hours=hour),
|
||||
duration=timedelta(hours=step - 1),
|
||||
) for hour in range(0, max_hour, step)]
|
||||
intervals = [
|
||||
maya.MayaInterval(
|
||||
start=base.add(hours=hour), duration=timedelta(hours=step - 1)
|
||||
)
|
||||
for hour in range(0, max_hour, step)
|
||||
]
|
||||
random.shuffle(intervals)
|
||||
|
||||
assert maya.MayaInterval.flatten(intervals) == sorted(intervals)
|
||||
|
||||
|
||||
def test_interval_flatten_adjacent():
|
||||
step = 2
|
||||
max_hour = 20
|
||||
base = maya.when('jan/1/2011')
|
||||
|
||||
base = maya.when("jan/1/2011")
|
||||
intervals = [
|
||||
maya.MayaInterval(
|
||||
start=base.add(hours=hour),
|
||||
duration=timedelta(hours=step),
|
||||
) for hour in range(0, max_hour, step)
|
||||
maya.MayaInterval(start=base.add(hours=hour), duration=timedelta(hours=step))
|
||||
for hour in range(0, max_hour, step)
|
||||
]
|
||||
random.shuffle(intervals)
|
||||
|
||||
assert maya.MayaInterval.flatten(intervals) == [maya.MayaInterval(
|
||||
start=base,
|
||||
duration=timedelta(hours=max_hour),
|
||||
)]
|
||||
assert maya.MayaInterval.flatten(intervals) == [
|
||||
maya.MayaInterval(start=base, duration=timedelta(hours=max_hour))
|
||||
]
|
||||
|
||||
|
||||
def test_interval_flatten_intersecting():
|
||||
step = 2
|
||||
max_hour = 20
|
||||
base = maya.now()
|
||||
intervals = [maya.MayaInterval(
|
||||
start=base.add(hours=hour),
|
||||
duration=timedelta(hours=step, minutes=30),
|
||||
) for hour in range(0, max_hour, step)]
|
||||
intervals = [
|
||||
maya.MayaInterval(
|
||||
start=base.add(hours=hour), duration=timedelta(hours=step, minutes=30)
|
||||
)
|
||||
for hour in range(0, max_hour, step)
|
||||
]
|
||||
random.shuffle(intervals)
|
||||
|
||||
assert maya.MayaInterval.flatten(intervals) == [maya.MayaInterval(
|
||||
start=base,
|
||||
duration=timedelta(hours=max_hour, minutes=30),
|
||||
)]
|
||||
assert maya.MayaInterval.flatten(intervals) == [
|
||||
maya.MayaInterval(start=base, duration=timedelta(hours=max_hour, minutes=30))
|
||||
]
|
||||
|
||||
|
||||
def test_interval_flatten_containing():
|
||||
@@ -544,16 +455,16 @@ def test_interval_flatten_containing():
|
||||
max_hour = 20
|
||||
base = maya.now()
|
||||
containing_interval = maya.MayaInterval(
|
||||
start=base,
|
||||
end=base.add(hours=max_hour + step),
|
||||
start=base, end=base.add(hours=max_hour + step)
|
||||
)
|
||||
intervals = [maya.MayaInterval(
|
||||
start=base.add(hours=hour),
|
||||
duration=timedelta(hours=step - 1),
|
||||
) for hour in range(2, max_hour, step)]
|
||||
intervals = [
|
||||
maya.MayaInterval(
|
||||
start=base.add(hours=hour), duration=timedelta(hours=step - 1)
|
||||
)
|
||||
for hour in range(2, max_hour, step)
|
||||
]
|
||||
intervals.append(containing_interval)
|
||||
random.shuffle(intervals)
|
||||
|
||||
assert maya.MayaInterval.flatten(intervals) == [containing_interval]
|
||||
|
||||
|
||||
@@ -561,24 +472,90 @@ def test_interval_from_datetime():
|
||||
start = maya.now()
|
||||
duration = timedelta(hours=1)
|
||||
end = start + duration
|
||||
|
||||
interval = maya.MayaInterval.from_datetime(
|
||||
start_dt=start.datetime(naive=False),
|
||||
end_dt=end.datetime(naive=False),
|
||||
start_dt=start.datetime(naive=False), end_dt=end.datetime(naive=False)
|
||||
)
|
||||
assert interval.start == start
|
||||
assert interval.end == end
|
||||
|
||||
interval2 = maya.MayaInterval.from_datetime(
|
||||
start_dt=start.datetime(naive=False),
|
||||
duration=duration,
|
||||
start_dt=start.datetime(naive=False), duration=duration
|
||||
)
|
||||
assert interval2.start == start
|
||||
assert interval2.end == end
|
||||
|
||||
interval3 = maya.MayaInterval.from_datetime(
|
||||
end_dt=end.datetime(naive=False),
|
||||
duration=duration,
|
||||
end_dt=end.datetime(naive=False), duration=duration
|
||||
)
|
||||
assert interval3.start == start
|
||||
assert interval3.end == end
|
||||
|
||||
|
||||
def test_interval_iso8601():
|
||||
start = maya.when("11-17-11 08:09:10")
|
||||
interval = maya.MayaInterval(start=start, duration=1)
|
||||
assert interval.iso8601() == "2011-11-17T08:09:10Z/2011-11-17T08:09:11Z"
|
||||
|
||||
|
||||
def test_interval_from_iso8601():
|
||||
interval = maya.MayaInterval.from_iso8601(
|
||||
"2018-03-18T14:27:18Z/2018-04-01T04:15:27Z"
|
||||
)
|
||||
s = maya.when("2018-03-18T14:27:18Z")
|
||||
e = maya.when("2018-04-01T04:15:27Z")
|
||||
|
||||
assert interval.start == s
|
||||
assert interval.end == e
|
||||
|
||||
|
||||
def test_interval_from_iso8601_duration():
|
||||
interval = maya.MayaInterval.from_iso8601("2018-03-18T14:27:18Z/P13DT13H48M9S")
|
||||
s = maya.when("2018-03-18T14:27:18Z")
|
||||
e = maya.when("2018-04-01T04:15:27Z")
|
||||
|
||||
assert interval.start == s
|
||||
assert interval.end == e
|
||||
|
||||
interval = maya.MayaInterval.from_iso8601("2018-03-05T14:27:18Z/P2W")
|
||||
s = maya.when("2018-03-05T14:27:18Z")
|
||||
e = maya.when("2018-03-19T14:27:18Z")
|
||||
|
||||
assert interval.start == s
|
||||
assert interval.end == e
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"start_string,end_string,interval,expected_count",
|
||||
[
|
||||
("2019-01-03 11:40:00Z", "2019-01-03 11:40:20Z", 2, 10),
|
||||
("2019-01-03 11:40:00Z", "2019-01-03 11:40:30Z", timedelta(seconds=2), 15),
|
||||
("2019-01-03 11:40:00Z", "2019-01-03 11:45:00Z", 2 * 60, 3),
|
||||
("2019-01-03 11:40:00Z", "2019-01-03 11:51:00Z", timedelta(minutes=1), 11),
|
||||
("2019-01-03 11:40:00Z", "2019-01-03 21:40:00Z", 3 * 60 * 60, 4),
|
||||
("2019-01-03 11:40:00Z", "2019-01-03 13:41:00Z", timedelta(hours=1), 3),
|
||||
("2019-01-03 11:40:00Z", "2019-01-09 11:40:00Z", 3 * 60 * 60 * 24, 2),
|
||||
("2019-01-03 11:40:00Z", "2019-01-05 12:00:00Z", timedelta(days=2), 2),
|
||||
],
|
||||
ids=(
|
||||
"seconds",
|
||||
"seconds-timedelta",
|
||||
"minutes",
|
||||
"minutes-timedelta",
|
||||
"hours",
|
||||
"hours-timedelta",
|
||||
"days",
|
||||
"days-timedelta",
|
||||
),
|
||||
)
|
||||
def test_intervals(start_string, end_string, interval, expected_count):
|
||||
start = maya.parse(start_string)
|
||||
end = maya.parse(end_string)
|
||||
assert len(list(maya.intervals(start, end, interval))) == expected_count
|
||||
|
||||
|
||||
def test_issue_168_regression():
|
||||
start = maya.now()
|
||||
end = start.add(weeks=1)
|
||||
gen = maya.intervals(start=start, end=end, interval=60 * 60 * 24)
|
||||
# Since the bug causes the generator to never end, first sanity
|
||||
# check that two results are not the same.
|
||||
assert next(gen) != next(gen)
|
||||
assert len(list(maya.intervals(start=start, end=end, interval=60 * 60 * 24))) == 7
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
[tox]
|
||||
envlist = lint,manifest,py27,py35,py36,py37,docs,coverage-report
|
||||
|
||||
|
||||
[testenv]
|
||||
# Prevent random setuptools/pip breakages like
|
||||
# https://github.com/pypa/setuptools/issues/1042 from breaking our builds.
|
||||
setenv =
|
||||
VIRTUALENV_NO_DOWNLOAD=1
|
||||
extras = {env:TOX_AP_TEST_EXTRAS:tests}
|
||||
commands = coverage run --parallel -m pytest {posargs}
|
||||
|
||||
|
||||
[testenv:coverage-report]
|
||||
basepython = python3.7
|
||||
skip_install = true
|
||||
deps = coverage
|
||||
commands =
|
||||
coverage combine
|
||||
coverage report
|
||||
|
||||
|
||||
[testenv:lint]
|
||||
basepython = python3.7
|
||||
skip_install = true
|
||||
deps = pre-commit
|
||||
passenv = HOMEPATH # needed on Windows
|
||||
commands = pre-commit run --all-files
|
||||
|
||||
|
||||
[testenv:docs]
|
||||
basepython = python3.7
|
||||
extras = docs
|
||||
commands =
|
||||
sphinx-build -W -b html -d {envtmpdir}/doctrees docs/source docs/_build/html
|
||||
;sphinx-build -W -b doctest -d {envtmpdir}/doctrees docs/source docs/_build/html
|
||||
|
||||
|
||||
[testenv:manifest]
|
||||
basepython = python3.7
|
||||
deps = check-manifest
|
||||
skip_install = true
|
||||
commands = check-manifest
|
||||
Reference in New Issue
Block a user