mirror of
https://github.com/kennethreitz/maya.git
synced 2026-06-05 23:00:18 +00:00
Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 52ca9a1cc4 | |||
| d872f29cfc | |||
| d8cbdede28 | |||
| c0f1bd709f | |||
| 8469e60d3c | |||
| 953f857940 | |||
| 6375143eac | |||
| c0fd845d4c | |||
| 0f38b99157 | |||
| 66a016ea84 | |||
| 6f1df92b8f | |||
| 51c4298ece | |||
| 74092289dd | |||
| f3f2793b50 | |||
| 251f535d67 | |||
| 98e9a2190a | |||
| c8dd4b9264 | |||
| a1b27e80d6 | |||
| 92f3b83b8b | |||
| 0f39ec6323 | |||
| 4d88eede9d | |||
| 971ecec9cf | |||
| ef471f8041 | |||
| 0fda49bdb9 | |||
| a8a18462fe | |||
| 82dc88c878 | |||
| e9a14e32da | |||
| 7b7f990a13 | |||
| 720617f062 | |||
| 5bf45dbfb7 | |||
| 488a25bcd8 | |||
| b2ac4f08a5 | |||
| 57ccc67721 | |||
| 7814ec2864 | |||
| f39c932039 | |||
| 96ff770071 | |||
| cd16ee94f0 | |||
| 7c68489682 | |||
| 93e163722b | |||
| b3ac13fcbf | |||
| d50dc8701c | |||
| 6c1cc24ad5 | |||
| 46b7e369b3 | |||
| 2b60e817ea | |||
| 0fea886c00 | |||
| ef52cb4b7d | |||
| f1be1585d3 | |||
| 89ee548ec1 | |||
| 09351afc68 | |||
| 5d4a217fd6 | |||
| 27950e6d35 | |||
| f2c52af853 | |||
| 01cc151b5c | |||
| eda4a92da9 | |||
| 6fbf8f2d74 | |||
| 343abed679 | |||
| 704af5a3a0 | |||
| f9f5b97f62 |
+2
-2
@@ -4,6 +4,6 @@ python:
|
||||
- "3.6"
|
||||
|
||||
# command to install dependencies
|
||||
install: pip install pipenv; pipenv install --dev
|
||||
install: pip install pipenv; pipenv lock; pipenv install --dev
|
||||
# command to run tests
|
||||
script: pipenv run pytest
|
||||
script: pipenv run pytest tests/
|
||||
|
||||
@@ -21,3 +21,4 @@ In chronological order:
|
||||
- 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>`_)
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Kenneth Reitz
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
tests:
|
||||
pytest test_maya.py
|
||||
pytest tests/
|
||||
docs:
|
||||
cd docs && make html
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
[dev-packages]
|
||||
pytest = "*"
|
||||
Sphinx = "*"
|
||||
|
||||
[packages]
|
||||
humanize = "*"
|
||||
pytz = "*"
|
||||
@@ -5,6 +9,3 @@ dateparser = "*"
|
||||
"ruamel.yaml" = "*"
|
||||
tzlocal = "*"
|
||||
pendulum = ">=1.0"
|
||||
|
||||
[dev-packages]
|
||||
pytest = "*"
|
||||
|
||||
+27
-3
@@ -1,5 +1,5 @@
|
||||
Maya: Timestamps for Humans™
|
||||
============================
|
||||
Maya: Datetimes for Humans™
|
||||
===========================
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/maya.svg
|
||||
:target: https://pypi.python.org/pypi/maya
|
||||
@@ -10,6 +10,7 @@ Maya: Timestamps for Humans™
|
||||
.. image:: https://img.shields.io/badge/SayThanks-!-1EAEDB.svg
|
||||
:target: https://saythanks.io/to/kennethreitz
|
||||
|
||||
test
|
||||
|
||||
Datetimes are very frustrating to work with in Python, especially when dealing
|
||||
with different locales on different systems. This library exists to make the
|
||||
@@ -77,6 +78,27 @@ Behold, datetimes for humans!
|
||||
>>> maya.interval(start=maya.now(), end=maya.now().add(days=1), interval=60*60)
|
||||
<generator object intervals at 0x105ba5820>
|
||||
|
||||
☤ Advanced Usage of Maya
|
||||
------------------------
|
||||
|
||||
In addition to timestamps, Maya also includes a wonderfuly powerful ``MayaInterval`` class, which represents a range of time (e.g. an event). With this class, you can perform a multitude of advanced calendar calculations with finese and ease.
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from maya import MayaInterval
|
||||
|
||||
# Create an event that is one hour long, starting now.
|
||||
>>> event_start = maya.now()
|
||||
>>> event_end = event_start.add(hours=1)
|
||||
|
||||
>>> event = MayaInterval(start=event_start, end=event_end)
|
||||
|
||||
From here, there a a number of methods available to you, which you can use to compare this event to another event.
|
||||
|
||||
|
||||
|
||||
☤ Why is this useful?
|
||||
---------------------
|
||||
|
||||
@@ -92,11 +114,13 @@ Behold, datetimes for humans!
|
||||
☤ What about Delorean, Arrow, & Pendulum?
|
||||
-----------------------------------------
|
||||
|
||||
All these project complement eachother, 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.
|
||||
|
||||
I simply desire a sane API for datetimes that made sense to me for all the things I'd ever want to do—especially when dealing with timezone algebra. Arrow doesn't do all of the things I need (but it does a lot more!). Maya does do exactly what I need.
|
||||
|
||||
I think these projects complement each-other, personally. Maya is great for parsing websites!
|
||||
I think these projects complement each-other, personally. Maya is great for parsing websites, and dealing with calendar events!
|
||||
|
||||
|
||||
☤ Installing Maya
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = python -msphinx
|
||||
SPHINXPROJ = maya
|
||||
SOURCEDIR = source
|
||||
BUILDDIR = _build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
@@ -0,0 +1,36 @@
|
||||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=python -msphinx
|
||||
)
|
||||
set SOURCEDIR=source
|
||||
set BUILDDIR=build
|
||||
set SPHINXPROJ=maya
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The Sphinx module was not found. Make sure you have Sphinx installed,
|
||||
echo.then set the SPHINXBUILD environment variable to point to the full
|
||||
echo.path of the 'sphinx-build' executable. Alternatively you may add the
|
||||
echo.Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
|
||||
:end
|
||||
popd
|
||||
@@ -0,0 +1,163 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# maya documentation build configuration file, created by
|
||||
# sphinx-quickstart on Sun May 28 15:46:10 2017.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
import os
|
||||
import sys
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
sys.path.insert(0, os.path.abspath('_themes'))
|
||||
|
||||
import maya
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#
|
||||
# needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = ['sphinx.ext.autodoc',
|
||||
'sphinx.ext.todo',
|
||||
'sphinx.ext.coverage',
|
||||
'sphinx.ext.viewcode']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
#
|
||||
# source_suffix = ['.rst', '.md']
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = 'maya'
|
||||
copyright = '2017, Kenneth Reitz'
|
||||
author = 'Kenneth Reitz'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = maya.__version__
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = maya.__version__
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = None
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This patterns also effect to html_static_path and html_extra_path
|
||||
exclude_patterns = []
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||
todo_include_todos = True
|
||||
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = 'alabaster'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#
|
||||
# html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
|
||||
# -- Options for HTMLHelp output ------------------------------------------
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'mayadoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#
|
||||
# 'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#
|
||||
# 'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#
|
||||
# 'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#
|
||||
# 'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'maya.tex', 'maya Documentation',
|
||||
'Kenneth Reitz', 'manual'),
|
||||
]
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'maya', 'maya Documentation',
|
||||
[author], 1)
|
||||
]
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'maya', 'maya Documentation',
|
||||
author, 'maya', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
.. maya documentation master file, created by
|
||||
sphinx-quickstart on Sun May 28 15:46:10 2017.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Maya: Datetime for Humans
|
||||
================================
|
||||
Release v\ |version|. (:ref:`Installation <install>`)
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/maya.svg
|
||||
:target: https://pypi.python.org/pypi/maya
|
||||
|
||||
.. image:: https://travis-ci.org/kennethreitz/maya.svg?branch=master
|
||||
:target: https://travis-ci.org/kennethreitz/maya
|
||||
|
||||
.. image:: https://img.shields.io/badge/SayThanks-!-1EAEDB.svg
|
||||
:target: https://saythanks.io/to/kennethreitz
|
||||
|
||||
☤ Behold, datetimes for humans!
|
||||
-------------------------------
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> now = maya.now()
|
||||
<MayaDT epoch=1481850660.9>
|
||||
|
||||
>>> tomorrow = maya.when('tomorrow')
|
||||
<MayaDT epoch=1481919067.23>
|
||||
|
||||
>>> tomorrow.slang_date()
|
||||
'tomorrow'
|
||||
|
||||
>>> tomorrow.slang_time()
|
||||
'23 hours from now'
|
||||
|
||||
# Also: MayaDT.from_iso8601(...)
|
||||
>>> tomorrow.iso8601()
|
||||
'2017-02-10T22:17:01.445418Z'
|
||||
|
||||
# Also: MayaDT.from_rfc2822(...)
|
||||
>>> tomorrow.rfc2822()
|
||||
'Fri, 10 Feb 2017 22:17:01 GMT'
|
||||
|
||||
# Also: MayaDT.from_rfc3339(...)
|
||||
>>> tomorrow.rfc3339()
|
||||
'2017-02-10T22:17:01.44Z'
|
||||
|
||||
>>> tomorrow.datetime()
|
||||
datetime.datetime(2016, 12, 16, 15, 11, 30, 263350, tzinfo=<UTC>)
|
||||
|
||||
# Automatically parse datetime strings and generate naive datetimes.
|
||||
>>> scraped = '2016-12-16 18:23:45.423992+00:00'
|
||||
>>> maya.parse(scraped).datetime(to_timezone='US/Eastern', naive=True)
|
||||
datetime.datetime(2016, 12, 16, 13, 23, 45, 423992)
|
||||
|
||||
>>> rand_day = maya.when('2011-02-07', timezone='US/Eastern')
|
||||
<MayaDT epoch=1297036800.0>
|
||||
|
||||
>>> rand_day.day
|
||||
7
|
||||
|
||||
>>> rand_day.add(days=10).day
|
||||
17
|
||||
|
||||
# Always.
|
||||
>>> rand_day.timezone
|
||||
UTC
|
||||
|
||||
# Range of hours in a day:
|
||||
>>> maya.interval(
|
||||
... start=maya.now(),
|
||||
... end=maya.now().add(days=1),
|
||||
... interval=60*60)
|
||||
<generator object intervals at 0x105ba5820>
|
||||
|
||||
|
||||
Table of Contents
|
||||
-----------------
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
user/install
|
||||
user/quickstart
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
.. _install:
|
||||
Installation
|
||||
=============
|
||||
|
||||
Pip Install Maya
|
||||
----------------
|
||||
|
||||
To install maya, simply use::
|
||||
$ pip install maya
|
||||
|
||||
Source Code
|
||||
-----------
|
||||
Maya is actively developed on `Github
|
||||
<https://github.com/kennethreitz/maya.git>`_
|
||||
|
||||
You can either clone the public repository::
|
||||
|
||||
$ git clone git://github.com/kennethreitz/maya.git
|
||||
|
||||
Or, download the `tarball <https://github.com/kennethreitz/maya/tarball/master>`_::
|
||||
|
||||
$ curl -OL https://github.com/kennethreitz/maya/tarball/master
|
||||
# optionally, zipball is also available (for Windows users).
|
||||
|
||||
Once you have a copy of the source, you can embed it in your own Python
|
||||
package, or install it into your site-packages easily::
|
||||
|
||||
$ python setup.py install
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
.. _quickstart:
|
||||
Quickstart
|
||||
==========
|
||||
|
||||
.. module::maya
|
||||
|
||||
Ready for a simple datetime tool? This doc provides some tools to use in your
|
||||
busy workflow.
|
||||
|
||||
First, make sure that Maya is:
|
||||
|
||||
- :ref:`Installed <install>`
|
||||
- :ref:`Up to date <update>`
|
||||
|
||||
|
||||
Parse a Date
|
||||
------------
|
||||
Parsing a date from a string with Maya is 🍰!
|
||||
|
||||
First, you'll need to import maya::
|
||||
|
||||
>>> import maya
|
||||
|
||||
There are currently two ways to make sense of datetime:
|
||||
|
||||
- ``maya.parse``
|
||||
- ``maya.when``
|
||||
|
||||
A simple answer is that you should use parse on machine output, and when on human input.
|
||||
|
||||
Use as follows::
|
||||
|
||||
>>> recent_win = maya.parse('2016-11-02T20:00PM')
|
||||
>>> old_win = maya.when('October 14, 1908')
|
||||
>>> grandpas_date = maya.when('108 years ago')
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
.. _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
|
||||
@@ -0,0 +1,2 @@
|
||||
from .core import *
|
||||
from .__version__ import __version__
|
||||
@@ -0,0 +1 @@
|
||||
__version__ = '0.3.2'
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
maya.compat
|
||||
~~~~~~~~~~~~~~~
|
||||
This module handles import compatibility issues between Python 2 and
|
||||
Python 3.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
# -------
|
||||
# Pythons
|
||||
# -------
|
||||
|
||||
# Syntax sugar.
|
||||
_ver = sys.version_info
|
||||
|
||||
#: Python 2.x?
|
||||
is_py2 = (_ver[0] == 2)
|
||||
|
||||
#: Python 3.x?
|
||||
is_py3 = (_ver[0] == 3)
|
||||
|
||||
# ---------
|
||||
# Specifics
|
||||
# ---------
|
||||
|
||||
if is_py2:
|
||||
cmp = cmp
|
||||
|
||||
elif is_py3:
|
||||
def cmp(a, b):
|
||||
"""
|
||||
Compare two objects.
|
||||
Returns a negative number if C{a < b}, zero if they are equal, and a
|
||||
positive number if C{a > b}.
|
||||
"""
|
||||
if a < b:
|
||||
return -1
|
||||
elif a == b:
|
||||
return 0
|
||||
else:
|
||||
return 1
|
||||
|
||||
|
||||
def comparable(klass):
|
||||
"""
|
||||
Class decorator that ensures support for the special C{__cmp__} method.
|
||||
On Python 2 this does nothing.
|
||||
On Python 3, C{__eq__}, C{__lt__}, etc. methods are added to the class,
|
||||
relying on C{__cmp__} to implement their comparisons.
|
||||
"""
|
||||
# On Python 2, __cmp__ will just work, so no need to add extra methods:
|
||||
if not is_py3:
|
||||
return klass
|
||||
|
||||
def __eq__(self, other):
|
||||
c = self.__cmp__(other)
|
||||
if c is NotImplemented:
|
||||
return c
|
||||
return c == 0
|
||||
|
||||
def __ne__(self, other):
|
||||
c = self.__cmp__(other)
|
||||
if c is NotImplemented:
|
||||
return c
|
||||
return c != 0
|
||||
|
||||
def __lt__(self, other):
|
||||
c = self.__cmp__(other)
|
||||
if c is NotImplemented:
|
||||
return c
|
||||
return c < 0
|
||||
|
||||
def __le__(self, other):
|
||||
c = self.__cmp__(other)
|
||||
if c is NotImplemented:
|
||||
return c
|
||||
return c <= 0
|
||||
|
||||
def __gt__(self, other):
|
||||
c = self.__cmp__(other)
|
||||
if c is NotImplemented:
|
||||
return c
|
||||
return c > 0
|
||||
|
||||
def __ge__(self, other):
|
||||
c = self.__cmp__(other)
|
||||
if c is NotImplemented:
|
||||
return c
|
||||
return c >= 0
|
||||
|
||||
klass.__lt__ = __lt__
|
||||
klass.__gt__ = __gt__
|
||||
klass.__le__ = __le__
|
||||
klass.__ge__ = __ge__
|
||||
klass.__eq__ = __eq__
|
||||
klass.__ne__ = __ne__
|
||||
return klass
|
||||
+139
-86
@@ -1,4 +1,3 @@
|
||||
|
||||
# ___ __ ___ _ _ ___
|
||||
# || \/ | ||=|| \\// ||=||
|
||||
# || | || || // || ||
|
||||
@@ -6,19 +5,21 @@
|
||||
# Ignore warnings for yaml usage.
|
||||
import warnings
|
||||
import ruamel.yaml
|
||||
warnings.simplefilter('ignore', ruamel.yaml.error.UnsafeLoaderWarning)
|
||||
|
||||
warnings.simplefilter('ignore', ruamel.yaml.error.UnsafeLoaderWarning)
|
||||
|
||||
import email.utils
|
||||
import time
|
||||
from datetime import timedelta, datetime as Datetime
|
||||
|
||||
import functools
|
||||
import pytz
|
||||
import humanize
|
||||
import dateparser
|
||||
import pendulum
|
||||
from tzlocal import get_localzone
|
||||
|
||||
from .compat import cmp, comparable
|
||||
|
||||
_EPOCH_START = (1970, 1, 1)
|
||||
|
||||
@@ -26,7 +27,7 @@ _EPOCH_START = (1970, 1, 1)
|
||||
def validate_class_type_arguments(operator):
|
||||
"""
|
||||
Decorator to validate all the arguments to function
|
||||
are of the type of calling class
|
||||
are of the type of calling class for passed operator
|
||||
"""
|
||||
|
||||
def inner(function):
|
||||
@@ -38,7 +39,33 @@ def validate_class_type_arguments(operator):
|
||||
return function(self, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
return inner
|
||||
|
||||
|
||||
def validate_arguments_type_of_function(param_type=None):
|
||||
"""
|
||||
Decorator to validate the <type> of arguments in
|
||||
the calling function are of the `param_type` class.
|
||||
|
||||
if `param_type` is None, uses `param_type` as the class where it is used.
|
||||
|
||||
Note: Use this decorator on the functions of the class.
|
||||
"""
|
||||
def inner(function):
|
||||
def wrapper(self, *args, **kwargs):
|
||||
type_ = param_type or type(self)
|
||||
for arg in args + tuple(kwargs.values()):
|
||||
if not isinstance(arg, type_):
|
||||
raise TypeError(('Invalid Type: {}.{}() accepts only the '
|
||||
'arguments of type "<{}>"').format(
|
||||
type(self).__name__,
|
||||
function.__name__,
|
||||
type_.__name__,
|
||||
)
|
||||
)
|
||||
return function(self, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
return inner
|
||||
|
||||
|
||||
@@ -61,40 +88,40 @@ class MayaDT(object):
|
||||
|
||||
@validate_class_type_arguments('==')
|
||||
def __eq__(self, maya_dt):
|
||||
return self._epoch == maya_dt._epoch
|
||||
return int(self._epoch) == int(maya_dt._epoch)
|
||||
|
||||
@validate_class_type_arguments('!=')
|
||||
def __ne__(self, maya_dt):
|
||||
return self._epoch != maya_dt._epoch
|
||||
return int(self._epoch) != int(maya_dt._epoch)
|
||||
|
||||
@validate_class_type_arguments('<')
|
||||
def __lt__(self, maya_dt):
|
||||
return self._epoch < maya_dt._epoch
|
||||
return int(self._epoch) < int(maya_dt._epoch)
|
||||
|
||||
@validate_class_type_arguments('<=')
|
||||
def __le__(self, maya_dt):
|
||||
return self._epoch <= maya_dt._epoch
|
||||
return int(self._epoch) <= int(maya_dt._epoch)
|
||||
|
||||
@validate_class_type_arguments('>')
|
||||
def __gt__(self, maya_dt):
|
||||
return self._epoch > maya_dt._epoch
|
||||
return int(self._epoch) > int(maya_dt._epoch)
|
||||
|
||||
@validate_class_type_arguments('>=')
|
||||
def __ge__(self, maya_dt):
|
||||
return self._epoch >= maya_dt._epoch
|
||||
return int(self._epoch) >= int(maya_dt._epoch)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.epoch)
|
||||
return hash(int(self.epoch))
|
||||
|
||||
def __add__(self, item):
|
||||
return self.add(seconds=seconds_or_timedelta(item).total_seconds())
|
||||
def __add__(self, duration):
|
||||
return self.add(seconds=seconds_or_timedelta(duration).total_seconds())
|
||||
|
||||
def __radd__(self, item):
|
||||
return self + item
|
||||
def __radd__(self, duration):
|
||||
return self + duration
|
||||
|
||||
def __sub__(self, item):
|
||||
def __sub__(self, duration):
|
||||
return self.subtract(
|
||||
seconds=seconds_or_timedelta(item).total_seconds())
|
||||
seconds=seconds_or_timedelta(duration).total_seconds())
|
||||
|
||||
def add(self, **kwargs):
|
||||
""""Returns a new MayaDT object with the given offsets."""
|
||||
@@ -128,6 +155,7 @@ class MayaDT(object):
|
||||
return get_localzone()
|
||||
|
||||
@staticmethod
|
||||
@validate_arguments_type_of_function(Datetime)
|
||||
def __dt_to_epoch(dt):
|
||||
"""Converts a datetime into an epoch."""
|
||||
|
||||
@@ -142,24 +170,25 @@ class MayaDT(object):
|
||||
# ---------
|
||||
|
||||
@classmethod
|
||||
@validate_arguments_type_of_function(Datetime)
|
||||
def from_datetime(klass, dt):
|
||||
"""Returns MayaDT instance from datetime."""
|
||||
return klass(klass.__dt_to_epoch(dt))
|
||||
|
||||
@classmethod
|
||||
def from_iso8601(klass, string):
|
||||
def from_iso8601(klass, iso8601_string):
|
||||
"""Returns MayaDT instance from iso8601 string."""
|
||||
return parse(string)
|
||||
return parse(iso8601_string)
|
||||
|
||||
@staticmethod
|
||||
def from_rfc2822(string):
|
||||
def from_rfc2822(rfc2822_string):
|
||||
"""Returns MayaDT instance from rfc2822 string."""
|
||||
return parse(string)
|
||||
return parse(rfc2822_string)
|
||||
|
||||
@staticmethod
|
||||
def from_rfc3339(string):
|
||||
def from_rfc3339(rfc3339_string):
|
||||
"""Returns MayaDT instance from rfc3339 string."""
|
||||
return parse(string)
|
||||
return parse(rfc3339_string)
|
||||
|
||||
# Exporters
|
||||
# ---------
|
||||
@@ -243,7 +272,7 @@ class MayaDT(object):
|
||||
|
||||
@property
|
||||
def epoch(self):
|
||||
return self._epoch
|
||||
return int(self._epoch)
|
||||
|
||||
# Human Slang Extras
|
||||
# ------------------
|
||||
@@ -276,15 +305,17 @@ def to_iso8601(dt):
|
||||
|
||||
|
||||
def end_of_day_midnight(dt):
|
||||
return dt if dt.time() == time.min else\
|
||||
return dt if dt.time() == time.min else \
|
||||
(dt.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=1))
|
||||
|
||||
|
||||
@comparable
|
||||
class MayaInterval(object):
|
||||
"""
|
||||
A MayaInterval represents a range between two datetimes, inclusive of the start
|
||||
and exclusive of the end.
|
||||
"""
|
||||
|
||||
def __init__(self, start=None, end=None, duration=None):
|
||||
try:
|
||||
# Ensure that proper arguments were passed.
|
||||
@@ -299,7 +330,8 @@ class MayaInterval(object):
|
||||
'Exactly 2 of start, end, and duration must be specified')
|
||||
|
||||
# Convert duration to timedelta if seconds were provided.
|
||||
duration = seconds_or_timedelta(duration)
|
||||
if duration:
|
||||
duration = seconds_or_timedelta(duration)
|
||||
|
||||
if not start:
|
||||
start = end - duration
|
||||
@@ -314,7 +346,7 @@ class MayaInterval(object):
|
||||
self.end = end
|
||||
|
||||
def __repr__(self):
|
||||
return '<MayaInterval start={!r0} end={!r1}>'.format(self.start, self.end)
|
||||
return '<MayaInterval start={0!r} end={1!r}>'.format(self.start, self.end)
|
||||
|
||||
def iso8601(self):
|
||||
"""Returns an ISO 8601 representation of the MayaInterval."""
|
||||
@@ -335,19 +367,21 @@ class MayaInterval(object):
|
||||
|
||||
# # Start and duration, such as "2007-03-01T13:00:00Z/P1Y2M10DT2H30M"
|
||||
# # Duration and end, such as "P1Y2M10DT2H30M/2008-05-11T15:30:00Z"
|
||||
raise NotImplentedError()
|
||||
raise NotImplementedError()
|
||||
|
||||
@validate_arguments_type_of_function()
|
||||
def __and__(self, maya_interval):
|
||||
return self.intersection(maya_interval)
|
||||
|
||||
def __and__(self, i):
|
||||
return self.intersection(i)
|
||||
@validate_arguments_type_of_function()
|
||||
def __or__(self, maya_interval):
|
||||
return self.combine(maya_interval)
|
||||
|
||||
def __or__(self, i):
|
||||
return self.combine(i)
|
||||
|
||||
def __eq__(self, i):
|
||||
@validate_arguments_type_of_function()
|
||||
def __eq__(self, maya_interval):
|
||||
return (
|
||||
self.start == i.start and
|
||||
self.end == i.end
|
||||
self.start == maya_interval.start and
|
||||
self.end == maya_interval.end
|
||||
)
|
||||
|
||||
def __hash__(self):
|
||||
@@ -357,10 +391,11 @@ class MayaInterval(object):
|
||||
yield self.start
|
||||
yield self.end
|
||||
|
||||
def __cmp__(self, i):
|
||||
@validate_arguments_type_of_function()
|
||||
def __cmp__(self, maya_interval):
|
||||
return (
|
||||
cmp(self.start, i.start) or
|
||||
cmp(self.end, i.end)
|
||||
cmp(self.start, maya_interval.start) or
|
||||
cmp(self.end, maya_interval.end)
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -375,45 +410,49 @@ class MayaInterval(object):
|
||||
def is_instant(self):
|
||||
return self.timedelta == timedelta(seconds=0)
|
||||
|
||||
def intersects(self, i):
|
||||
return self & i is not None
|
||||
def intersects(self, maya_interval):
|
||||
return self & maya_interval is not None
|
||||
|
||||
@property
|
||||
def midpoint(self):
|
||||
return self.start.add(seconds=(self.duration / 2))
|
||||
|
||||
def combine(self, i):
|
||||
@validate_arguments_type_of_function()
|
||||
def combine(self, maya_interval):
|
||||
"""Returns a combined list of timespans, merged together."""
|
||||
ii = sorted([self, i])
|
||||
if self & i or self.is_adjacent(i):
|
||||
interval_list = sorted([self, maya_interval])
|
||||
if self & maya_interval or self.is_adjacent(maya_interval):
|
||||
return [
|
||||
MayaInterval(
|
||||
ii[0].start,
|
||||
max(ii[0].end, ii[1].end),
|
||||
interval_list[0].start,
|
||||
max(interval_list[0].end, interval_list[1].end),
|
||||
),
|
||||
]
|
||||
return ii
|
||||
return interval_list
|
||||
|
||||
def subtract(self, i):
|
||||
""""Removes the given inerval."""
|
||||
if not self & i:
|
||||
@validate_arguments_type_of_function()
|
||||
def subtract(self, maya_interval):
|
||||
""""Removes the given interval."""
|
||||
if not self & maya_interval:
|
||||
return [self]
|
||||
elif i.contains(self):
|
||||
elif maya_interval.contains(self):
|
||||
return []
|
||||
|
||||
ii = []
|
||||
if self.start < i.start:
|
||||
ii.append(MayaInterval(self.start, i.start))
|
||||
if self.end > i.end:
|
||||
ii.append(MayaInterval(i.end, self.end))
|
||||
return ii
|
||||
interval_list = []
|
||||
if self.start < maya_interval.start:
|
||||
interval_list.append(MayaInterval(self.start, maya_interval.start))
|
||||
if self.end > maya_interval.end:
|
||||
interval_list.append(MayaInterval(maya_interval.end, self.end))
|
||||
return interval_list
|
||||
|
||||
def split(self, duration, include_remainder=True):
|
||||
|
||||
# Convert seconds to timedelta, if appropriate.
|
||||
duration = seconds_or_timedelta(duration)
|
||||
|
||||
assert duration > timedelta(seconds=0), 'cannot call split with a non-positive timedelta'
|
||||
if duration <= timedelta(seconds=0):
|
||||
raise ValueError('cannot call split with a non-positive timedelta')
|
||||
|
||||
start = self.start
|
||||
while start < self.end:
|
||||
if start + duration <= self.end:
|
||||
@@ -429,7 +468,10 @@ class MayaInterval(object):
|
||||
duration = seconds_or_timedelta(duration)
|
||||
timezone = pytz.timezone(timezone)
|
||||
|
||||
assert duration > timedelta(seconds=0), 'cannot quantize by non-positive timedelta'
|
||||
|
||||
if duration <= timedelta(seconds=0):
|
||||
raise ValueError('cannot quantize by non-positive timedelta')
|
||||
|
||||
epoch = timezone.localize(Datetime(1970, 1, 1))
|
||||
seconds = int(duration.total_seconds())
|
||||
|
||||
@@ -452,39 +494,42 @@ class MayaInterval(object):
|
||||
end=MayaDT.from_datetime(epoch).add(seconds=end_seconds),
|
||||
)
|
||||
|
||||
def intersection(self, i):
|
||||
@validate_arguments_type_of_function()
|
||||
def intersection(self, maya_interval):
|
||||
"""Returns the intersection between two intervals."""
|
||||
|
||||
start = max(self.start, i.start)
|
||||
end = min(self.end, i.end)
|
||||
start = max(self.start, maya_interval.start)
|
||||
end = min(self.end, maya_interval.end)
|
||||
|
||||
either_instant = self.is_instant or i.is_instant
|
||||
either_instant = self.is_instant or maya_interval.is_instant
|
||||
instant_overlap = (
|
||||
self.start == i.start or
|
||||
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)
|
||||
|
||||
def contains(self, i):
|
||||
@validate_arguments_type_of_function()
|
||||
def contains(self, maya_interval):
|
||||
return (
|
||||
self.start <= i.start and
|
||||
self.end >= i.end
|
||||
self.start <= maya_interval.start and
|
||||
self.end >= maya_interval.end
|
||||
)
|
||||
|
||||
def __contains__(self, item):
|
||||
if isinstance(item, MayaDT):
|
||||
return self.contains_dt(item)
|
||||
def __contains__(self, maya_dt):
|
||||
if isinstance(maya_dt, MayaDT):
|
||||
return self.contains_dt(maya_dt)
|
||||
|
||||
return item.contains(self)
|
||||
return self.contains(maya_dt)
|
||||
|
||||
def contains_dt(self, dt):
|
||||
return self.start <= dt < self.end
|
||||
|
||||
def is_adjacent(self, i):
|
||||
@validate_arguments_type_of_function()
|
||||
def is_adjacent(self, maya_interval):
|
||||
return (
|
||||
self.start == i.end or
|
||||
self.end == i.start
|
||||
self.start == maya_interval.end or
|
||||
self.end == maya_interval.start
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -504,11 +549,11 @@ class MayaInterval(object):
|
||||
).replace(' ', '').strip('\r\n').replace('\n', '\r\n')
|
||||
|
||||
@staticmethod
|
||||
def flatten(ii):
|
||||
return reduce(lambda reduced, i: (
|
||||
(reduced[:-1] + i.combine(reduced[-1]))
|
||||
if reduced else [i]
|
||||
), sorted(ii), [])
|
||||
def flatten(interval_list):
|
||||
return functools.reduce(lambda reduced, maya_interval: (
|
||||
(reduced[:-1] + maya_interval.combine(reduced[-1]))
|
||||
if reduced else [maya_interval]
|
||||
), sorted(interval_list), [])
|
||||
|
||||
@classmethod
|
||||
def from_datetime(cls, start_dt=None, end_dt=None, duration=None):
|
||||
@@ -536,7 +581,8 @@ def when(string, timezone='UTC'):
|
||||
timezone -- timezone referenced from (default: 'UTC')
|
||||
|
||||
"""
|
||||
dt = dateparser.parse(string, settings={'TIMEZONE': timezone, 'RETURN_AS_TIMEZONE_AWARE': True, 'TO_TIMEZONE': 'UTC'})
|
||||
dt = dateparser.parse(string,
|
||||
settings={'TIMEZONE': timezone, 'RETURN_AS_TIMEZONE_AWARE': True, 'TO_TIMEZONE': 'UTC'})
|
||||
|
||||
if dt is None:
|
||||
raise ValueError('invalid datetime input specified.')
|
||||
@@ -557,13 +603,20 @@ def parse(string, day_first=False):
|
||||
return MayaDT.from_datetime(dt)
|
||||
|
||||
|
||||
def seconds_or_timedelta(s):
|
||||
def seconds_or_timedelta(duration):
|
||||
"""Returns `datetime.timedelta` object for the passed duration.
|
||||
|
||||
# Convert seconds into timedelta.
|
||||
if isinstance(s, int):
|
||||
s = timedelta(seconds=s)
|
||||
|
||||
return s
|
||||
Keyword Arguments:
|
||||
duration -- `datetime.timedelta` object or seconds in `int` format.
|
||||
"""
|
||||
if isinstance(duration, int):
|
||||
dt_timedelta = timedelta(seconds=duration)
|
||||
elif isinstance(duration, timedelta):
|
||||
dt_timedelta = duration
|
||||
else:
|
||||
raise TypeError('Expects argument as `datetime.timedelta` object '
|
||||
'or seconds in `int` format')
|
||||
return dt_timedelta
|
||||
|
||||
|
||||
def intervals(start, end, interval):
|
||||
@@ -33,18 +33,38 @@ required = [
|
||||
'pendulum'
|
||||
]
|
||||
|
||||
packages = [
|
||||
'maya',
|
||||
]
|
||||
|
||||
# About dict to store version and package info
|
||||
about = dict()
|
||||
with open(os.path.join(here, 'maya', '__version__.py'), 'r', 'utf-8') as f:
|
||||
exec(f.read(), about)
|
||||
|
||||
setup(
|
||||
name='maya',
|
||||
version='0.3.0',
|
||||
version=about['__version__'],
|
||||
description='Datetimes for Humans.',
|
||||
long_description=long_description,
|
||||
author='Kenneth Reitz',
|
||||
author_email='me@kennethreitz.org',
|
||||
url='https://github.com/kennethreitz/maya',
|
||||
py_modules=['maya'],
|
||||
packages=packages,
|
||||
install_requires=required,
|
||||
license='MIT',
|
||||
classifiers=(
|
||||
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Natural Language :: English',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: Implementation',
|
||||
'Programming Language :: Python :: Implementation :: CPython',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules'
|
||||
),
|
||||
)
|
||||
|
||||
@@ -204,6 +204,17 @@ def test_comparison_operations():
|
||||
with pytest.raises(TypeError):
|
||||
now >= 1
|
||||
|
||||
|
||||
def test_seconds_or_timedelta():
|
||||
# test for value in seconds
|
||||
assert maya.seconds_or_timedelta(1234) == timedelta(0, 1234)
|
||||
# test for value as `datetime.timedelta`
|
||||
assert maya.seconds_or_timedelta(timedelta(0, 1234)) == timedelta(0, 1234)
|
||||
# test for invalid value
|
||||
with pytest.raises(TypeError):
|
||||
maya.seconds_or_timedelta('invalid interval')
|
||||
|
||||
|
||||
def test_intervals():
|
||||
now = maya.now()
|
||||
tomorrow = now.add(days=1)
|
||||
@@ -5,7 +5,7 @@ import pytest
|
||||
import pytz
|
||||
|
||||
import maya
|
||||
|
||||
from maya.compat import cmp
|
||||
|
||||
Los_Angeles = pytz.timezone('America/Los_Angeles')
|
||||
New_York = pytz.timezone('America/New_York')
|
||||
@@ -34,9 +34,9 @@ def test_interval_requires_2_of_start_end_duration():
|
||||
|
||||
|
||||
def test_interval_requires_end_time_after_or_on_start_time():
|
||||
maya.MayaInterval(start=maya.now(), duration=0)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
maya.MayaInterval(start=maya.now(), duration=0)
|
||||
maya.MayaInterval(start=maya.now(), duration=-1)
|
||||
|
||||
|
||||
@@ -65,32 +65,32 @@ def test_interval_init_end_duration():
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start_doy1,end_doy1,start_doy2,end_doy2,intersection_doys', (
|
||||
(0, 2, 1, 3, (1, 2)),
|
||||
(0, 2, 3, 4, None),
|
||||
(0, 2, 2, 3, None),
|
||||
(0, 1, 0, 1, (0, 1)),
|
||||
(1, 1, 1, 3, (1, 1)),
|
||||
(1, 1, 1, 1, (1, 1)),
|
||||
(1, 1, 2, 3, None),
|
||||
(2, 2, 1, 3, (2, 2)),
|
||||
(1, 3, 1, 1, (1, 1)),
|
||||
(2, 3, 1, 1, None),
|
||||
(1, 3, 2, 2, (2, 2)),
|
||||
(0, 2, 1, 3, (1, 2)),
|
||||
(0, 2, 3, 4, None),
|
||||
(0, 2, 2, 3, None),
|
||||
(0, 1, 0, 1, (0, 1)),
|
||||
(1, 1, 1, 3, (1, 1)),
|
||||
(1, 1, 1, 1, (1, 1)),
|
||||
(1, 1, 2, 3, None),
|
||||
(2, 2, 1, 3, (2, 2)),
|
||||
(1, 3, 1, 1, (1, 1)),
|
||||
(2, 3, 1, 1, None),
|
||||
(1, 3, 2, 2, (2, 2)),
|
||||
), ids=(
|
||||
'overlapping',
|
||||
'non-overlapping',
|
||||
'adjacent',
|
||||
'equal',
|
||||
'instant overlapping start only',
|
||||
'instant equal',
|
||||
'instant disjoint',
|
||||
'instant overlapping',
|
||||
'instant overlapping start only (left)',
|
||||
'instant disjoint (left)',
|
||||
'instant overlapping (left)'
|
||||
'overlapping',
|
||||
'non-overlapping',
|
||||
'adjacent',
|
||||
'equal',
|
||||
'instant overlapping start only',
|
||||
'instant equal',
|
||||
'instant disjoint',
|
||||
'instant overlapping',
|
||||
'instant overlapping start only (left)',
|
||||
'instant disjoint (left)',
|
||||
'instant overlapping (left)'
|
||||
))
|
||||
def test_interval_intersection(
|
||||
start_doy1, end_doy1, start_doy2, end_doy2, intersection_doys
|
||||
start_doy1, end_doy1, start_doy2, end_doy2, intersection_doys
|
||||
):
|
||||
base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
|
||||
interval1 = maya.MayaInterval(
|
||||
@@ -111,6 +111,10 @@ def test_interval_intersection(
|
||||
else:
|
||||
assert (interval1 & interval2) is None
|
||||
|
||||
# check invalid argument
|
||||
with pytest.raises(TypeError):
|
||||
interval1 & 'invalid type'
|
||||
|
||||
|
||||
def test_interval_intersects():
|
||||
base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
|
||||
@@ -122,6 +126,10 @@ def test_interval_intersects():
|
||||
base.add(days=3),
|
||||
))
|
||||
|
||||
# check invalid argument
|
||||
with pytest.raises(TypeError):
|
||||
interval.intersects('invalid type')
|
||||
|
||||
|
||||
def test_and_operator():
|
||||
base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
|
||||
@@ -134,6 +142,10 @@ def test_and_operator():
|
||||
interval1.intersection(interval2)
|
||||
)
|
||||
|
||||
# check invalid argument
|
||||
with pytest.raises(TypeError):
|
||||
interval1.intersection('invalid type')
|
||||
|
||||
|
||||
def test_interval_eq_operator():
|
||||
start = maya.now()
|
||||
@@ -143,6 +155,13 @@ def test_interval_eq_operator():
|
||||
assert interval == maya.MayaInterval(start=start, end=end)
|
||||
assert interval != maya.MayaInterval(start=start, end=end.add(days=1))
|
||||
|
||||
# check invalid argument
|
||||
with pytest.raises(TypeError):
|
||||
interval == 'invalid type'
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
interval != 'invalid type'
|
||||
|
||||
|
||||
def test_interval_timedelta():
|
||||
start = maya.now()
|
||||
@@ -161,20 +180,20 @@ def test_interval_duration():
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start_doy1,end_doy1,start_doy2,end_doy2,expected', (
|
||||
(0, 2, 1, 3, False),
|
||||
(0, 2, 3, 4, False),
|
||||
(0, 2, 2, 3, False),
|
||||
(0, 1, 0, 1, True),
|
||||
(0, 3, 1, 2, True),
|
||||
(0, 2, 1, 3, False),
|
||||
(0, 2, 3, 4, False),
|
||||
(0, 2, 2, 3, False),
|
||||
(0, 1, 0, 1, True),
|
||||
(0, 3, 1, 2, True),
|
||||
), ids=(
|
||||
'overlapping',
|
||||
'non-overlapping',
|
||||
'adjacent',
|
||||
'equal',
|
||||
'subset',
|
||||
'overlapping',
|
||||
'non-overlapping',
|
||||
'adjacent',
|
||||
'equal',
|
||||
'subset',
|
||||
))
|
||||
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))
|
||||
interval1 = maya.MayaInterval(
|
||||
@@ -187,24 +206,28 @@ def test_interval_contains(
|
||||
)
|
||||
|
||||
assert interval1.contains(interval2) is expected
|
||||
assert (interval1 in interval2) is expected
|
||||
assert (interval2 in interval1) is expected
|
||||
|
||||
# check invalid argument
|
||||
with pytest.raises(TypeError):
|
||||
interval1.contains('invalid type')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start_doy,end_doy,dt_doy,expected', (
|
||||
(2, 4, 1, False),
|
||||
(2, 4, 2, True),
|
||||
(2, 4, 3, True),
|
||||
(2, 4, 4, False),
|
||||
(2, 4, 5, False),
|
||||
(2, 4, 1, False),
|
||||
(2, 4, 2, True),
|
||||
(2, 4, 3, True),
|
||||
(2, 4, 4, False),
|
||||
(2, 4, 5, False),
|
||||
), ids=(
|
||||
'before-start',
|
||||
'on-start',
|
||||
'during',
|
||||
'on-end',
|
||||
'after-end',
|
||||
'before-start',
|
||||
'on-start',
|
||||
'during',
|
||||
'on-end',
|
||||
'after-end',
|
||||
))
|
||||
def test_interval_in_operator_maya_dt(
|
||||
start_doy, end_doy, dt_doy, expected
|
||||
start_doy, end_doy, dt_doy, expected
|
||||
):
|
||||
base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
|
||||
interval = maya.MayaInterval(
|
||||
@@ -215,6 +238,10 @@ def test_interval_in_operator_maya_dt(
|
||||
|
||||
assert (dt in interval) is expected
|
||||
|
||||
# check invalid argument
|
||||
with pytest.raises(TypeError):
|
||||
'invalid type' in interval
|
||||
|
||||
|
||||
def test_interval_hash():
|
||||
start = maya.now()
|
||||
@@ -239,10 +266,10 @@ def test_interval_iter():
|
||||
(2, 4, 1, 3, 1),
|
||||
(1, 2, 1, 3, -1),
|
||||
], 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):
|
||||
base = maya.now()
|
||||
@@ -256,6 +283,10 @@ def test_interval_cmp(start1, end1, start2, end2, expected):
|
||||
)
|
||||
assert cmp(interval1, interval2) == expected
|
||||
|
||||
# check invalid argument
|
||||
with pytest.raises(TypeError):
|
||||
cmp(interval1, 'invalid type')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start1,end1,start2,end2,expected', [
|
||||
(1, 2, 2, 3, [(1, 3)]),
|
||||
@@ -263,10 +294,10 @@ def test_interval_cmp(start1, end1, start2, end2, expected):
|
||||
(1, 2, 3, 4, [(1, 2), (3, 4)]),
|
||||
(1, 5, 2, 3, [(1, 5)]),
|
||||
], ids=(
|
||||
'adjacent',
|
||||
'overlapping',
|
||||
'non-overlapping',
|
||||
'contains',
|
||||
'adjacent',
|
||||
'overlapping',
|
||||
'non-overlapping',
|
||||
'contains',
|
||||
))
|
||||
def test_interval_combine(start1, end1, start2, end2, expected):
|
||||
base = maya.now()
|
||||
@@ -286,6 +317,10 @@ def test_interval_combine(start1, end1, start2, end2, expected):
|
||||
assert interval1.combine(interval2) == expected_intervals
|
||||
assert interval2.combine(interval1) == expected_intervals
|
||||
|
||||
# check invalid argument
|
||||
with pytest.raises(TypeError):
|
||||
interval2.combine('invalid type')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start1,end1,start2,end2,expected', [
|
||||
(1, 2, 3, 4, [(1, 2)]),
|
||||
@@ -297,14 +332,14 @@ def test_interval_combine(start1, end1, start2, end2, expected):
|
||||
(1, 4, 1, 2, [(2, 4)]),
|
||||
(1, 4, 3, 4, [(1, 3)]),
|
||||
], ids=(
|
||||
'non-overlapping',
|
||||
'adjacent',
|
||||
'contains',
|
||||
'splits',
|
||||
'overlaps-left',
|
||||
'overlaps-right',
|
||||
'overlaps-left-identical-start',
|
||||
'overlaps-right-identical-end',
|
||||
'non-overlapping',
|
||||
'adjacent',
|
||||
'contains',
|
||||
'splits',
|
||||
'overlaps-left',
|
||||
'overlaps-right',
|
||||
'overlaps-left-identical-start',
|
||||
'overlaps-right-identical-end',
|
||||
))
|
||||
def test_interval_subtract(start1, end1, start2, end2, expected):
|
||||
base = maya.now()
|
||||
@@ -323,6 +358,10 @@ def test_interval_subtract(start1, end1, start2, end2, expected):
|
||||
|
||||
assert interval1.subtract(interval2) == expected_intervals
|
||||
|
||||
# check invalid argument
|
||||
with pytest.raises(TypeError):
|
||||
interval1.subtract('invalid type')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start1,end1,start2,end2,expected', [
|
||||
(1, 2, 2, 3, True),
|
||||
@@ -330,10 +369,10 @@ def test_interval_subtract(start1, end1, start2, end2, expected):
|
||||
(1, 3, 2, 3, False),
|
||||
(2, 3, 4, 5, False),
|
||||
], ids=(
|
||||
'adjacent-right',
|
||||
'adjacent-left',
|
||||
'overlapping',
|
||||
'non-overlapping',
|
||||
'adjacent-right',
|
||||
'adjacent-left',
|
||||
'overlapping',
|
||||
'non-overlapping',
|
||||
))
|
||||
def test_interval_is_adjacent(start1, end1, start2, end2, expected):
|
||||
base = maya.now()
|
||||
@@ -347,6 +386,10 @@ def test_interval_is_adjacent(start1, end1, start2, end2, expected):
|
||||
)
|
||||
assert interval1.is_adjacent(interval2) == expected
|
||||
|
||||
# check invalid argument
|
||||
with pytest.raises(TypeError):
|
||||
interval1.is_adjacent('invalid type')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start,end,delta,include_remainder,expected', [
|
||||
(0, 10, 5, False, [(0, 5), (5, 10)]),
|
||||
@@ -356,12 +399,12 @@ def test_interval_is_adjacent(start1, end1, start2, end2, expected):
|
||||
(0, 2, 5, False, []),
|
||||
(0, 2, 5, True, [(0, 2)]),
|
||||
], ids=(
|
||||
'even-split',
|
||||
'even-split-include-partial',
|
||||
'uneven-split-do-not-include-partial',
|
||||
'uneven-split-include-partial',
|
||||
'delta-larger-than-timepsan-do-not-include-partial',
|
||||
'delta-larger-than-timepsan-include-partial',
|
||||
'even-split',
|
||||
'even-split-include-partial',
|
||||
'uneven-split-do-not-include-partial',
|
||||
'uneven-split-include-partial',
|
||||
'delta-larger-than-timepsan-do-not-include-partial',
|
||||
'delta-larger-than-timepsan-include-partial',
|
||||
))
|
||||
def test_interval_split(start, end, delta, include_remainder, expected):
|
||||
base = maya.now()
|
||||
@@ -387,10 +430,10 @@ def test_interval_split_non_positive_delta():
|
||||
end = start.add(days=1)
|
||||
interval = maya.MayaInterval(start=start, end=end)
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
with pytest.raises(ValueError):
|
||||
list(interval.split(timedelta(seconds=0)))
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
with pytest.raises(ValueError):
|
||||
list(interval.split(timedelta(seconds=-10)))
|
||||
|
||||
|
||||
@@ -406,16 +449,16 @@ def test_interval_split_non_positive_delta():
|
||||
((6, 20), (6, 50), 60, 'America/Chicago', False, (6, 0), (6, 0)),
|
||||
((6, 20), (6, 50), 60, 'America/Chicago', True, (6, 0), (7, 0)),
|
||||
], ids=(
|
||||
'normal',
|
||||
'normal-snap_out',
|
||||
'already-quantized',
|
||||
'already-quantized-snap_out',
|
||||
'with-timezone',
|
||||
'with-timezone-snap_out',
|
||||
'too-small',
|
||||
'too-small-snap_out',
|
||||
'too-small-with-timezone',
|
||||
'too-small-with-timezone-snap_out',
|
||||
'normal',
|
||||
'normal-snap_out',
|
||||
'already-quantized',
|
||||
'already-quantized-snap_out',
|
||||
'with-timezone',
|
||||
'with-timezone-snap_out',
|
||||
'too-small',
|
||||
'too-small-snap_out',
|
||||
'too-small-with-timezone',
|
||||
'too-small-with-timezone-snap_out',
|
||||
))
|
||||
def test_quantize(start, end, minutes, timezone, snap_out, expected_start, expected_end):
|
||||
base = maya.MayaDT.from_datetime(datetime(2017, 1, 1))
|
||||
@@ -442,9 +485,9 @@ def test_quantize_invalid_delta():
|
||||
end = start.add(days=1)
|
||||
interval = maya.MayaInterval(start=start, end=end)
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
with pytest.raises(ValueError):
|
||||
interval.quantize(timedelta(minutes=0))
|
||||
with pytest.raises(AssertionError):
|
||||
with pytest.raises(ValueError):
|
||||
interval.quantize(timedelta(minutes=-1))
|
||||
|
||||
|
||||
@@ -464,11 +507,14 @@ def test_interval_flatten_non_overlapping():
|
||||
def test_interval_flatten_adjacent():
|
||||
step = 2
|
||||
max_hour = 20
|
||||
base = maya.now()
|
||||
intervals = [maya.MayaInterval(
|
||||
start=base.add(hours=hour),
|
||||
duration=timedelta(hours=step),
|
||||
) for hour in range(0, max_hour, step)]
|
||||
base = maya.when('jan/1/2011')
|
||||
|
||||
intervals = [
|
||||
maya.MayaInterval(
|
||||
start=base.add(hours=hour),
|
||||
duration=timedelta(hours=step),
|
||||
) for hour in range(0, max_hour, step)
|
||||
]
|
||||
random.shuffle(intervals)
|
||||
|
||||
assert maya.MayaInterval.flatten(intervals) == [maya.MayaInterval(
|
||||
Reference in New Issue
Block a user