Compare commits

..

77 Commits

Author SHA1 Message Date
Timo Furrer fa1c4eb600 Include epoch microseconds in DT comparison methods. Closes #156 2019-08-24 23:22:35 +02:00
Timo Furrer 42eb6fb387 Remove Windows from the integration test matrix 2019-08-24 23:22:26 +02:00
Timo Furrer be1bea2dcf Remove executable flag from test module 2019-08-24 23:18:54 +02:00
Timo Furrer a4432eef31 Cleanup Python project 2019-08-24 23:10:26 +02:00
Timo Furrer 260f0d54eb Cleanup README 2019-08-24 22:34:50 +02:00
Timo Furrer 70af3c8054 Merge pull request #178 from alxwrd/update-readme
Update README links
2019-07-24 18:37:36 +02:00
Alex Ward 1f12c2c6e4 update links 2019-07-24 17:07:48 +01:00
kennethreitz c5dae59909 Update README.rst 2019-06-14 07:50:42 -04:00
Timo Furrer 774b141d91 Merge pull request #172 from martinzugnoni/master
Interactive demo
2019-03-22 09:09:44 +01:00
Timo Furrer e3bc3516be Merge pull request #173 from PiDelport/patch-1
README: Link related projects
2019-03-20 13:31:49 +01:00
Pi Delport 08c0fb5071 README: Link related projects
This makes it easier for readers to find out more about Delorean, Arrow, and Pendulum.
2019-03-20 11:20:54 +02:00
Martin Zugnoni 7ddf2db33c Update demo link with kennethreitz's repository 2019-02-20 15:29:12 -03:00
Martin Zugnoni 27ee872a7f Merge branch 'master' of github.com:kennethreitz/maya 2019-02-20 15:24:49 -03:00
Timo Furrer 4e58e6455e Merge pull request #169 from ftobia/fix_intervals
Fix issue #168: intervals greater than one day were incorrect.
2019-01-04 15:13:52 +01:00
Frank Tobia 42cc61e7a8 Update authors. 2019-01-03 14:00:47 -05:00
Frank Tobia 9abd618083 Add basic tests for maya.intervals() 2019-01-03 13:58:06 -05:00
Frank Tobia 2c2a8d03c8 Fix issue #168: intervals greater than one day were incorrect. 2019-01-02 14:03:34 -05:00
Martin Zugnoni 3220a7fc32 Add do your own experiments section in DEMO notebook 2018-12-14 14:54:05 -03:00
Martin Zugnoni d95ed97e73 Add DEMO files and link in the README 2018-12-14 14:48:26 -03:00
Timo Furrer bdf2bb332b Merge pull request #166 from kennethreitz/feature/pendulum-2
Use pendulum >= 2.0.2
2018-12-14 13:30:22 +01:00
Timo Furrer 6522525d7c alpha release: 0.6.0a1 2018-12-02 13:37:48 +00:00
Timo Furrer 3a83f10988 Use pendulum >= 2.0.2. Closes #160 2018-12-02 13:31:22 +00:00
Timo Furrer 05eb0d0051 Merge pull request #164 from possnfiffer/patch-1
Update README.rst
2018-10-18 11:31:58 +02:00
__ROLLER__ 61eabc54e0 Update README.rst 2018-10-15 22:05:26 -06:00
kennethreitz 6bb380e7c5 Update README.rst 2018-09-17 08:12:21 -04:00
Timo Furrer cc698817b4 release: 0.5.0 2018-05-26 15:19:57 +02:00
Timo Furrer 9964e9305e Merge pull request #154 from alxwrd/support-locales
Allow slang methods to change locale
2018-05-26 15:16:17 +02:00
Alex Ward d87236a30d update authors 2018-05-26 11:19:57 +01:00
Alex Ward d44a1dd2f6 add tracer tests for slang_ locales 2018-05-26 11:19:36 +01:00
Timo Furrer ed5d72b34c Merge pull request #155 from DavidMuller/patch-1
Add Example of Creating a MayaDT Using a Unix Time
2018-05-22 21:38:21 +02:00
David Muller 9c1e94f956 Add example that instantiates MayaDT w/ unix time 2018-05-22 10:45:01 -07:00
Alex Ward 6755968f69 remove unused calendar import 2018-05-22 09:57:32 +01:00
Alex Ward 3760af2c41 use pendulum and dateparser for slang_date 2018-05-21 22:37:31 +01:00
Alex Ward df09625e74 fix failing test 2018-05-15 22:56:28 +01:00
Alex Ward 2d848bd62a typo fix 2018-05-15 22:55:07 +01:00
Alex Ward a99cddf76f allow changing the locale for slang_time 2018-05-15 22:27:16 +01:00
Timo Furrer 1b432d6626 release: 0.4.3 2018-05-15 09:13:21 +02:00
Timo Furrer f09d6eec63 Split RFC3339 test 2018-05-15 09:13:01 +02:00
Timo Furrer 62a6283200 Merge pull request #150 from marcelstoer/patch-1
Fix RFC3339 representation
2018-05-15 09:08:52 +02:00
Marcel Stör 9ef43a29d9 Add RFC3339 unit test 2018-05-14 21:49:47 +02:00
Marcel Stör a2bff42439 Fix RFC3339 representation
Must be at most 1-digit millisecond.
2018-05-14 20:48:28 +02:00
Timo Furrer 5525beda31 release: 0.4.2 2018-05-10 17:25:04 +02:00
Timo Furrer eeb07d46db Merge pull request #152 from kennethreitz/feature/local-datetime
Implement method to get MayaDT instance as local timezone-aware datetime instance
2018-05-10 17:24:44 +02:00
Timo Furrer 4056d1a9aa Implement method to get MayaDT instance as local timezone-aware datetime instance 2018-05-10 17:22:35 +02:00
Timo Furrer 7a750a1cff release: 0.4.1 2018-05-10 11:32:54 +02:00
Timo Furrer bc06315abd Limit pendulum version to >=1.0 and <= 1.5.1
See https://github.com/sdispater/pendulum/issues/205
2018-05-10 11:32:32 +02:00
Timo Furrer 7cc767781a release: 0.4.0 2018-05-10 11:13:55 +02:00
Timo Furrer 8468dd2ead Fix subtracting MayaDT instances. Fixes #151 2018-05-10 11:04:16 +02:00
kennethreitz 046f005ca7 Update README.rst 2018-05-08 08:05:07 -04:00
kennethreitz cd0b2300d7 Merge pull request #128 from azban/patch-1
readme: add artist attribution
2018-05-08 08:04:05 -04:00
Timo Furrer d3ddb39d9d Merge pull request #144 from kennethreitz/feature/cleanup-pipenv-use
Refactor pipenv usage according to semi-official best practices.
2018-05-08 08:53:31 +02:00
Timo Furrer 3c8fe4478c Merge pull request #147 from alxwrd/fix-when-weekday-names
.when with weekdays and prefer_past
2018-04-17 17:58:27 +02:00
Alex Ward a0983132bb update .when docstring 2018-04-16 12:20:14 +01:00
Alex Ward 9612d70707 update .when tests to use new prefer_dates_from param 2018-04-16 12:06:12 +01:00
Alex Ward 363fae1aaf update .when to use prefer_dates_from to align with dateparser 2018-04-16 11:39:16 +01:00
Alex Ward be2cd96132 add more options to .when pefer_past param 2018-04-15 19:22:56 +01:00
Alex Ward 771a2b6ce2 add tests around weekday names 2018-04-15 17:31:06 +01:00
Timo Furrer 8e644f655a Merge pull request #145 from vlcinsky/pendulum_ver
fix `pendulum>=1.0` in setup.py (was missing version)
2018-04-05 10:17:55 +02:00
Jan Vlcinsky 6903f6eb63 fix pendulum>=1.0 in setup.py (was missing version)
Assuming, that when Pipfile declares such rule, it shall be also in
setup.py.
2018-04-05 00:50:53 +02:00
Timo Furrer ca076ff625 Refactor pipenv usage according to semi-official best practices. Closes #142 2018-04-04 20:42:13 +02:00
Jan Vlčinský e0e33cc29f Remove probably obsolete requirement for ruamel.yaml (#136)
* Remove probably obsolete requirement for ruamel.yaml

Closes issue #134

* require dateparser>=0.7.0 (to ensure ruamel.yaml is not really needed)
2018-04-04 13:37:31 +02:00
Timo Furrer bd34915f96 Merge pull request #135 from vlcinsky/frozen_time
test with "now" frozen to different times/zones
2018-04-03 20:08:57 +02:00
Jan Vlcinsky 16690cfceb Removed Pipfile.lock 2018-04-03 19:57:58 +02:00
Jan Vlcinsky 97af369f05 Pipfile.lock updated with --dev (freezegun) 2018-04-03 19:54:18 +02:00
Jan Vlcinsky 367dac4e62 number of frozen_now variants narrowed to speed up tests 2018-04-03 19:54:18 +02:00
Jan Vlcinsky 59ec275ef6 test with "now" frozen to different times/zones
Using freezegun to freeze current times.
2018-04-03 19:54:18 +02:00
Timo Furrer 76f99bc781 Merge pull request #141 from vlcinsky/simple_pytest
Simplified call to pytest in tox.ini (issue #139)
2018-04-03 16:40:26 +02:00
Timo Furrer bf56321545 Merge branch 'master' into simple_pytest 2018-04-03 16:35:23 +02:00
Timo Furrer ee602ea56b Merge pull request #140 from vlcinsky/tox_ini
Get rid of Pipfile.lock (issue #138)
2018-04-03 16:34:35 +02:00
Jan Vlcinsky df3bdf231d Simplified call to pytest in tox.ini (issue #139) 2018-04-02 22:50:14 +02:00
Jan Vlcinsky 1a58d4f710 Get rid of Pipfile.lock (issue #138) 2018-04-02 22:43:28 +02:00
Timo Furrer d0d5a8f75d Explicitly specifiy time struct when getting UTC offset for current time 2018-04-02 12:09:31 +02:00
Timo Furrer 00a0ceb3c8 Add tox.ini for local testing. Refs #137 2018-04-01 14:09:02 +02:00
Timo Furrer ee0fe3b810 Merge pull request #131 from vlcinsky/master
test_maya.py refactored: using parametrization where feasible
2018-03-31 11:50:32 +02:00
Timo Furrer 0e0815c45a Merge pull request #132 from kennethreitz/feature/python-3.7
Add Python 3.7 to test matrix
2018-03-31 11:49:07 +02:00
Jan Vlcinsky e9592a3146 test_maya.py refactored: using parametrization where feasible
repeated code replaced by parameters provided by pytest.

source string and rexpected intentionally indented to match by column
to make checking year, month and date simpler.

Note, that the test_issue_104 was (and still is) failing.
2018-03-31 01:31:14 +02:00
azban b05ca8707c readme: add artist attribution 2018-03-21 10:50:24 -07:00
40 changed files with 1543 additions and 937 deletions
+12
View File
@@ -0,0 +1,12 @@
[run]
branch = True
source =
maya
[paths]
source =
src
.tox/*/site-packages
[report]
show_missing = True
+46
View File
@@ -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/
+45
View File
@@ -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
```
+42
View File
@@ -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.
+29
View File
@@ -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.
+20
View File
@@ -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.
+82
View File
@@ -0,0 +1,82 @@
name: Continuous Integration and Deployment
on: [push]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
max-parallel: 4
matrix:
python-version: [2.7, 3.5, 3.6, 3.7]
os: [ubuntu-latest, macOS-latest]
steps:
- uses: actions/checkout@v1
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
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@v1
- name: Set up Python 3.7
uses: actions/setup-python@v1
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
steps:
- uses: actions/checkout@v1
- name: Set up Python 3.7
if: startsWith(github.event.ref, 'refs/tags')
uses: actions/setup-python@v1
with:
python-version: 3.7
- name: Build Package
if: startsWith(github.event.ref, 'refs/tags')
run: |
python -m pip install --upgrade pip setuptools wheel
python setup.py sdist bdist_wheel --universal
- name: Publish Package on PyPI
if: startsWith(github.event.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@master
with:
user: __token__
password: ${{ secrets.pypi_token }}
+18
View File
@@ -0,0 +1,18 @@
name: "Close Stale Issues and Pull Requests"
on:
schedule:
- cron: "0 * * * *"
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 60
days-before-close: 7
stale-issue-label: stale
stale-pr-label: state
stale-issue-message: 'This Issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days'
stale-pr-message: 'This Pull Request is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days'
+1
View File
@@ -24,6 +24,7 @@ wheels/
*.egg-info/ *.egg-info/
.installed.cfg .installed.cfg
*.egg *.egg
Pipfile.lock
# PyInstaller # PyInstaller
# Usually these files are written by a python script from a template # Usually these files are written by a python script from a template
+32
View File
@@ -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
-15
View File
@@ -1,15 +0,0 @@
language: python
python:
- "2.7"
- "3.6"
- "3.7-dev"
matrix:
allow_failures:
- python: "3.7-dev"
# command to install dependencies
install: pip install pipenv; pipenv lock; pipenv install --dev
# command to run tests
script: pipenv run pytest tests/
-26
View File
@@ -1,26 +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>`_)
- Dima Spivak <dima@spivak.ch> (`@dimaspivak <https://github.com/dimaspivak>`_)
- Tom Barron <tusculum@gmail.com> (`@dtbarron <https://github.com/tbarron>`_)
+507
View File
@@ -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
View File
@@ -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
-6
View File
@@ -1,6 +0,0 @@
.PHONY: tests docs
tests:
pytest tests/
docs:
cd docs && make html
-15
View File
@@ -1,15 +0,0 @@
[dev-packages]
pytest = "*"
sphinx = "*"
[packages]
humanize = "*"
pytz = "*"
dateparser = "*"
"ruamel.yaml" = "*"
tzlocal = "*"
pendulum = ">=1.0"
snaptime = "*"
Generated
-317
View File
@@ -1,317 +0,0 @@
{
"_meta": {
"hash": {
"sha256": "3abd33d2f9beea083faca673fa3864252623b284fa24f9a8b939aed12428318c"
},
"pipfile-spec": 6,
"requires": {},
"sources": [
{
"name": "pypi",
"url": "https://pypi.python.org/simple",
"verify_ssl": true
}
]
},
"default": {
"dateparser": {
"hashes": [
"sha256:940828183c937bcec530753211b70f673c0a9aab831e43273489b310538dff86",
"sha256:b452ef8b36cd78ae86a50721794bc674aa3994e19b570f7ba92810f4e0a2ae03"
],
"index": "pypi",
"version": "==0.7.0"
},
"humanize": {
"hashes": [
"sha256:a43f57115831ac7c70de098e6ac46ac13be00d69abbf60bdcac251344785bb19"
],
"index": "pypi",
"version": "==0.5.1"
},
"pendulum": {
"hashes": [
"sha256:0c14388546db6605a860b8b7112cb69d0b11c9ce5e072210504544e0d4575799",
"sha256:39a255776528afe11ea0d57814f9bf3729c1e0b99063af2e5c6cfd750c3e1f7f",
"sha256:3c85e8cbc91f45e1cc916cc9180b34153cd6aaaaacfb51a48b3156318314fa82",
"sha256:8199206c479b13947dcac63c025575d035331bb3819d1783dc1d568a11962906",
"sha256:8798aeca58b3dd7ffdc5a4993c9eaafedc4048165429e8f499ddd62c73bf3964",
"sha256:881efe37328de0785c0731d462e1485a45712f2cd5cb55907d6c15458460ebeb",
"sha256:bcca072f82e84b419efec1320cd3ee5c230d263f3a601b146651ed4db77d89f0",
"sha256:ff0c5fa3af4a471a218408c448b804ac6bccb105127727474f4e83c0e4072e97"
],
"index": "pypi",
"version": "==1.4.2"
},
"python-dateutil": {
"hashes": [
"sha256:07009062406cffd554a9b4135cd2ff167c9bf6b7aac61fe946c93e69fad1bbd8",
"sha256:8f95bb7e6edbb2456a51a1fb58c8dca942024b4f5844cae62c90aa88afe6e300"
],
"version": "==2.7.0"
},
"pytz": {
"hashes": [
"sha256:07edfc3d4d2705a20a6e99d97f0c4b61c800b8232dc1c04d87e8554f130148dd",
"sha256:3a47ff71597f821cd84a162e71593004286e5be07a340fd462f0d33a760782b5",
"sha256:410bcd1d6409026fbaa65d9ed33bf6dd8b1e94a499e32168acfc7b332e4095c0",
"sha256:5bd55c744e6feaa4d599a6cbd8228b4f8f9ba96de2c38d56f08e534b3c9edf0d",
"sha256:61242a9abc626379574a166dc0e96a66cd7c3b27fc10868003fa210be4bff1c9",
"sha256:887ab5e5b32e4d0c86efddd3d055c1f363cbaa583beb8da5e22d2fa2f64d51ef",
"sha256:ba18e6a243b3625513d85239b3e49055a2f0318466e0b8a92b8fb8ca7ccdf55f",
"sha256:ed6509d9af298b7995d69a440e2822288f2eca1681b8cce37673dbb10091e5fe",
"sha256:f93ddcdd6342f94cea379c73cddb5724e0d6d0a1c91c9bdef364dc0368ba4fda"
],
"index": "pypi",
"version": "==2018.3"
},
"pytzdata": {
"hashes": [
"sha256:4e2cceb54335cd6c28caea46b15cd592e2aec5e8b05b0241cbccfb1b23c02ae7",
"sha256:7cd949123e2c2060fd12793de3a4a449e36b5dea5e169b810a3ac3f0b9877cfa"
],
"version": "==2018.3"
},
"regex": {
"hashes": [
"sha256:1b428a296531ea1642a7da48562746309c5c06471a97bd0c02dd6a82e9cecee8",
"sha256:27d72bb42dffb32516c28d218bb054ce128afd3e18464f30837166346758af67",
"sha256:32cf4743debee9ea12d3626ee21eae83052763740e04086304e7a74778bf58c9",
"sha256:32f6408dbca35040bc65f9f4ae1444d5546411fde989cb71443a182dd643305e",
"sha256:333687d9a44738c486735955993f83bd22061a416c48f5a5f9e765e90cf1b0c9",
"sha256:35eeccf17af3b017a54d754e160af597036435c58eceae60f1dd1364ae1250c7",
"sha256:361a1fd703a35580a4714ec28d85e29780081a4c399a99bbfb2aee695d72aedb",
"sha256:494bed6396a20d3aa6376bdf2d3fbb1005b8f4339558d8ac7b53256755f80303",
"sha256:5b9c0ddd5b4afa08c9074170a2ea9b34ea296e32aeea522faaaaeeeb2fe0af2e",
"sha256:a50532f61b23d4ab9d216a6214f359dd05c911c1a1ad20986b6738a782926c1a",
"sha256:a9243d7b359b72c681a2c32eaa7ace8d346b7e8ce09d172a683acf6853161d9c",
"sha256:b44624a38d07d3c954c84ad302c29f7930f4bf01443beef5589e9157b14e2a29",
"sha256:be42a601aaaeb7a317f818490a39d153952a97c40c6e9beeb2a1103616405348",
"sha256:eee4d94b1a626490fc8170ffd788883f8c641b576e11ba9b4a29c9f6623371e0",
"sha256:f69d1201a4750f763971ea8364ed95ee888fc128968b39d38883a72a4d005895"
],
"version": "==2018.2.21"
},
"ruamel.yaml": {
"hashes": [
"sha256:01e30ecb1b1c0ebf9fce814dc20dace402571517277799291202b61b22096c24",
"sha256:02babffd019911841ba01b76e23dfec7c9e9b2725503fb2698c4982fa1a6e835",
"sha256:072f6364a89972e8dc0afdce3335a709d5464dfeaa4f736d092a54574338b874",
"sha256:14d161558e3bf89e87d77c218098be22fa9a0d6d0bea40250fce525b1d0cbee2",
"sha256:5504398fc755a2b14c9983b2101161a8591a4b30812590cc1c365e7fcc117dfa",
"sha256:68c8f2986bcb91b6db1aea8698941769840c7257e951a9377048f7eff35be773",
"sha256:6d05c5a5baf829c70916c226ef3200650846a7227de226bca8a59efaf88bb973",
"sha256:6d7929b24e329d662fa43b657fddfee5260e2d35d0a543065cd755d4e17a9b2f",
"sha256:8dc74821e4bb6b21fb1ab35964e159391d99ee44981d07d57bf96e2395f3ef75",
"sha256:9225c83952d28f302cfc23c3d9a6f8231bfd581476d7aff1e3c7de49eecb4ee9",
"sha256:b6c5d5f03ba78e3f27c7188a00c4e09b6a4507fe3154ba40a294e09cb30ee016",
"sha256:c0908896e34b617ead40552cab03c1769bdc43d1da02419160dc900c5dfddde2",
"sha256:c41e04b526d0153c9246cfab87d7ddefdc9f165cb8886a8ec48ba7a2b73069f6",
"sha256:e2d2715bf92156bec5fb42e92e95dac1c4d9904f8a3d4e2d0c438758fe9092d7",
"sha256:e3bbfe0d294e08fdbb0cb05485435a2ceb4e168e98b5dc611f051c1864986b4b",
"sha256:f2d02a4af5a13b09d0b823cdd0317b54f3e0115e50b5ac4d9840c3a1b566817f",
"sha256:fcfc24a21594c071cc4588e84b7657a1f47ebcf6037c6c43fa15c4bbd3989ec2"
],
"index": "pypi",
"version": "==0.15.35"
},
"six": {
"hashes": [
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
],
"version": "==1.11.0"
},
"snaptime": {
"hashes": [
"sha256:e3f1eb89043d58d30721ab98cb65023f1a4c2740e3b197704298b163c92d508b"
],
"index": "pypi",
"version": "==0.2.4"
},
"tzlocal": {
"hashes": [
"sha256:4ebeb848845ac898da6519b9b31879cf13b6626f7184c496037b818e238f2c4e"
],
"index": "pypi",
"version": "==1.5.1"
}
},
"develop": {
"alabaster": {
"hashes": [
"sha256:2eef172f44e8d301d25aff8068fddd65f767a3f04b5f15b0f4922f113aa1c732",
"sha256:37cdcb9e9954ed60912ebc1ca12a9d12178c26637abdf124e3cde2341c257fe0"
],
"version": "==0.7.10"
},
"attrs": {
"hashes": [
"sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9",
"sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450"
],
"version": "==17.4.0"
},
"babel": {
"hashes": [
"sha256:8ce4cb6fdd4393edd323227cba3a077bceb2a6ce5201c902c65e730046f41f14",
"sha256:ad209a68d7162c4cff4b29cdebe3dec4cef75492df501b0049a9433c96ce6f80"
],
"version": "==2.5.3"
},
"certifi": {
"hashes": [
"sha256:14131608ad2fd56836d33a71ee60fa1c82bc9d2c8d98b7bdbc631fe1b3cd1296",
"sha256:edbc3f203427eef571f79a7692bb160a2b0f7ccaa31953e99bd17e307cf63f7d"
],
"version": "==2018.1.18"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"docutils": {
"hashes": [
"sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6",
"sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274",
"sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6"
],
"version": "==0.14"
},
"idna": {
"hashes": [
"sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f",
"sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4"
],
"version": "==2.6"
},
"imagesize": {
"hashes": [
"sha256:3620cc0cadba3f7475f9940d22431fc4d407269f1be59ec9b8edcca26440cf18",
"sha256:5b326e4678b6925158ccc66a9fa3122b6106d7c876ee32d7de6ce59385b96315"
],
"version": "==1.0.0"
},
"jinja2": {
"hashes": [
"sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
"sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
],
"version": "==2.10"
},
"markupsafe": {
"hashes": [
"sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"
],
"version": "==1.0"
},
"packaging": {
"hashes": [
"sha256:e9215d2d2535d3ae866c3d6efc77d5b24a0192cce0ff20e42896cc0664f889c0",
"sha256:f019b770dd64e585a99714f1fd5e01c7a8f11b45635aa953fd41c689a657375b"
],
"version": "==17.1"
},
"pluggy": {
"hashes": [
"sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff"
],
"version": "==0.6.0"
},
"py": {
"hashes": [
"sha256:8cca5c229d225f8c1e3085be4fcf306090b00850fefad892f9d96c7b6e2f310f",
"sha256:ca18943e28235417756316bfada6cd96b23ce60dd532642690dcfdaba988a76d"
],
"version": "==1.5.2"
},
"pygments": {
"hashes": [
"sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d",
"sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc"
],
"version": "==2.2.0"
},
"pyparsing": {
"hashes": [
"sha256:0832bcf47acd283788593e7a0f542407bd9550a55a8a8435214a1960e04bcb04",
"sha256:281683241b25fe9b80ec9d66017485f6deff1af5cde372469134b56ca8447a07",
"sha256:8f1e18d3fd36c6795bb7e02a39fd05c611ffc2596c1e0d995d34d67630426c18",
"sha256:9e8143a3e15c13713506886badd96ca4b579a87fbdf49e550dbfc057d6cb218e",
"sha256:b8b3117ed9bdf45e14dcc89345ce638ec7e0e29b2b579fa1ecf32ce45ebac8a5",
"sha256:e4d45427c6e20a59bf4f88c639dcc03ce30d193112047f94012102f235853a58",
"sha256:fee43f17a9c4087e7ed1605bd6df994c6173c1e977d7ade7b651292fab2bd010"
],
"version": "==2.2.0"
},
"pytest": {
"hashes": [
"sha256:062027955bccbc04d2fcd5d79690947e018ba31abe4c90b2c6721abec734261b",
"sha256:117bad36c1a787e1a8a659df35de53ba05f9f3398fb9e4ac17e80ad5903eb8c5"
],
"index": "pypi",
"version": "==3.4.2"
},
"pytz": {
"hashes": [
"sha256:07edfc3d4d2705a20a6e99d97f0c4b61c800b8232dc1c04d87e8554f130148dd",
"sha256:3a47ff71597f821cd84a162e71593004286e5be07a340fd462f0d33a760782b5",
"sha256:410bcd1d6409026fbaa65d9ed33bf6dd8b1e94a499e32168acfc7b332e4095c0",
"sha256:5bd55c744e6feaa4d599a6cbd8228b4f8f9ba96de2c38d56f08e534b3c9edf0d",
"sha256:61242a9abc626379574a166dc0e96a66cd7c3b27fc10868003fa210be4bff1c9",
"sha256:887ab5e5b32e4d0c86efddd3d055c1f363cbaa583beb8da5e22d2fa2f64d51ef",
"sha256:ba18e6a243b3625513d85239b3e49055a2f0318466e0b8a92b8fb8ca7ccdf55f",
"sha256:ed6509d9af298b7995d69a440e2822288f2eca1681b8cce37673dbb10091e5fe",
"sha256:f93ddcdd6342f94cea379c73cddb5724e0d6d0a1c91c9bdef364dc0368ba4fda"
],
"index": "pypi",
"version": "==2018.3"
},
"requests": {
"hashes": [
"sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b",
"sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e"
],
"version": "==2.18.4"
},
"six": {
"hashes": [
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
],
"version": "==1.11.0"
},
"snowballstemmer": {
"hashes": [
"sha256:919f26a68b2c17a7634da993d91339e288964f93c274f1343e3bbbe2096e1128",
"sha256:9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89"
],
"version": "==1.2.1"
},
"sphinx": {
"hashes": [
"sha256:41ae26acc6130ccf6ed47e5cca73742b80d55a134f0ab897c479bba8d3640b8e",
"sha256:da987de5fcca21a4acc7f67a86a363039e67ac3e8827161e61b91deb131c0ee8"
],
"index": "pypi",
"version": "==1.7.1"
},
"sphinxcontrib-websupport": {
"hashes": [
"sha256:7a85961326aa3a400cd4ad3c816d70ed6f7c740acd7ce5d78cd0a67825072eb9",
"sha256:f4932e95869599b89bf4f80fc3989132d83c9faa5bf633e7b5e0c25dffb75da2"
],
"version": "==1.0.1"
},
"urllib3": {
"hashes": [
"sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b",
"sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f"
],
"version": "==1.22"
}
}
}
+23 -18
View File
@@ -4,11 +4,8 @@ Maya: Datetimes for Humans™
.. image:: https://img.shields.io/pypi/v/maya.svg .. image:: https://img.shields.io/pypi/v/maya.svg
:target: https://pypi.python.org/pypi/maya :target: https://pypi.python.org/pypi/maya
.. image:: https://travis-ci.org/kennethreitz/maya.svg?branch=master .. image:: https://github.com/timofurrer/maya/workflows/Continuous%20Integration%20and%20Deployment/badge.svg
:target: https://travis-ci.org/kennethreitz/maya :target: https://github.com/timofurrer/maya/actions
.. image:: https://img.shields.io/badge/SayThanks-!-1EAEDB.svg
:target: https://saythanks.io/to/kennethreitz
Datetimes are very frustrating to work with in Python, especially when dealing Datetimes are very frustrating to work with in Python, especially when dealing
@@ -20,8 +17,6 @@ Datetimes should be interacted with via an API written for humans.
Maya is mostly built around the headaches and use-cases around parsing datetime data from websites. Maya is mostly built around the headaches and use-cases around parsing datetime data from websites.
.. image:: https://farm4.staticflickr.com/3702/33288285996_5b69d2b8f7_k_d.jpg
☤ Basic Usage of Maya ☤ Basic Usage of Maya
--------------------- ---------------------
@@ -74,6 +69,10 @@ Behold, datetimes for humans!
>>> print(m) >>> print(m)
Wed, 20 Sep 2017 17:24:32 GMT 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 >>> rand_day.day
7 7
@@ -126,10 +125,10 @@ From here, there are a number of methods available to you, which you can use to
- Maya never panics, and always carries a towel. - Maya never panics, and always carries a towel.
☤ What about Delorean, Arrow, & Pendulum? ☤ What about Delorean_, Arrow_, & Pendulum_?
----------------------------------------- -----------------------------------------
All these project complement each other, 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. 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.
@@ -137,20 +136,27 @@ 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! 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 ☤ Installing Maya
----------------- -----------------
Installation is easy, with `pipenv <http://pipenv.org/>`_:: Installation is easy, with:
$ pipenv install maya $ pip install maya
✨🍰✨
Like it? Demo
---------- ------
`Say Thanks <https://saythanks.io/to/kennethreitz>`_! Try ``maya`` interactively using this online demo:
.. image:: https://user-images.githubusercontent.com/1155573/49400125-16e73500-f722-11e8-9275-e1d7eb3bbf99.png
:target: https://notebooks.ai/demo/gh/kennethreitz/maya
:alt: Open Live Demo
How to Contribute How to Contribute
@@ -159,7 +165,6 @@ How to Contribute
#. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug. #. 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). #. 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. #. 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 .. _`the repository`: http://github.com/timofurrer/maya
.. _AUTHORS: https://github.com/kennethreitz/maya/blob/master/AUTHORS.rst
+2
View File
@@ -0,0 +1,2 @@
requirements:
- maya==0.6.0
View File
View File
+32 -33
View File
@@ -19,11 +19,12 @@
# #
import os import os
import sys 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 ------------------------------------------------ # -- General configuration ------------------------------------------------
@@ -34,27 +35,29 @@ import maya
# Add any Sphinx extension module names here, as strings. They can be # Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones. # ones.
extensions = ['sphinx.ext.autodoc', extensions = [
'sphinx.ext.todo', "sphinx.ext.autodoc",
'sphinx.ext.coverage', "sphinx.ext.todo",
'sphinx.ext.viewcode'] "sphinx.ext.coverage",
"sphinx.ext.viewcode",
]
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates'] templates_path = ["_templates"]
# The suffix(es) of source filenames. # The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string: # You can specify multiple suffix as a list of string:
# #
# source_suffix = ['.rst', '.md'] # source_suffix = ['.rst', '.md']
source_suffix = '.rst' source_suffix = ".rst"
# The master toctree document. # The master toctree document.
master_doc = 'index' master_doc = "index"
# General information about the project. # General information about the project.
project = 'maya' project = "maya"
copyright = '2017, Kenneth Reitz' copyright = "2017, Kenneth Reitz"
author = 'Kenneth Reitz' author = "Kenneth Reitz"
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the
@@ -78,7 +81,7 @@ language = None
exclude_patterns = [] exclude_patterns = []
# The name of the Pygments (syntax highlighting) style to use. # 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. # If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True 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 # The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes. # 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 # 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 # 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, # 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, # relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css". # so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static'] # html_static_path = ["_static"]
# -- Options for HTMLHelp output ------------------------------------------ # -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder. # Output file base name for HTML help builder.
htmlhelp_basename = 'mayadoc' htmlhelp_basename = "mayadoc"
# -- Options for LaTeX output --------------------------------------------- # -- Options for LaTeX output ---------------------------------------------
@@ -115,15 +118,12 @@ latex_elements = {
# The paper size ('letterpaper' or 'a4paper'). # The paper size ('letterpaper' or 'a4paper').
# #
# 'papersize': 'letterpaper', # 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt'). # The font size ('10pt', '11pt' or '12pt').
# #
# 'pointsize': '10pt', # 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble. # Additional stuff for the LaTeX preamble.
# #
# 'preamble': '', # 'preamble': '',
# Latex figure (float) alignment # Latex figure (float) alignment
# #
# 'figure_align': 'htbp', # 'figure_align': 'htbp',
@@ -133,8 +133,7 @@ latex_elements = {
# (source start file, target name, title, # (source start file, target name, title,
# author, documentclass [howto, manual, or own class]). # author, documentclass [howto, manual, or own class]).
latex_documents = [ latex_documents = [
(master_doc, 'maya.tex', 'maya Documentation', (master_doc, "maya.tex", "maya Documentation", "Kenneth Reitz", "manual")
'Kenneth Reitz', 'manual'),
] ]
@@ -142,10 +141,7 @@ latex_documents = [
# One entry per manual page. List of tuples # One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section). # (source start file, name, description, authors, manual section).
man_pages = [ man_pages = [(master_doc, "maya", "maya Documentation", [author], 1)]
(master_doc, 'maya', 'maya Documentation',
[author], 1)
]
# -- Options for Texinfo output ------------------------------------------- # -- Options for Texinfo output -------------------------------------------
@@ -154,10 +150,13 @@ man_pages = [
# (source start file, target name, title, author, # (source start file, target name, title, author,
# dir menu entry, description, category) # dir menu entry, description, category)
texinfo_documents = [ texinfo_documents = [
(master_doc, 'maya', 'maya Documentation', (
author, 'maya', 'One line description of project.', master_doc,
'Miscellaneous'), "maya",
"maya Documentation",
author,
"maya",
"One line description of project.",
"Miscellaneous",
)
] ]
-1
View File
@@ -80,4 +80,3 @@ Table of Contents
user/install user/install
user/quickstart user/quickstart
View File
+2 -3
View File
@@ -1,6 +1,7 @@
.. _install: .. _install:
Installation Installation
============= ============
Pip Install Maya 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:: package, or install it into your site-packages easily::
$ python setup.py install $ python setup.py install
View File
+1 -9
View File
@@ -1,4 +1,5 @@
.. _quickstart: .. _quickstart:
Quickstart Quickstart
========== ==========
@@ -7,12 +8,6 @@ Quickstart
Ready for a simple datetime tool? This doc provides some tools to use in your Ready for a simple datetime tool? This doc provides some tools to use in your
busy workflow. busy workflow.
First, make sure that Maya is:
- :ref:`Installed <install>`
- :ref:`Up to date <update>`
Parse a Date Parse a Date
------------ ------------
Parsing a date from a string with Maya is 🍰! Parsing a date from a string with Maya is 🍰!
@@ -33,6 +28,3 @@ Use as follows::
>>> recent_win = maya.parse('2016-11-02T20:00PM') >>> recent_win = maya.parse('2016-11-02T20:00PM')
>>> old_win = maya.when('October 14, 1908') >>> old_win = maya.when('October 14, 1908')
>>> grandpas_date = maya.when('108 years ago') >>> grandpas_date = maya.when('108 years ago')
-27
View File
@@ -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
-2
View File
@@ -1,2 +0,0 @@
from .core import *
from .__version__ import __version__
-1
View File
@@ -1 +0,0 @@
__version__ = '0.3.4'
+16
View File
@@ -1,2 +1,18 @@
[bdist_wheel] [bdist_wheel]
universal = 1 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
Executable → Regular
+80 -95
View File
@@ -1,107 +1,92 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import io
import os
import sys
import codecs import codecs
from shutil import rmtree import os
import re
from setuptools import find_packages, setup, Command from setuptools import find_packages, setup
#: Holds a list of packages to install with the binary distribution
here = os.path.abspath(os.path.dirname(__file__)) PACKAGES = find_packages(where="src")
META_FILE = os.path.abspath("src/maya/__init__.py")
with codecs.open(os.path.join(here, 'README.rst'), encoding='utf-8') as f: KEYWORDS = ["datetime", "timezone", "scrape", "web"]
long_description = '\n' + f.read() CLASSIFIERS = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
if sys.argv[-1] == "publish": "License :: OSI Approved :: MIT License",
os.system("python setup.py sdist bdist_wheel upload") "Natural Language :: English",
sys.exit() "Operating System :: OS Independent",
"Programming Language :: Python :: 2",
required = [ "Programming Language :: Python :: 2.7",
'humanize', "Programming Language :: Python :: 3",
'pytz', "Programming Language :: Python :: 3.6",
'dateparser', "Programming Language :: Python :: Implementation",
'ruamel.yaml', "Programming Language :: Python :: Implementation :: CPython",
'tzlocal', "Topic :: Software Development :: Libraries :: Python Modules",
'pendulum',
'snaptime'
] ]
packages = [ #: Holds the runtime requirements for the end user
'maya', 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"]
)
class UploadCommand(Command): #: Holds the contents of the README file
"""Support setup.py upload.""" with codecs.open("README.rst", encoding="utf-8") as readme:
__README_CONTENTS__ = readme.read()
description = 'Build and publish the package.'
user_options = []
@staticmethod
def status(s):
"""Prints things in bold."""
print('\033[1m{0}\033[0m'.format(s))
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
try:
self.status('Removing previous builds…')
rmtree(os.path.join(here, 'dist'))
except OSError:
pass
self.status('Building Source and Wheel (universal) distribution…')
os.system('{0} setup.py sdist bdist_wheel --universal'.format(sys.executable))
self.status('Uploading the package to PyPi via Twine…')
os.system('twine upload dist/*')
self.status('Pushing git tags…')
os.system('git tag v{0}'.format(about['__version__']))
os.system('git push --tags')
sys.exit()
# About dict to store version and package info def read(metafile):
about = dict() """
with codecs.open(os.path.join(here, 'maya', '__version__.py'), 'r', encoding='utf-8') as f: Return the contents of the given meta data file assuming UTF-8 encoding.
exec(f.read(), about) """
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))
setup( setup(
name='maya', name="maya",
version=about['__version__'], version=get_meta("version", META_FILE),
description='Datetimes for Humans.', license=get_meta("license", META_FILE),
long_description=long_description, description=get_meta("description", META_FILE),
author='Kenneth Reitz', long_description=__README_CONTENTS__,
author_email='me@kennethreitz.org', author=get_meta("author", META_FILE),
url='https://github.com/kennethreitz/maya', author_email=get_meta("author_email", META_FILE),
packages=packages, maintainer=get_meta("author", META_FILE),
install_requires=required, maintainer_email=get_meta("author_email", META_FILE),
license='MIT', platforms=["Linux", "Windows", "MAC OS X"],
classifiers=( url=get_meta("url", META_FILE),
'Development Status :: 5 - Production/Stable', download_url=get_meta("download_url", META_FILE),
'Intended Audience :: Developers', bugtrack_url=get_meta("bugtrack_url", META_FILE),
'License :: OSI Approved :: MIT License', packages=PACKAGES,
'Natural Language :: English', package_dir={"": "src"},
'Operating System :: OS Independent', include_package_data=True,
'Programming Language :: Python :: 2', install_requires=INSTALL_REQUIRES,
'Programming Language :: Python :: 2.7', extras_require=EXTRAS_REQUIRES,
'Programming Language :: Python :: 3', keywords=KEYWORDS,
'Programming Language :: Python :: 3.6', classifiers=CLASSIFIERS,
'Programming Language :: Python :: Implementation',
'Programming Language :: Python :: Implementation :: CPython',
'Topic :: Software Development :: Libraries :: Python Modules'
),
cmdclass={
'upload': UploadCommand,
},
) )
+10
View File
@@ -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
+4 -3
View File
@@ -14,14 +14,15 @@ import sys
# Syntax sugar. # Syntax sugar.
_ver = sys.version_info _ver = sys.version_info
# : Python 2.x? # : Python 2.x?
is_py2 = (_ver[0] == 2) is_py2 = _ver[0] == 2
# : Python 3.x? # : Python 3.x?
is_py3 = (_ver[0] == 3) is_py3 = _ver[0] == 3
# --------- # ---------
# Specifics # Specifics
# --------- # ---------
if is_py2: if is_py2:
cmp = cmp cmp = cmp # noqa
elif is_py3: elif is_py3:
def cmp(a, b): def cmp(a, b):
+134 -122
View File
@@ -1,12 +1,6 @@
# ___ __ ___ _ _ ___ # ___ __ ___ _ _ ___
# || \/ | ||=|| \\// ||=|| # || \/ | ||=|| \\// ||=||
# || | || || // || || # || | || || // || ||
# Ignore warnings for yaml usage.
import warnings
import ruamel.yaml
warnings.simplefilter('ignore', ruamel.yaml.error.UnsafeLoaderWarning)
import email.utils import email.utils
import time import time
import functools import functools
@@ -20,6 +14,7 @@ import pendulum
import snaptime import snaptime
from tzlocal import get_localzone from tzlocal import get_localzone
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from dateparser.languages.loader import default_loader
from .compat import cmp, comparable from .compat import cmp, comparable
@@ -31,12 +26,11 @@ def validate_class_type_arguments(operator):
""" """
def inner(function): def inner(function):
def wrapper(self, *args, **kwargs): def wrapper(self, *args, **kwargs):
for arg in args + tuple(kwargs.values()): for arg in args + tuple(kwargs.values()):
if not isinstance(arg, self.__class__): if not isinstance(arg, self.__class__):
raise TypeError( raise TypeError(
'unorderable types: {}() {} {}()'.format( "unorderable types: {}() {} {}()".format(
type(self).__name__, operator, type(arg).__name__ type(self).__name__, operator, type(arg).__name__
) )
) )
@@ -59,20 +53,15 @@ def validate_arguments_type_of_function(param_type=None):
""" """
def inner(function): def inner(function):
def wrapper(self, *args, **kwargs): def wrapper(self, *args, **kwargs):
type_ = param_type or type(self) type_ = param_type or type(self)
for arg in args + tuple(kwargs.values()): for arg in args + tuple(kwargs.values()):
if not isinstance(arg, type_): if not isinstance(arg, type_):
raise TypeError( raise TypeError(
( (
'Invalid Type: {}.{}() accepts only the ' "Invalid Type: {}.{}() accepts only the "
'arguments of type "<{}>"' 'arguments of type "<{}>"'
).format( ).format(type(self).__name__, function.__name__, type_.__name__)
type(self).__name__,
function.__name__,
type_.__name__,
)
) )
return function(self, *args, **kwargs) return function(self, *args, **kwargs)
@@ -84,14 +73,18 @@ def validate_arguments_type_of_function(param_type=None):
class MayaDT(object): class MayaDT(object):
"""The Maya Datetime object.""" """The Maya Datetime object."""
__EPOCH_START = (1970, 1, 1) __EPOCH_START = (1970, 1, 1)
def __init__(self, epoch): def __init__(self, epoch):
super(MayaDT, self).__init__() super(MayaDT, self).__init__()
self._epoch = epoch # round the given epoch's microseconds to 6 digits
# to be compatible with Python's datetime.microseconds.
# See: https://docs.python.org/3/library/datetime.html#datetime.datetime.microsecond
self._epoch = round(epoch, 6)
def __repr__(self): def __repr__(self):
return '<MayaDT epoch={}>'.format(self._epoch) return "<MayaDT epoch={}>".format(self._epoch)
def __str__(self): def __str__(self):
return self.rfc2822() return self.rfc2822()
@@ -100,37 +93,35 @@ class MayaDT(object):
"""Return's the datetime's format""" """Return's the datetime's format"""
return format(self.datetime(), *args, **kwargs) return format(self.datetime(), *args, **kwargs)
@validate_class_type_arguments('==') @validate_class_type_arguments("==")
def __eq__(self, maya_dt): def __eq__(self, maya_dt):
return int(self._epoch) == int(maya_dt._epoch) return self._epoch == maya_dt._epoch
@validate_class_type_arguments('!=') @validate_class_type_arguments("!=")
def __ne__(self, maya_dt): def __ne__(self, maya_dt):
return int(self._epoch) != int(maya_dt._epoch) return self._epoch != maya_dt._epoch
@validate_class_type_arguments('<') @validate_class_type_arguments("<")
def __lt__(self, maya_dt): def __lt__(self, maya_dt):
return int(self._epoch) < int(maya_dt._epoch) return self._epoch < maya_dt._epoch
@validate_class_type_arguments('<=') @validate_class_type_arguments("<=")
def __le__(self, maya_dt): def __le__(self, maya_dt):
return int(self._epoch) <= int(maya_dt._epoch) return self._epoch <= maya_dt._epoch
@validate_class_type_arguments('>') @validate_class_type_arguments(">")
def __gt__(self, maya_dt): def __gt__(self, maya_dt):
return int(self._epoch) > int(maya_dt._epoch) return self._epoch > maya_dt._epoch
@validate_class_type_arguments('>=') @validate_class_type_arguments(">=")
def __ge__(self, maya_dt): def __ge__(self, maya_dt):
return int(self._epoch) >= int(maya_dt._epoch) return self._epoch >= maya_dt._epoch
def __hash__(self): def __hash__(self):
return hash(int(self.epoch)) return hash(self.epoch)
def __add__(self, duration): def __add__(self, duration):
return self.add( return self.add(seconds=_seconds_or_timedelta(duration).total_seconds())
seconds=_seconds_or_timedelta(duration).total_seconds()
)
def __radd__(self, duration): def __radd__(self, duration):
return self + duration return self + duration
@@ -146,19 +137,15 @@ class MayaDT(object):
def add(self, **kwargs): 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( return self.from_datetime(pendulum.instance(self.datetime()).add(**kwargs))
pendulum.instance(self.datetime()).add(**kwargs)
)
def subtract(self, **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( return self.from_datetime(pendulum.instance(self.datetime()).subtract(**kwargs))
pendulum.instance(self.datetime()).subtract(**kwargs)
)
def subtract_date(self, **kwargs): def subtract_date(self, **kwargs):
"""Returns a timedelta object with the duration between the dates""" """Returns a timedelta object with the duration between the dates"""
return timedelta(self.epoch - kwargs['dt'].epoch) return timedelta(seconds=self.epoch - kwargs["dt"].epoch)
def snap(self, instruction): def snap(self, instruction):
""" """
@@ -174,7 +161,7 @@ class MayaDT(object):
@property @property
def timezone(self): def timezone(self):
"""Returns the UTC tzinfo name. It's always UTC. Always.""" """Returns the UTC tzinfo name. It's always UTC. Always."""
return 'UTC' return "UTC"
@property @property
def _tz(self): def _tz(self):
@@ -201,7 +188,7 @@ class MayaDT(object):
# Assume UTC if no datetime is provided. # Assume UTC if no datetime is provided.
if dt.tzinfo is None: if dt.tzinfo is None:
dt = dt.replace(tzinfo=pytz.utc) dt = dt.replace(tzinfo=pytz.utc)
epoch_start = Datetime(*MayaDT.__EPOCH_START, tzinfo=pytz.timezone('UTC')) epoch_start = Datetime(*MayaDT.__EPOCH_START, tzinfo=pytz.timezone("UTC"))
return (dt - epoch_start).total_seconds() return (dt - epoch_start).total_seconds()
# Importers # Importers
@@ -263,11 +250,19 @@ class MayaDT(object):
dt = dt.replace(tzinfo=self._tz) dt = dt.replace(tzinfo=self._tz)
return dt 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): def iso8601(self):
"""Returns an ISO 8601 representation of the MayaDT.""" """Returns an ISO 8601 representation of the MayaDT."""
# Get a timezone-naive datetime. # Get a timezone-naive datetime.
dt = self.datetime(naive=True) dt = self.datetime(naive=True)
return '{}Z'.format(dt.isoformat()) return "{}Z".format(dt.isoformat())
def rfc2822(self): def rfc2822(self):
"""Returns an RFC 2822 representation of the MayaDT.""" """Returns an RFC 2822 representation of the MayaDT."""
@@ -275,7 +270,7 @@ class MayaDT(object):
def rfc3339(self): def rfc3339(self):
"""Returns an RFC 3339 representation of the MayaDT.""" """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"
# Properties # Properties
# ---------- # ----------
@@ -329,15 +324,39 @@ class MayaDT(object):
# Human Slang Extras # Human Slang Extras
# ------------------ # ------------------
def slang_date(self): def slang_date(self, locale="en"):
""""Returns human slang representation of date.""" """"Returns human slang representation of date.
dt = self.datetime(naive=True, to_timezone=self.local_timezone)
return humanize.naturaldate(dt)
def slang_time(self): Keyword Arguments:
""""Returns human slang representation of time.""" locale -- locale to translate to, e.g. 'fr' for french.
dt = self.datetime(naive=True, to_timezone=self.local_timezone) (default: 'en' - English)
return humanize.naturaltime(dt) """
dt = pendulum.instance(self.datetime())
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): def utc_offset(time_struct=None):
@@ -376,7 +395,7 @@ def to_utc_offset_aware(dt):
def to_iso8601(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): def end_of_day_midnight(dt):
@@ -384,10 +403,7 @@ def end_of_day_midnight(dt):
return dt return dt
else: else:
return ( return dt.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=1)
dt.replace(hour=0, minute=0, second=0, microsecond=0) +
timedelta(days=1)
)
@comparable @comparable
@@ -409,9 +425,7 @@ class MayaInterval(object):
) )
assert not all((start, end, duration is not None)) assert not all((start, end, duration is not None))
except AssertionError: except AssertionError:
raise ValueError( raise ValueError("Exactly 2 of start, end, and duration must be specified")
'Exactly 2 of start, end, and duration must be specified'
)
# Convert duration to timedelta if seconds were provided. # Convert duration to timedelta if seconds were provided.
if duration: if duration:
@@ -421,25 +435,23 @@ class MayaInterval(object):
if not end: if not end:
end = start + duration end = start + duration
if start > end: if start > end:
raise ValueError('MayaInterval cannot end before it starts') raise ValueError("MayaInterval cannot end before it starts")
self.start = start self.start = start
self.end = end self.end = end
def __repr__(self): def __repr__(self):
return '<MayaInterval start={0!r} end={1!r}>'.format( return "<MayaInterval start={0!r} end={1!r}>".format(self.start, self.end)
self.start, self.end
)
def iso8601(self): def iso8601(self):
"""Returns an ISO 8601 representation of the MayaInterval.""" """Returns an ISO 8601 representation of the MayaInterval."""
return '{0}/{1}'.format(self.start.iso8601(), self.end.iso8601()) return "{0}/{1}".format(self.start.iso8601(), self.end.iso8601())
@classmethod @classmethod
def parse_iso8601_duration(cls, duration, start=None, end=None): def parse_iso8601_duration(cls, duration, start=None, end=None):
match = re.match( 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)?)?)', 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 duration,
) )
time_components = {} time_components = {}
@@ -461,7 +473,7 @@ class MayaInterval(object):
@classmethod @classmethod
def from_iso8601(cls, s): def from_iso8601(cls, s):
# # Start and end, such as "2007-03-01T13:00:00Z/2008-05-11T15:30:00Z" # # Start and end, such as "2007-03-01T13:00:00Z/2008-05-11T15:30:00Z"
start, end = s.split('/') start, end = s.split("/")
try: try:
start = parse(start) start = parse(start)
except pendulum.parsing.exceptions.ParserError: except pendulum.parsing.exceptions.ParserError:
@@ -470,7 +482,7 @@ class MayaInterval(object):
try: try:
end = parse(end) end = parse(end)
except pendulum.parsing.exceptions.ParserError as e: except (pendulum.parsing.exceptions.ParserError, TypeError):
end = cls.parse_iso8601_duration(end, start=start) end = cls.parse_iso8601_duration(end, start=start)
return cls(start=start, end=end) return cls(start=start, end=end)
@@ -488,9 +500,7 @@ class MayaInterval(object):
@validate_arguments_type_of_function() @validate_arguments_type_of_function()
def __eq__(self, maya_interval): def __eq__(self, maya_interval):
return ( return self.start == maya_interval.start and self.end == maya_interval.end
self.start == maya_interval.start and self.end == maya_interval.end
)
def __hash__(self): def __hash__(self):
return hash((self.start, self.end)) return hash((self.start, self.end))
@@ -501,10 +511,7 @@ class MayaInterval(object):
@validate_arguments_type_of_function() @validate_arguments_type_of_function()
def __cmp__(self, maya_interval): def __cmp__(self, maya_interval):
return ( return cmp(self.start, maya_interval.start) or cmp(self.end, maya_interval.end)
cmp(self.start, maya_interval.start)
or cmp(self.end, maya_interval.end)
)
@property @property
def duration(self): def duration(self):
@@ -559,7 +566,7 @@ class MayaInterval(object):
# Convert seconds to timedelta, if appropriate. # Convert seconds to timedelta, if appropriate.
duration = _seconds_or_timedelta(duration) duration = _seconds_or_timedelta(duration)
if duration <= timedelta(seconds=0): 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 start = self.start
while start < self.end: while start < self.end:
@@ -571,22 +578,18 @@ class MayaInterval(object):
start += duration start += duration
def quantize(self, duration, snap_out=False, timezone='UTC'): def quantize(self, duration, snap_out=False, timezone="UTC"):
"""Returns a quanitzed interval.""" """Returns a quanitzed interval."""
# Convert seconds to timedelta, if appropriate. # Convert seconds to timedelta, if appropriate.
duration = _seconds_or_timedelta(duration) duration = _seconds_or_timedelta(duration)
timezone = pytz.timezone(timezone) timezone = pytz.timezone(timezone)
if duration <= timedelta(seconds=0): 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)) epoch = timezone.localize(Datetime(1970, 1, 1))
seconds = int(duration.total_seconds()) seconds = int(duration.total_seconds())
start_seconds = int( start_seconds = int((self.start.datetime(naive=False) - epoch).total_seconds())
(self.start.datetime(naive=False) - epoch).total_seconds() end_seconds = int((self.end.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: if start_seconds % seconds and not snap_out:
start_seconds += seconds start_seconds += seconds
if end_seconds % seconds and snap_out: if end_seconds % seconds and snap_out:
@@ -606,15 +609,13 @@ class MayaInterval(object):
start = max(self.start, maya_interval.start) start = max(self.start, maya_interval.start)
end = min(self.end, maya_interval.end) end = min(self.end, maya_interval.end)
either_instant = self.is_instant or maya_interval.is_instant 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): if (either_instant and instant_overlap) or (start < end):
return MayaInterval(start, end) return MayaInterval(start, end)
@validate_arguments_type_of_function() @validate_arguments_type_of_function()
def contains(self, maya_interval): def contains(self, maya_interval):
return ( return self.start <= maya_interval.start and self.end >= maya_interval.end
self.start <= maya_interval.start and self.end >= maya_interval.end
)
def __contains__(self, maya_dt): def __contains__(self, maya_dt):
if isinstance(maya_dt, MayaDT): if isinstance(maya_dt, MayaDT):
@@ -627,14 +628,13 @@ class MayaInterval(object):
@validate_arguments_type_of_function() @validate_arguments_type_of_function()
def is_adjacent(self, maya_interval): def is_adjacent(self, maya_interval):
return ( return self.start == maya_interval.end or self.end == maya_interval.start
self.start == maya_interval.end or self.end == maya_interval.start
)
@property @property
def icalendar(self): def icalendar(self):
ical_dt_format = '%Y%m%dT%H%M%SZ' ical_dt_format = "%Y%m%dT%H%M%SZ"
return """ return (
"""
BEGIN:VCALENDAR BEGIN:VCALENDAR
VERSION:2.0 VERSION:2.0
BEGIN:VEVENT BEGIN:VEVENT
@@ -645,24 +645,19 @@ class MayaInterval(object):
""".format( """.format(
self.start.datetime().strftime(ical_dt_format), self.start.datetime().strftime(ical_dt_format),
self.end.datetime().strftime(ical_dt_format), self.end.datetime().strftime(ical_dt_format),
).replace( )
' ', '' .replace(" ", "")
).strip( .strip("\r\n")
'\r\n' .replace("\n", "\r\n")
).replace(
'\n', '\r\n'
) )
@staticmethod @staticmethod
def flatten(interval_list): def flatten(interval_list):
return functools.reduce( return functools.reduce(
lambda reduced, lambda reduced, maya_interval: (
maya_interval: ( (reduced[:-1] + maya_interval.combine(reduced[-1]))
( if reduced
reduced[:-1] + maya_interval.combine(reduced[-1]) else [maya_interval]
) if reduced else [
maya_interval
]
), ),
sorted(interval_list), sorted(interval_list),
[], [],
@@ -681,7 +676,7 @@ def now():
return MayaDT(epoch=epoch) return MayaDT(epoch=epoch)
def when(string, timezone='UTC', prefer_past=False): def when(string, timezone="UTC", prefer_dates_from="current_period"):
""""Returns a MayaDT instance for the human moment specified. """"Returns a MayaDT instance for the human moment specified.
Powered by dateparser. Useful for scraping websites. Powered by dateparser. Useful for scraping websites.
@@ -692,24 +687,28 @@ def when(string, timezone='UTC', prefer_past=False):
Keyword Arguments: Keyword Arguments:
string -- string to be parsed string -- string to be parsed
timezone -- timezone referenced from (default: 'UTC') timezone -- timezone referenced from (default: 'UTC')
prefer_past -- prefer parsing ambiguous date as in the past prefer_dates_from -- what dates are prefered when `string` is ambigous.
options are 'past', 'future', and 'current_period'
(default: 'current_period'). see: [1]
Reference:
[1] dateparser.readthedocs.io/en/latest/usage.html#handling-incomplete-dates
""" """
settings = { settings = {
'TIMEZONE': timezone, "TIMEZONE": timezone,
'RETURN_AS_TIMEZONE_AWARE': True, "RETURN_AS_TIMEZONE_AWARE": True,
'TO_TIMEZONE': 'UTC', "TO_TIMEZONE": "UTC",
"PREFER_DATES_FROM": prefer_dates_from,
} }
if prefer_past:
settings['PREFER_DATES_FROM'] = 'past'
dt = dateparser.parse(string, settings=settings) dt = dateparser.parse(string, settings=settings)
if dt is None: if dt is None:
raise ValueError('invalid datetime input specified.') raise ValueError("invalid datetime input specified.")
return MayaDT.from_datetime(dt) return MayaDT.from_datetime(dt)
def parse(string, timezone='UTC', day_first=False, year_first=True): def parse(string, timezone="UTC", day_first=False, year_first=True, strict=False):
""""Returns a MayaDT instance for the machine-produced moment specified. """"Returns a MayaDT instance for the machine-produced moment specified.
Powered by pendulum. Powered by pendulum.
@@ -724,11 +723,14 @@ def parse(string, timezone='UTC', day_first=False, year_first=True):
between YDM and YMD. (default: False) between YDM and YMD. (default: False)
year_first -- if true, the first value (e.g. 2016/05/01) year_first -- if true, the first value (e.g. 2016/05/01)
is parsed as year (default: True) is parsed as year (default: True)
strict -- if False, allow pendulum to fall back on datetime parsing
if pendulum's own parsing fails
""" """
options = {} options = {}
options['tz'] = timezone options["tz"] = timezone
options['day_first'] = day_first options["day_first"] = day_first
options['year_first'] = year_first options["year_first"] = year_first
options["strict"] = strict
dt = pendulum.parse(str(string), **options) dt = pendulum.parse(str(string), **options)
return MayaDT.from_datetime(dt) return MayaDT.from_datetime(dt)
@@ -746,13 +748,23 @@ def _seconds_or_timedelta(duration):
dt_timedelta = duration dt_timedelta = duration
else: else:
raise TypeError( raise TypeError(
'Expects argument as `datetime.timedelta` object ' "Expects argument as `datetime.timedelta` object "
'or seconds in `int` format' "or seconds in `int` format"
) )
return dt_timedelta 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): def intervals(start, end, interval):
""" """
Yields MayaDT objects between the start and end MayaDTs given, Yields MayaDT objects between the start and end MayaDTs given,
@@ -763,4 +775,4 @@ def intervals(start, end, interval):
while current_timestamp.epoch < end.epoch: while current_timestamp.epoch < end.epoch:
yield current_timestamp yield current_timestamp
current_timestamp = current_timestamp.add(seconds=interval.seconds) current_timestamp = current_timestamp.add(seconds=interval.total_seconds())
+25
View File
@@ -0,0 +1,25 @@
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
+170 -96
View File
@@ -1,5 +1,6 @@
import copy import copy
import time import time
import calendar
from datetime import timedelta, datetime as Datetime from datetime import timedelta, datetime as Datetime
import pytz import pytz
@@ -9,51 +10,44 @@ import maya
from maya.core import _seconds_or_timedelta # import private function from maya.core import _seconds_or_timedelta # import private function
def test_rfc2822(): @pytest.mark.parametrize(
r = maya.parse('February 21, 1994').rfc2822() "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) d = maya.MayaDT.from_rfc2822(r)
assert r == 'Mon, 21 Feb 1994 00:00:00 GMT' assert r == expected
assert r == d.rfc2822() assert r == d.rfc2822()
def test_iso8601(): @pytest.mark.parametrize(
r = maya.parse('February 21, 1994').iso8601() "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) d = maya.MayaDT.from_iso8601(r)
assert r == '1994-02-21T00:00:00Z' assert r == expected
assert r == d.iso8601() assert r == d.iso8601()
def test_parse_iso8601(): @pytest.mark.parametrize(
string = '20161001T1430.4+05:30' "string,expected",
expected = '2016-10-01T09:00:00.400000Z' [
d = maya.MayaDT.from_iso8601(string) ("20161001T1430.4+05:30", "2016-10-01T09:00:00.400000Z"),
assert expected == d.iso8601() ("2016T14", "2016-01-01T14:00:00Z"),
string = '2016T14' ("2016-10T14", "2016-10-01T14:00:00Z"),
expected = '2016-01-01T14:00:00Z' ("2012W05", "2012-01-30T00:00:00Z"),
d = maya.MayaDT.from_iso8601(string) ("2012W055", "2012-02-03T00:00:00Z"),
assert expected == d.iso8601() ("2012007", "2012-01-07T00:00:00Z"),
string = '2016-10T14' ("2016-W07T09", "2016-02-15T09:00:00Z"),
expected = '2016-10-01T14:00:00Z' ],
d = maya.MayaDT.from_iso8601(string) )
assert expected == d.iso8601() def test_parse_iso8601(string, expected):
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) d = maya.MayaDT.from_iso8601(string)
assert expected == d.iso8601() assert expected == d.iso8601()
@pytest.mark.usefixtures("frozen_now")
def test_struct(): def test_struct():
now = round(time.time()) now = round(time.time())
ts = time.gmtime(now) ts = time.gmtime(now)
@@ -63,9 +57,7 @@ def test_struct():
assert m.datetime() == dt assert m.datetime() == dt
ts = time.localtime(now) ts = time.localtime(now)
m = maya.MayaDT.from_struct(ts) m = maya.MayaDT.from_struct(ts)
dt = Datetime.fromtimestamp( dt = Datetime.fromtimestamp(time.mktime(ts) - maya.core.utc_offset(ts), pytz.UTC)
time.mktime(ts) - maya.core.utc_offset(), pytz.UTC
)
assert m._epoch is not None assert m._epoch is not None
assert m.datetime() == dt assert m.datetime() == dt
@@ -74,42 +66,44 @@ def test_issue_104():
e = 1507756331 e = 1507756331
t = Datetime.utcfromtimestamp(e) t = Datetime.utcfromtimestamp(e)
t = maya.MayaDT.from_datetime(t) t = maya.MayaDT.from_datetime(t)
assert str(t) == 'Wed, 11 Oct 2017 21:12:11 GMT' assert str(t) == "Wed, 11 Oct 2017 21:12:11 GMT"
t = time.gmtime(e) t = time.gmtime(e)
t = maya.MayaDT.from_struct(t) t = maya.MayaDT.from_struct(t)
assert str(t) == 'Wed, 11 Oct 2017 21:12:11 GMT' assert str(t) == "Wed, 11 Oct 2017 21:12:11 GMT"
def test_human_when(): def test_human_when():
r1 = maya.when('yesterday') r1 = maya.when("yesterday")
r2 = maya.when('today') r2 = maya.when("today")
assert (r2.day - r1.day) in (1, -30, -29, -28, -27) assert (r2.day - r1.day) in (1, -30, -29, -28, -27)
def test_machine_parse(): def test_machine_parse():
r1 = maya.parse('August 14, 2015') r1 = maya.parse("August 14, 2015")
assert r1.day == 14 assert r1.day == 14
r2 = maya.parse('August 15, 2015') r2 = maya.parse("August 15, 2015")
assert r2.day == 15 assert r2.day == 15
@pytest.mark.usefixtures("frozen_now")
def test_dt_tz_translation(): def test_dt_tz_translation():
d1 = maya.now().datetime() 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 assert (d1.hour - d2.hour) % 24 == 5
@pytest.mark.usefixtures("frozen_now")
def test_dt_tz_naive(): def test_dt_tz_naive():
d1 = maya.now().datetime(naive=True) d1 = maya.now().datetime(naive=True)
assert d1.tzinfo is None 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 d2.tzinfo is None
assert (d1.hour - d2.hour) % 24 == 5 assert (d1.hour - d2.hour) % 24 == 5
def test_random_date(): def test_random_date():
# Test properties for maya.when() # 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.year == 2011
assert d1.month == 11 assert d1.month == 11
assert d1.day == 17 assert d1.day == 17
@@ -120,7 +114,7 @@ def test_random_date():
assert d1.second == 10 assert d1.second == 10
assert d1.microsecond == 0 assert d1.microsecond == 0
# Test properties for maya.parse() # 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.year == 1992
assert d2.month == 2 assert d2.month == 2
assert d2.day == 29 assert d2.day == 29
@@ -133,74 +127,127 @@ def test_random_date():
def test_print_date(capsys): def test_print_date(capsys):
d = maya.when('11-17-11') d = maya.when("11-17-11")
print(d) print(d)
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert out == 'Thu, 17 Nov 2011 00:00:00 GMT\n' assert out == "Thu, 17 Nov 2011 00:00:00 GMT\n"
assert repr(d) == '<MayaDT epoch=1321488000.0>' assert repr(d) == "<MayaDT epoch=1321488000.0>"
def test_invalid_date(): def test_invalid_date():
with pytest.raises(ValueError): with pytest.raises(ValueError):
maya.when('another day') maya.when("another day")
def test_slang_date(): def test_slang_date():
d = maya.when('tomorrow') d = maya.when("tomorrow")
assert d.slang_date() == '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(): def test_slang_time():
d = maya.when('1 hour ago') d = maya.when("1 hour ago")
assert d.slang_time() == 'an hour ago' assert d.slang_time() == "1 hour ago"
def test_parse(): def test_slang_time_locale():
d = maya.parse('February 21, 1994') d = maya.when("1 hour ago")
assert format(d) == '1994-02-21 00:00:00+00:00' 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'
d = maya.parse('2016/05/01', year_first=True, day_first=False)
assert format(d) == '2016-05-01 00:00:00+00:00'
d = maya.parse('2016/01/05', year_first=True, day_first=True)
assert format(d) == '2016-05-01 00:00:00+00:00'
d = maya.parse('01/05/2016', timezone='UTC')
assert format(d) == '2016-01-05 00:00:00+00:00'
d = maya.parse('01/05/2016', timezone='US/Central')
assert format(d) == '2016-01-05 06: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(): def test_when_past():
next_month = str(maya.now().add(months=1).month) two_days_away = maya.now().add(days=2)
this_year = maya.now().year
last_year = this_year - 1 past_date = maya.when(two_days_away.slang_date(), prefer_dates_from="past")
future_date = maya.when(next_month)
past_date = maya.when(next_month, prefer_past=True) assert past_date < maya.now()
assert future_date.year == this_year
if next_month == '1':
assert past_date.year == this_year @pytest.mark.usefixtures("frozen_now")
else: def test_when_future():
assert past_date.year == last_year 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(): def test_datetime_to_timezone():
dt = maya.when('2016-01-01').datetime(to_timezone='US/Eastern') dt = maya.when("2016-01-01").datetime(to_timezone="US/Eastern")
assert dt.tzinfo.zone == 'US/Eastern' assert dt.tzinfo.zone == "US/Eastern"
def test_rfc3339(): def test_rfc3339_epoch():
mdt = maya.when('2016-01-01') mdt = maya.when("2016-01-01")
out = mdt.rfc3339() out = mdt.rfc3339()
mdt2 = maya.MayaDT.from_rfc3339(out) mdt2 = maya.MayaDT.from_rfc3339(out)
assert mdt.epoch == mdt2.epoch 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(): def test_comparison_operations():
now = maya.now() now = maya.now()
now_copy = copy.deepcopy(now) now_copy = copy.deepcopy(now)
tomorrow = maya.when('tomorrow') tomorrow = maya.when("tomorrow")
assert (now == now_copy) is True assert (now == now_copy) is True
assert (now == tomorrow) is False assert (now == tomorrow) is False
assert (now != now_copy) is False assert (now != now_copy) is False
@@ -235,62 +282,89 @@ def test_seconds_or_timedelta():
assert _seconds_or_timedelta(timedelta(0, 1234)) == timedelta(0, 1234) assert _seconds_or_timedelta(timedelta(0, 1234)) == timedelta(0, 1234)
# test for invalid value # test for invalid value
with pytest.raises(TypeError): with pytest.raises(TypeError):
_seconds_or_timedelta('invalid interval') _seconds_or_timedelta("invalid interval")
@pytest.mark.usefixtures("frozen_now")
def test_intervals(): def test_intervals():
now = maya.now() now = maya.now()
tomorrow = now.add(days=1) 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(): def test_dunder_add():
now = maya.now() now = maya.now()
assert now + 1 == now.add(seconds=1) assert now + 1 == now.add(seconds=1)
assert now + timedelta(seconds=1) == now.add(seconds=1) assert now + timedelta(seconds=1) == now.add(seconds=1)
@pytest.mark.usefixtures("frozen_now")
def test_dunder_radd(): def test_dunder_radd():
now = maya.now() now = maya.now()
assert now.add(seconds=1) == now + 1 assert now.add(seconds=1) == now + 1
assert now.add(seconds=1) == now + timedelta(seconds=1) assert now.add(seconds=1) == now + timedelta(seconds=1)
@pytest.mark.usefixtures("frozen_now")
def test_dunder_sub(): def test_dunder_sub():
now = maya.now() now = maya.now()
assert now - 1 == now.subtract(seconds=1) assert now - 1 == now.subtract(seconds=1)
assert now - timedelta(seconds=1) == now.subtract(seconds=1) assert now - timedelta(seconds=1) == now.subtract(seconds=1)
@pytest.mark.usefixtures("frozen_now")
def test_mayaDT_sub(): def test_mayaDT_sub():
now = maya.now() now = maya.now()
then = now.add(days=1) then = now.add(days=1)
assert then - now == timedelta(24 * 60 * 60) assert then - now == timedelta(seconds=24 * 60 * 60)
assert now - then == timedelta(-24 * 60 * 60) assert now - then == timedelta(seconds=-24 * 60 * 60)
def test_core_local_timezone(monkeypatch): def test_core_local_timezone(monkeypatch):
@property @property
def mock_local_tz(self): def mock_local_tz(self):
class StaticTzInfo(object): class StaticTzInfo(object):
zone = 'local' zone = "local"
def __repr__(self): def __repr__(self):
return "<StaticTzInfo 'local'>" return "<StaticTzInfo 'local'>"
return StaticTzInfo() return StaticTzInfo()
monkeypatch.setattr(maya.MayaDT, '_local_tz', mock_local_tz) monkeypatch.setattr(maya.MayaDT, "_local_tz", mock_local_tz)
mdt = maya.MayaDT(0) mdt = maya.MayaDT(0)
assert mdt.local_timezone == 'UTC' assert mdt.local_timezone == "UTC"
def test_snaptime(): 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 # given
dt = maya.when('Mon, 21 Feb 1994 21:21:42 GMT') dt = maya.when(when_str)
# when # when
dt = dt.snap('@d') dt = dt.snap(snap_str)
# then # then
assert dt == maya.when('Mon, 21 Feb 1994 00:00:00 GMT') assert dt == maya.when(expected_when)
Executable → Regular
+135 -146
View File
@@ -7,9 +7,9 @@ import pytz
import maya import maya
from maya.compat import cmp from maya.compat import cmp
Los_Angeles = pytz.timezone('America/Los_Angeles') Los_Angeles = pytz.timezone("America/Los_Angeles")
New_York = pytz.timezone('America/New_York') New_York = pytz.timezone("America/New_York")
Melbourne = pytz.timezone('Australia/Melbourne') Melbourne = pytz.timezone("Australia/Melbourne")
def test_interval_requires_2_of_start_end_duration(): def test_interval_requires_2_of_start_end_duration():
@@ -59,7 +59,7 @@ def test_interval_init_end_duration():
@pytest.mark.parametrize( @pytest.mark.parametrize(
'start_doy1,end_doy1,start_doy2,end_doy2,intersection_doys', "start_doy1,end_doy1,start_doy2,end_doy2,intersection_doys",
( (
(0, 2, 1, 3, (1, 2)), (0, 2, 1, 3, (1, 2)),
(0, 2, 3, 4, None), (0, 2, 3, 4, None),
@@ -74,40 +74,35 @@ def test_interval_init_end_duration():
(1, 3, 2, 2, (2, 2)), (1, 3, 2, 2, (2, 2)),
), ),
ids=( ids=(
'overlapping', "overlapping",
'non-overlapping', "non-overlapping",
'adjacent', "adjacent",
'equal', "equal",
'instant overlapping start only', "instant overlapping start only",
'instant equal', "instant equal",
'instant disjoint', "instant disjoint",
'instant overlapping', "instant overlapping",
'instant overlapping start only (left)', "instant overlapping start only (left)",
'instant disjoint (left)', "instant disjoint (left)",
'instant overlapping (left)', "instant overlapping (left)",
), ),
) )
def test_interval_intersection( 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)) base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
interval1 = maya.MayaInterval( interval1 = maya.MayaInterval(base.add(days=start_doy1), base.add(days=end_doy1))
base.add(days=start_doy1), base.add(days=end_doy1) interval2 = maya.MayaInterval(base.add(days=start_doy2), base.add(days=end_doy2))
)
interval2 = maya.MayaInterval(
base.add(days=start_doy2), base.add(days=end_doy2)
)
if intersection_doys: if intersection_doys:
start_doy_intersection, end_doy_intersection = intersection_doys start_doy_intersection, end_doy_intersection = intersection_doys
assert interval1 & interval2 == maya.MayaInterval( assert interval1 & interval2 == maya.MayaInterval(
base.add(days=start_doy_intersection), base.add(days=start_doy_intersection), base.add(days=end_doy_intersection)
base.add(days=end_doy_intersection),
) )
else: else:
assert (interval1 & interval2) is None assert (interval1 & interval2) is None
# check invalid argument # check invalid argument
with pytest.raises(TypeError): with pytest.raises(TypeError):
interval1 & 'invalid type' interval1 & "invalid type"
def test_interval_intersects(): def test_interval_intersects():
@@ -119,7 +114,7 @@ def test_interval_intersects():
) )
# check invalid argument # check invalid argument
with pytest.raises(TypeError): with pytest.raises(TypeError):
interval.intersects('invalid type') interval.intersects("invalid type")
def test_and_operator(): def test_and_operator():
@@ -127,13 +122,13 @@ def test_and_operator():
interval1 = maya.MayaInterval(base, base.add(days=2)) interval1 = maya.MayaInterval(base, base.add(days=2))
interval2 = maya.MayaInterval(base.add(days=1), base.add(days=3)) interval2 = maya.MayaInterval(base.add(days=1), base.add(days=3))
assert ( assert (
interval1 & interval2 == interval1 & interval2
interval2 & interval1 == == interval2 & interval1 # noqa
interval1.intersection(interval2) == interval1.intersection(interval2) # noqa
) )
# check invalid argument # check invalid argument
with pytest.raises(TypeError): with pytest.raises(TypeError):
interval1.intersection('invalid type') interval1.intersection("invalid type")
def test_interval_eq_operator(): def test_interval_eq_operator():
@@ -144,9 +139,9 @@ def test_interval_eq_operator():
assert interval != maya.MayaInterval(start=start, end=end.add(days=1)) assert interval != maya.MayaInterval(start=start, end=end.add(days=1))
# check invalid argument # check invalid argument
with pytest.raises(TypeError): with pytest.raises(TypeError):
interval == 'invalid type' interval == "invalid type"
with pytest.raises(TypeError): with pytest.raises(TypeError):
interval != 'invalid type' interval != "invalid type"
def test_interval_timedelta(): def test_interval_timedelta():
@@ -164,7 +159,7 @@ def test_interval_duration():
@pytest.mark.parametrize( @pytest.mark.parametrize(
'start_doy1,end_doy1,start_doy2,end_doy2,expected', "start_doy1,end_doy1,start_doy2,end_doy2,expected",
( (
(0, 2, 1, 3, False), (0, 2, 1, 3, False),
(0, 2, 3, 4, False), (0, 2, 3, 4, False),
@@ -172,27 +167,21 @@ def test_interval_duration():
(0, 1, 0, 1, True), (0, 1, 0, 1, True),
(0, 3, 1, 2, True), (0, 3, 1, 2, True),
), ),
ids=('overlapping', 'non-overlapping', 'adjacent', 'equal', 'subset'), ids=("overlapping", "non-overlapping", "adjacent", "equal", "subset"),
) )
def test_interval_contains( def test_interval_contains(start_doy1, end_doy1, start_doy2, end_doy2, expected):
start_doy1, end_doy1, start_doy2, end_doy2, expected
):
base = maya.MayaDT.from_datetime(datetime(2016, 1, 1)) base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
interval1 = maya.MayaInterval( interval1 = maya.MayaInterval(base.add(days=start_doy1), base.add(days=end_doy1))
base.add(days=start_doy1), base.add(days=end_doy1) interval2 = maya.MayaInterval(base.add(days=start_doy2), base.add(days=end_doy2))
)
interval2 = maya.MayaInterval(
base.add(days=start_doy2), base.add(days=end_doy2)
)
assert interval1.contains(interval2) is expected assert interval1.contains(interval2) is expected
assert (interval2 in interval1) is expected assert (interval2 in interval1) is expected
# check invalid argument # check invalid argument
with pytest.raises(TypeError): with pytest.raises(TypeError):
interval1.contains('invalid type') interval1.contains("invalid type")
@pytest.mark.parametrize( @pytest.mark.parametrize(
'start_doy,end_doy,dt_doy,expected', "start_doy,end_doy,dt_doy,expected",
( (
(2, 4, 1, False), (2, 4, 1, False),
(2, 4, 2, True), (2, 4, 2, True),
@@ -200,7 +189,7 @@ def test_interval_contains(
(2, 4, 4, False), (2, 4, 4, False),
(2, 4, 5, False), (2, 4, 5, False),
), ),
ids=('before-start', 'on-start', 'during', 'on-end', 'after-end'), ids=("before-start", "on-start", "during", "on-end", "after-end"),
) )
def test_interval_in_operator_maya_dt(start_doy, end_doy, dt_doy, expected): def test_interval_in_operator_maya_dt(start_doy, end_doy, dt_doy, expected):
base = maya.MayaDT.from_datetime(datetime(2016, 1, 1)) base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
@@ -211,7 +200,7 @@ def test_interval_in_operator_maya_dt(start_doy, end_doy, dt_doy, expected):
assert (dt in interval) is expected assert (dt in interval) is expected
# check invalid argument # check invalid argument
with pytest.raises(TypeError): with pytest.raises(TypeError):
'invalid type' in interval "invalid type" in interval
def test_interval_hash(): def test_interval_hash():
@@ -219,9 +208,7 @@ def test_interval_hash():
end = start.add(hours=1) end = start.add(hours=1)
interval = maya.MayaInterval(start=start, end=end) 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))
assert hash(interval) != hash( assert hash(interval) != hash(maya.MayaInterval(start=start, end=end.add(days=1)))
maya.MayaInterval(start=start, end=end.add(days=1))
)
def test_interval_iter(): def test_interval_iter():
@@ -231,47 +218,34 @@ def test_interval_iter():
@pytest.mark.parametrize( @pytest.mark.parametrize(
'start1,end1,start2,end2,expected', "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)], [(1, 2, 1, 2, 0), (1, 3, 2, 4, -1), (2, 4, 1, 3, 1), (1, 2, 1, 3, -1)],
ids=( ids=("equal", "less-than", "greater-than", "use-end-time-if-start-time-identical"),
'equal',
'less-than',
'greater-than',
'use-end-time-if-start-time-identical',
),
) )
def test_interval_cmp(start1, end1, start2, end2, expected): def test_interval_cmp(start1, end1, start2, end2, expected):
base = maya.now() base = maya.now()
interval1 = maya.MayaInterval( interval1 = maya.MayaInterval(start=base.add(days=start1), end=base.add(days=end1))
start=base.add(days=start1), end=base.add(days=end1) interval2 = maya.MayaInterval(start=base.add(days=start2), end=base.add(days=end2))
)
interval2 = maya.MayaInterval(
start=base.add(days=start2), end=base.add(days=end2)
)
assert cmp(interval1, interval2) == expected assert cmp(interval1, interval2) == expected
# check invalid argument # check invalid argument
with pytest.raises(TypeError): with pytest.raises(TypeError):
cmp(interval1, 'invalid type') cmp(interval1, "invalid type")
@pytest.mark.parametrize( @pytest.mark.parametrize(
'start1,end1,start2,end2,expected', "start1,end1,start2,end2,expected",
[ [
(1, 2, 2, 3, [(1, 3)]), (1, 2, 2, 3, [(1, 3)]),
(1, 3, 2, 4, [(1, 4)]), (1, 3, 2, 4, [(1, 4)]),
(1, 2, 3, 4, [(1, 2), (3, 4)]), (1, 2, 3, 4, [(1, 2), (3, 4)]),
(1, 5, 2, 3, [(1, 5)]), (1, 5, 2, 3, [(1, 5)]),
], ],
ids=('adjacent', 'overlapping', 'non-overlapping', 'contains'), ids=("adjacent", "overlapping", "non-overlapping", "contains"),
) )
def test_interval_combine(start1, end1, start2, end2, expected): def test_interval_combine(start1, end1, start2, end2, expected):
base = maya.now() base = maya.now()
interval1 = maya.MayaInterval( interval1 = maya.MayaInterval(start=base.add(days=start1), end=base.add(days=end1))
start=base.add(days=start1), end=base.add(days=end1) interval2 = maya.MayaInterval(start=base.add(days=start2), end=base.add(days=end2))
)
interval2 = maya.MayaInterval(
start=base.add(days=start2), end=base.add(days=end2)
)
expected_intervals = [ expected_intervals = [
maya.MayaInterval(start=base.add(days=start), end=base.add(days=end)) maya.MayaInterval(start=base.add(days=start), end=base.add(days=end))
for start, end in expected for start, end in expected
@@ -280,11 +254,11 @@ def test_interval_combine(start1, end1, start2, end2, expected):
assert interval2.combine(interval1) == expected_intervals assert interval2.combine(interval1) == expected_intervals
# check invalid argument # check invalid argument
with pytest.raises(TypeError): with pytest.raises(TypeError):
interval2.combine('invalid type') interval2.combine("invalid type")
@pytest.mark.parametrize( @pytest.mark.parametrize(
'start1,end1,start2,end2,expected', "start1,end1,start2,end2,expected",
[ [
(1, 2, 3, 4, [(1, 2)]), (1, 2, 3, 4, [(1, 2)]),
(1, 2, 2, 4, [(1, 2)]), (1, 2, 2, 4, [(1, 2)]),
@@ -296,24 +270,20 @@ def test_interval_combine(start1, end1, start2, end2, expected):
(1, 4, 3, 4, [(1, 3)]), (1, 4, 3, 4, [(1, 3)]),
], ],
ids=( ids=(
'non-overlapping', "non-overlapping",
'adjacent', "adjacent",
'contains', "contains",
'splits', "splits",
'overlaps-left', "overlaps-left",
'overlaps-right', "overlaps-right",
'overlaps-left-identical-start', "overlaps-left-identical-start",
'overlaps-right-identical-end', "overlaps-right-identical-end",
), ),
) )
def test_interval_subtract(start1, end1, start2, end2, expected): def test_interval_subtract(start1, end1, start2, end2, expected):
base = maya.now() base = maya.now()
interval1 = maya.MayaInterval( interval1 = maya.MayaInterval(start=base.add(days=start1), end=base.add(days=end1))
start=base.add(days=start1), end=base.add(days=end1) interval2 = maya.MayaInterval(start=base.add(days=start2), end=base.add(days=end2))
)
interval2 = maya.MayaInterval(
start=base.add(days=start2), end=base.add(days=end2)
)
expected_intervals = [ expected_intervals = [
maya.MayaInterval(start=base.add(days=start), end=base.add(days=end)) maya.MayaInterval(start=base.add(days=start), end=base.add(days=end))
for start, end in expected for start, end in expected
@@ -321,35 +291,26 @@ def test_interval_subtract(start1, end1, start2, end2, expected):
assert interval1.subtract(interval2) == expected_intervals assert interval1.subtract(interval2) == expected_intervals
# check invalid argument # check invalid argument
with pytest.raises(TypeError): with pytest.raises(TypeError):
interval1.subtract('invalid type') interval1.subtract("invalid type")
@pytest.mark.parametrize( @pytest.mark.parametrize(
'start1,end1,start2,end2,expected', "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)],
(1, 2, 2, 3, True), ids=("adjacent-right", "adjacent-left", "overlapping", "non-overlapping"),
(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): def test_interval_is_adjacent(start1, end1, start2, end2, expected):
base = maya.now() base = maya.now()
interval1 = maya.MayaInterval( interval1 = maya.MayaInterval(start=base.add(days=start1), end=base.add(days=end1))
start=base.add(days=start1), end=base.add(days=end1) interval2 = maya.MayaInterval(start=base.add(days=start2), end=base.add(days=end2))
)
interval2 = maya.MayaInterval(
start=base.add(days=start2), end=base.add(days=end2)
)
assert interval1.is_adjacent(interval2) == expected assert interval1.is_adjacent(interval2) == expected
# check invalid argument # check invalid argument
with pytest.raises(TypeError): with pytest.raises(TypeError):
interval1.is_adjacent('invalid type') interval1.is_adjacent("invalid type")
@pytest.mark.parametrize( @pytest.mark.parametrize(
'start,end,delta,include_remainder,expected', "start,end,delta,include_remainder,expected",
[ [
(0, 10, 5, False, [(0, 5), (5, 10)]), (0, 10, 5, False, [(0, 5), (5, 10)]),
(0, 10, 5, True, [(0, 5), (5, 10)]), (0, 10, 5, True, [(0, 5), (5, 10)]),
@@ -359,19 +320,17 @@ def test_interval_is_adjacent(start1, end1, start2, end2, expected):
(0, 2, 5, True, [(0, 2)]), (0, 2, 5, True, [(0, 2)]),
], ],
ids=( ids=(
'even-split', "even-split",
'even-split-include-partial', "even-split-include-partial",
'uneven-split-do-not-include-partial', "uneven-split-do-not-include-partial",
'uneven-split-include-partial', "uneven-split-include-partial",
'delta-larger-than-timepsan-do-not-include-partial', "delta-larger-than-timepsan-do-not-include-partial",
'delta-larger-than-timepsan-include-partial', "delta-larger-than-timepsan-include-partial",
), ),
) )
def test_interval_split(start, end, delta, include_remainder, expected): def test_interval_split(start, end, delta, include_remainder, expected):
base = maya.now() base = maya.now()
interval = maya.MayaInterval( interval = maya.MayaInterval(start=base.add(days=start), end=base.add(days=end))
start=base.add(days=start), end=base.add(days=end)
)
delta = timedelta(days=delta) delta = timedelta(days=delta)
expected_intervals = [ expected_intervals = [
maya.MayaInterval(start=base.add(days=s), end=base.add(days=e)) maya.MayaInterval(start=base.add(days=s), end=base.add(days=e))
@@ -393,30 +352,30 @@ def test_interval_split_non_positive_delta():
@pytest.mark.parametrize( @pytest.mark.parametrize(
'start,end,minutes,timezone,snap_out,expected_start,expected_end', "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, False, (5, 30), (8, 30)),
((5, 12), (8, 48), 30, None, True, (5, 0), (9, 0)), ((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, False, (5, 15), (9, 0)),
((5, 15), (9, 0), 15, None, True, (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", False, (7, 0), (9, 0)),
((6, 50), (9, 15), 60, 'America/New_York', True, (6, 0), (10, 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, False, (6, 0), (6, 0)),
((6, 20), (6, 50), 60, None, True, (6, 0), (7, 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", False, (6, 0), (6, 0)),
((6, 20), (6, 50), 60, 'America/Chicago', True, (6, 0), (7, 0)), ((6, 20), (6, 50), 60, "America/Chicago", True, (6, 0), (7, 0)),
], ],
ids=( ids=(
'normal', "normal",
'normal-snap_out', "normal-snap_out",
'already-quantized', "already-quantized",
'already-quantized-snap_out', "already-quantized-snap_out",
'with-timezone', "with-timezone",
'with-timezone-snap_out', "with-timezone-snap_out",
'too-small', "too-small",
'too-small-snap_out', "too-small-snap_out",
'too-small-with-timezone', "too-small-with-timezone",
'too-small-with-timezone-snap_out', "too-small-with-timezone-snap_out",
), ),
) )
def test_quantize( def test_quantize(
@@ -427,7 +386,7 @@ def test_quantize(
start=base.add(hours=start[0], minutes=start[1]), start=base.add(hours=start[0], minutes=start[1]),
end=base.add(hours=end[0], minutes=end[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( quantized_interval = interval.quantize(
timedelta(minutes=minutes), snap_out=snap_out, **kwargs timedelta(minutes=minutes), snap_out=snap_out, **kwargs
) )
@@ -464,11 +423,9 @@ def test_interval_flatten_non_overlapping():
def test_interval_flatten_adjacent(): def test_interval_flatten_adjacent():
step = 2 step = 2
max_hour = 20 max_hour = 20
base = maya.when('jan/1/2011') base = maya.when("jan/1/2011")
intervals = [ intervals = [
maya.MayaInterval( maya.MayaInterval(start=base.add(hours=hour), duration=timedelta(hours=step))
start=base.add(hours=hour), duration=timedelta(hours=step)
)
for hour in range(0, max_hour, step) for hour in range(0, max_hour, step)
] ]
random.shuffle(intervals) random.shuffle(intervals)
@@ -483,16 +440,13 @@ def test_interval_flatten_intersecting():
base = maya.now() base = maya.now()
intervals = [ intervals = [
maya.MayaInterval( maya.MayaInterval(
start=base.add(hours=hour), start=base.add(hours=hour), duration=timedelta(hours=step, minutes=30)
duration=timedelta(hours=step, minutes=30),
) )
for hour in range(0, max_hour, step) for hour in range(0, max_hour, step)
] ]
random.shuffle(intervals) random.shuffle(intervals)
assert maya.MayaInterval.flatten(intervals) == [ assert maya.MayaInterval.flatten(intervals) == [
maya.MayaInterval( maya.MayaInterval(start=base, duration=timedelta(hours=max_hour, minutes=30))
start=base, duration=timedelta(hours=max_hour, minutes=30)
)
] ]
@@ -536,9 +490,9 @@ def test_interval_from_datetime():
def test_interval_iso8601(): def test_interval_iso8601():
start = maya.when('11-17-11 08:09:10') start = maya.when("11-17-11 08:09:10")
interval = maya.MayaInterval(start=start, duration=1) interval = maya.MayaInterval(start=start, duration=1)
assert interval.iso8601() == '2011-11-17T08:09:10Z/2011-11-17T08:09:11Z' assert interval.iso8601() == "2011-11-17T08:09:10Z/2011-11-17T08:09:11Z"
def test_interval_from_iso8601(): def test_interval_from_iso8601():
@@ -553,20 +507,55 @@ def test_interval_from_iso8601():
def test_interval_from_iso8601_duration(): def test_interval_from_iso8601_duration():
interval = maya.MayaInterval.from_iso8601( interval = maya.MayaInterval.from_iso8601("2018-03-18T14:27:18Z/P13DT13H48M9S")
"2018-03-18T14:27:18Z/P13DT13H48M9S"
)
s = maya.when("2018-03-18T14:27:18Z") s = maya.when("2018-03-18T14:27:18Z")
e = maya.when("2018-04-01T04:15:27Z") e = maya.when("2018-04-01T04:15:27Z")
assert interval.start == s assert interval.start == s
assert interval.end == e assert interval.end == e
interval = maya.MayaInterval.from_iso8601( interval = maya.MayaInterval.from_iso8601("2018-03-05T14:27:18Z/P2W")
"2018-03-05T14:27:18Z/P2W"
)
s = maya.when("2018-03-05T14:27:18Z") s = maya.when("2018-03-05T14:27:18Z")
e = maya.when("2018-03-19T14:27:18Z") e = maya.when("2018-03-19T14:27:18Z")
assert interval.start == s assert interval.start == s
assert interval.end == e 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
+43
View File
@@ -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