mirror of
https://github.com/kennethreitz/maya.git
synced 2026-06-05 14:50:19 +00:00
Compare commits
136 Commits
v0.1.0
...
Tafkas-master
| Author | SHA1 | Date | |
|---|---|---|---|
| d50dc8701c | |||
| 6c1cc24ad5 | |||
| 46b7e369b3 | |||
| 2b60e817ea | |||
| 0fea886c00 | |||
| ef52cb4b7d | |||
| f1be1585d3 | |||
| 89ee548ec1 | |||
| 09351afc68 | |||
| 5d4a217fd6 | |||
| 27950e6d35 | |||
| f2c52af853 | |||
| 01cc151b5c | |||
| eda4a92da9 | |||
| 6fbf8f2d74 | |||
| 343abed679 | |||
| 704af5a3a0 | |||
| f9f5b97f62 | |||
| 36fc82211f | |||
| 6eba043582 | |||
| 23652fe16a | |||
| 36379fc2f2 | |||
| c7b804985e | |||
| e4a928e8f4 | |||
| 080831519d | |||
| 43705d84d6 | |||
| 6521a7dbc5 | |||
| 1796fc1d40 | |||
| 450dff000b | |||
| 90ba4ded70 | |||
| 2e33ede5ac | |||
| 6a9661759f | |||
| f46c3792e8 | |||
| c07e51bf4d | |||
| 63d5e0ce90 | |||
| 361f7a45ad | |||
| 09db746027 | |||
| 15a447491a | |||
| f8d83c0370 | |||
| 7c47796ac6 | |||
| 9c88519e63 | |||
| 50d4558ffa | |||
| 77bfce0bae | |||
| 8aaa741793 | |||
| 5a442b3876 | |||
| 4822487efe | |||
| e0ee61fd43 | |||
| 08a654d523 | |||
| 0e260d761e | |||
| c9888a48e5 | |||
| 21743de790 | |||
| 09c3e352e4 | |||
| ba9e35525c | |||
| 562403da0f | |||
| e96db15c0e | |||
| a3b86b6025 | |||
| 2f47366cbf | |||
| c17a358c66 | |||
| b8300192c9 | |||
| d8f01a7789 | |||
| 199a290b1d | |||
| 82d20ad1dc | |||
| d6b8ac54ad | |||
| edfb9baae4 | |||
| 4fb95666ba | |||
| 03243e2194 | |||
| 6420cc91d9 | |||
| 40eeea252d | |||
| 2452b30d96 | |||
| 4e009dc51a | |||
| d535739413 | |||
| 299951bd0b | |||
| 84555b1f5c | |||
| 915337d2b4 | |||
| e31f018a23 | |||
| 0b64766b62 | |||
| 5c255d2682 | |||
| 702eaab906 | |||
| 5bbe383061 | |||
| d40e698fb9 | |||
| 327f057e63 | |||
| 470516146a | |||
| dbed0555db | |||
| 2fce84195e | |||
| 7ab9e48d45 | |||
| ee1a8e2438 | |||
| b0e91d6c0e | |||
| bce5d13401 | |||
| b4a23d668d | |||
| 90051da96d | |||
| 8f0ff0d68c | |||
| fd1262a8e8 | |||
| 7558261dc8 | |||
| 1407e688fd | |||
| 0c2b936111 | |||
| 9e72ef3d2f | |||
| eabba0b79e | |||
| 8af92bc33c | |||
| 917420071f | |||
| 2bb430b91d | |||
| 967638228a | |||
| 1ba2dac284 | |||
| 89a54abf1f | |||
| f119fe927b | |||
| 0efc489ba1 | |||
| 8b6ca639cb | |||
| 48c245a8ad | |||
| 29a5b990fd | |||
| 69c88552d7 | |||
| 2126c8f33e | |||
| dbc20b3026 | |||
| c521672f0e | |||
| f756b13d4d | |||
| 2ce5a23e38 | |||
| 605e15ae27 | |||
| 07a3e814a2 | |||
| 2e811d8689 | |||
| cbbd7d92c1 | |||
| d9e08034dd | |||
| e3b1eacb77 | |||
| be29d01160 | |||
| 5e4853936b | |||
| 7dbe450c28 | |||
| 3ce6734a5e | |||
| 6e2f530207 | |||
| c46d9874d4 | |||
| 87013d71e0 | |||
| a562f48e24 | |||
| 8fc07b3ab3 | |||
| 59064a5c50 | |||
| 7ae84f3266 | |||
| cc668974b6 | |||
| 6a4ddff215 | |||
| a0ab4fc3fb | |||
| c80ecad67b | |||
| 97842c01b3 |
+94
@@ -0,0 +1,94 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*,cover
|
||||
.hypothesis/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# dotenv
|
||||
.env
|
||||
|
||||
# virtualenv
|
||||
.venv/
|
||||
venv/
|
||||
ENV/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# PyCharm
|
||||
.idea/
|
||||
@@ -0,0 +1,9 @@
|
||||
language: python
|
||||
python:
|
||||
- "2.7"
|
||||
- "3.6"
|
||||
|
||||
# command to install dependencies
|
||||
install: pip install pipenv; pipenv lock; pipenv install --dev
|
||||
# command to run tests
|
||||
script: pipenv run pytest
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
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>`_)
|
||||
@@ -0,0 +1,10 @@
|
||||
[packages]
|
||||
humanize = "*"
|
||||
pytz = "*"
|
||||
dateparser = "*"
|
||||
"ruamel.yaml" = "*"
|
||||
tzlocal = "*"
|
||||
pendulum = ">=1.0"
|
||||
|
||||
[dev-packages]
|
||||
pytest = "*"
|
||||
Generated
+72
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "5617ff73ba51e60721267b24dc01e83f33d2a3692870b60e394b0f75ed2dc313"
|
||||
},
|
||||
"requires": {},
|
||||
"sources": [
|
||||
{
|
||||
"url": "https://pypi.python.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"dateparser": {
|
||||
"version": "==0.6.0"
|
||||
},
|
||||
"humanize": {
|
||||
"version": "==0.5.1"
|
||||
},
|
||||
"pendulum": {
|
||||
"version": "==1.2.0"
|
||||
},
|
||||
"python-dateutil": {
|
||||
"version": "==2.6.0"
|
||||
},
|
||||
"pytz": {
|
||||
"version": "==2017.2"
|
||||
},
|
||||
"pytzdata": {
|
||||
"version": "==2017.2"
|
||||
},
|
||||
"regex": {
|
||||
"version": "==2017.04.29"
|
||||
},
|
||||
"ruamel.ordereddict": {
|
||||
"version": "==0.4.9"
|
||||
},
|
||||
"ruamel.yaml": {
|
||||
"version": "==0.14.12"
|
||||
},
|
||||
"six": {
|
||||
"version": "==1.10.0"
|
||||
},
|
||||
"tzlocal": {
|
||||
"version": "==1.4"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"appdirs": {
|
||||
"version": "==1.4.3"
|
||||
},
|
||||
"packaging": {
|
||||
"version": "==16.8"
|
||||
},
|
||||
"py": {
|
||||
"version": "==1.4.33"
|
||||
},
|
||||
"pyparsing": {
|
||||
"version": "==2.2.0"
|
||||
},
|
||||
"pytest": {
|
||||
"version": "==3.0.7"
|
||||
},
|
||||
"setuptools": {
|
||||
"version": "==35.0.2"
|
||||
},
|
||||
"six": {
|
||||
"version": "==1.10.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
+84
-9
@@ -1,5 +1,15 @@
|
||||
Maya: Datetime for Humans™
|
||||
==========================
|
||||
Maya: Datetimes for Humans™
|
||||
===========================
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/maya.svg
|
||||
:target: https://pypi.python.org/pypi/maya
|
||||
|
||||
.. image:: https://travis-ci.org/kennethreitz/maya.svg?branch=master
|
||||
:target: https://travis-ci.org/kennethreitz/maya
|
||||
|
||||
.. image:: https://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
|
||||
with different locales on different systems. This library exists to make the
|
||||
@@ -8,6 +18,9 @@ simple things **much** easier, while admitting that time is an illusion
|
||||
|
||||
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.
|
||||
|
||||
|
||||
☤ Basic Usage of Maya
|
||||
---------------------
|
||||
|
||||
@@ -27,11 +40,17 @@ Behold, datetimes for humans!
|
||||
>>> tomorrow.slang_time()
|
||||
'23 hours from now'
|
||||
|
||||
# Also: MayaDT.from_iso8601(...)
|
||||
>>> tomorrow.iso8601()
|
||||
'2016-12-16T15:11:30.263350Z'
|
||||
'2017-02-10T22:17:01.445418Z'
|
||||
|
||||
>>> tomorrrow.rfc2822()
|
||||
'Fri, 16 Dec 2016 20:11:30 -0000'
|
||||
# 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>)
|
||||
@@ -44,15 +63,41 @@ Behold, datetimes for humans!
|
||||
>>> rand_day = maya.when('2011-02-07', timezone='US/Eastern')
|
||||
<MayaDT epoch=1297036800.0>
|
||||
|
||||
# Note how this is the 6th, not the 7th.
|
||||
>>> rand_day.day
|
||||
6
|
||||
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>
|
||||
|
||||
☤ 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?
|
||||
---------------------
|
||||
@@ -61,9 +106,22 @@ Behold, datetimes for humans!
|
||||
- Complete symmetric import and export of both ISO 8601 and RFC 2822 datetime stamps.
|
||||
- Fantastic parsing of both dates written for/by humans and machines (``maya.when()`` vs ``maya.parse()``).
|
||||
- Support for human slang, both import and export (e.g. `an hour ago`).
|
||||
- Datetimes can very easily be generated, with our without tzinfo attached.
|
||||
- Datetimes can very easily be generated, with or without tzinfo attached.
|
||||
- This library is based around epoch time, but dates before Jan 1 1970 are indeed supported, via negative integers.
|
||||
- Maya never panics, and always carrys a towel.
|
||||
- Maya never panics, and always carries a towel.
|
||||
|
||||
|
||||
☤ 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, and dealing with calendar events!
|
||||
|
||||
|
||||
☤ Installing Maya
|
||||
-----------------
|
||||
@@ -73,3 +131,20 @@ Installation is easy, with pip::
|
||||
$ pip install maya
|
||||
|
||||
✨🍰✨
|
||||
|
||||
☤ Like it?
|
||||
----------
|
||||
|
||||
`Say Thanks <https://saythanks.io/to/kennethreitz>`_!
|
||||
|
||||
|
||||
How to Contribute
|
||||
-----------------
|
||||
|
||||
#. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug.
|
||||
#. Fork `the repository`_ on GitHub to start making your changes to the **master** branch (or branch off of it).
|
||||
#. Write a test which shows that the bug was fixed or that the feature works as expected.
|
||||
#. Send a pull request and bug the maintainer until it gets merged and published. :) Make sure to add yourself to AUTHORS_.
|
||||
|
||||
.. _`the repository`: http://github.com/kennethreitz/maya
|
||||
.. _AUTHORS: https://github.com/kennethreitz/maya/blob/master/AUTHORS.rst
|
||||
|
||||
@@ -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
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
# ___ __ ___ _ _ ___
|
||||
# || \/ | ||=|| \\// ||=||
|
||||
# || | || || // || ||
|
||||
@@ -6,21 +5,43 @@
|
||||
# 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 datetime as Datetime
|
||||
from datetime import timedelta, datetime as Datetime
|
||||
|
||||
import functools
|
||||
import pytz
|
||||
import humanize
|
||||
import dateparser
|
||||
import iso8601
|
||||
import dateutil.parser
|
||||
import pendulum
|
||||
from tzlocal import get_localzone
|
||||
|
||||
EPOCH_START = (1970, 1, 1)
|
||||
from compat import cmp, comparable
|
||||
|
||||
_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
|
||||
"""
|
||||
|
||||
def inner(function):
|
||||
def wrapper(self, *args, **kwargs):
|
||||
for arg in args + tuple(kwargs.values()):
|
||||
if not isinstance(arg, self.__class__):
|
||||
raise TypeError('unorderable types: {}() {} {}()'.format(
|
||||
type(self).__name__, operator, type(arg).__name__))
|
||||
return function(self, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
class MayaDT(object):
|
||||
"""The Maya Datetime object."""
|
||||
@@ -32,9 +53,57 @@ class MayaDT(object):
|
||||
def __repr__(self):
|
||||
return '<MayaDT epoch={}>'.format(self._epoch)
|
||||
|
||||
def __str__(self):
|
||||
return self.rfc2822()
|
||||
|
||||
def __format__(self, *args, **kwargs):
|
||||
"""Return's the datetime's format"""
|
||||
return self.datetime(*args, **kwargs)
|
||||
return format(self.datetime(), *args, **kwargs)
|
||||
|
||||
@validate_class_type_arguments('==')
|
||||
def __eq__(self, maya_dt):
|
||||
return int(self._epoch) == int(maya_dt._epoch)
|
||||
|
||||
@validate_class_type_arguments('!=')
|
||||
def __ne__(self, maya_dt):
|
||||
return self._epoch != maya_dt._epoch
|
||||
|
||||
@validate_class_type_arguments('<')
|
||||
def __lt__(self, maya_dt):
|
||||
return self._epoch < maya_dt._epoch
|
||||
|
||||
@validate_class_type_arguments('<=')
|
||||
def __le__(self, maya_dt):
|
||||
return self._epoch <= maya_dt._epoch
|
||||
|
||||
@validate_class_type_arguments('>')
|
||||
def __gt__(self, maya_dt):
|
||||
return self._epoch > maya_dt._epoch
|
||||
|
||||
@validate_class_type_arguments('>=')
|
||||
def __ge__(self, maya_dt):
|
||||
return self._epoch >= maya_dt._epoch
|
||||
|
||||
def __hash__(self):
|
||||
return hash(int(self.epoch))
|
||||
|
||||
def __add__(self, item):
|
||||
return self.add(seconds=seconds_or_timedelta(item).total_seconds())
|
||||
|
||||
def __radd__(self, item):
|
||||
return self + item
|
||||
|
||||
def __sub__(self, item):
|
||||
return self.subtract(
|
||||
seconds=seconds_or_timedelta(item).total_seconds())
|
||||
|
||||
def add(self, **kwargs):
|
||||
""""Returns a new MayaDT object with the given offsets."""
|
||||
return self.from_datetime(pendulum.instance(self.datetime()).add(**kwargs))
|
||||
|
||||
def subtract(self, **kwargs):
|
||||
""""Returns a new MayaDT object with the given offsets."""
|
||||
return self.from_datetime(pendulum.instance(self.datetime()).subtract(**kwargs))
|
||||
|
||||
# Timezone Crap
|
||||
# -------------
|
||||
@@ -67,7 +136,7 @@ class MayaDT(object):
|
||||
if dt.tzinfo is None:
|
||||
dt = dt.replace(tzinfo=pytz.utc)
|
||||
|
||||
epoch_start = Datetime(*EPOCH_START, tzinfo=pytz.timezone('UTC'))
|
||||
epoch_start = Datetime(*_EPOCH_START, tzinfo=pytz.timezone('UTC'))
|
||||
return (dt - epoch_start).total_seconds()
|
||||
|
||||
# Importers
|
||||
@@ -81,14 +150,18 @@ class MayaDT(object):
|
||||
@classmethod
|
||||
def from_iso8601(klass, string):
|
||||
"""Returns MayaDT instance from iso8601 string."""
|
||||
dt = iso8601.parse_date(string)
|
||||
return klass.from_datetime(dt)
|
||||
return parse(string)
|
||||
|
||||
@staticmethod
|
||||
def from_rfc2822(string):
|
||||
"""Returns MayaDT instance from rfc2822 string."""
|
||||
return parse(string)
|
||||
|
||||
@staticmethod
|
||||
def from_rfc3339(string):
|
||||
"""Returns MayaDT instance from rfc3339 string."""
|
||||
return parse(string)
|
||||
|
||||
# Exporters
|
||||
# ---------
|
||||
|
||||
@@ -104,12 +177,16 @@ class MayaDT(object):
|
||||
dt = self.datetime().astimezone(pytz.timezone(to_timezone))
|
||||
else:
|
||||
dt = Datetime.utcfromtimestamp(self._epoch)
|
||||
dt.replace(tzinfo=self._tz)
|
||||
|
||||
# Strip the timezone info if requested to do so.
|
||||
if naive:
|
||||
return dt.replace(tzinfo=None)
|
||||
else:
|
||||
if dt.tzinfo is None:
|
||||
dt = dt.replace(tzinfo=self._tz)
|
||||
|
||||
return dt.replace(tzinfo=self._tz)
|
||||
return dt
|
||||
|
||||
def iso8601(self):
|
||||
"""Returns an ISO 8601 representation of the MayaDT."""
|
||||
@@ -121,6 +198,10 @@ class MayaDT(object):
|
||||
"""Returns an RFC 2822 representation of the MayaDT."""
|
||||
return email.utils.formatdate(self.epoch, usegmt=True)
|
||||
|
||||
def rfc3339(self):
|
||||
"""Returns an RFC 3339 representation of the MayaDT."""
|
||||
return self.datetime().strftime("%Y-%m-%dT%H:%M:%S.%f")[:-4] + "Z"
|
||||
|
||||
# Properties
|
||||
# ----------
|
||||
|
||||
@@ -136,6 +217,15 @@ class MayaDT(object):
|
||||
def day(self):
|
||||
return self.datetime().day
|
||||
|
||||
@property
|
||||
def week(self):
|
||||
return self.datetime().isocalendar()[1]
|
||||
|
||||
@property
|
||||
def weekday(self):
|
||||
"""Return the day of the week as an integer. Monday is 1 and Sunday is 7"""
|
||||
return self.datetime().isoweekday()
|
||||
|
||||
@property
|
||||
def hour(self):
|
||||
return self.datetime().hour
|
||||
@@ -161,7 +251,8 @@ class MayaDT(object):
|
||||
|
||||
def slang_date(self):
|
||||
""""Returns human slang representation of date."""
|
||||
return humanize.naturaldate(self.datetime())
|
||||
dt = self.datetime(naive=True, to_timezone=self.local_timezone)
|
||||
return humanize.naturaldate(dt)
|
||||
|
||||
def slang_time(self):
|
||||
""""Returns human slang representation of time."""
|
||||
@@ -169,6 +260,263 @@ class MayaDT(object):
|
||||
return humanize.naturaltime(dt)
|
||||
|
||||
|
||||
def to_utc_offset_naive(dt):
|
||||
if dt.tzinfo is None:
|
||||
return dt
|
||||
return dt.astimezone(pytz.utc).replace(tzinfo=None)
|
||||
|
||||
|
||||
def to_utc_offset_aware(dt):
|
||||
if dt.tzinfo is not None:
|
||||
return dt
|
||||
return pytz.utc.localize(dt)
|
||||
|
||||
|
||||
def to_iso8601(dt):
|
||||
return to_utc_offset_naive(dt).isoformat() + 'Z'
|
||||
|
||||
|
||||
def end_of_day_midnight(dt):
|
||||
return dt if dt.time() == time.min else \
|
||||
(dt.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=1))
|
||||
|
||||
|
||||
@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.
|
||||
assert any((
|
||||
(start and end),
|
||||
(start and duration is not None),
|
||||
(end and duration is not None),
|
||||
))
|
||||
assert not all((start, end, duration is not None))
|
||||
except AssertionError:
|
||||
raise ValueError(
|
||||
'Exactly 2 of start, end, and duration must be specified')
|
||||
|
||||
# Convert duration to timedelta if seconds were provided.
|
||||
duration = seconds_or_timedelta(duration)
|
||||
|
||||
if not start:
|
||||
start = end - duration
|
||||
|
||||
if not end:
|
||||
end = start + duration
|
||||
|
||||
if start > end:
|
||||
raise ValueError('MayaInterval cannot end before it starts')
|
||||
|
||||
self.start = start
|
||||
self.end = end
|
||||
|
||||
def __repr__(self):
|
||||
return '<MayaInterval start={0!r} end={1!r}>'.format(self.start, self.end)
|
||||
|
||||
def iso8601(self):
|
||||
"""Returns an ISO 8601 representation of the MayaInterval."""
|
||||
return '{0}/{1}'.format(self.start.iso6801, self.end.iso8601)
|
||||
|
||||
@classmethod
|
||||
def from_iso8601(cls, s):
|
||||
# # Start and end, such as "2007-03-01T13:00:00Z/2008-05-11T15:30:00Z"
|
||||
# start, end = s.split('/')
|
||||
# try:
|
||||
# start = parse(start)
|
||||
# except pendulum.parsing.exceptions.ParserError:
|
||||
# start = self._parse_iso8601_duration(start)
|
||||
# try:
|
||||
# end = parse(start)
|
||||
# except pendulum.parsing.exceptions.ParserError as e:
|
||||
# end = self._parse_iso8601_duration(start)
|
||||
|
||||
# # Start and duration, such as "2007-03-01T13:00:00Z/P1Y2M10DT2H30M"
|
||||
# # Duration and end, such as "P1Y2M10DT2H30M/2008-05-11T15:30:00Z"
|
||||
raise NotImplementedError()
|
||||
|
||||
def __and__(self, i):
|
||||
return self.intersection(i)
|
||||
|
||||
def __or__(self, i):
|
||||
return self.combine(i)
|
||||
|
||||
def __eq__(self, i):
|
||||
return (
|
||||
self.start == i.start and
|
||||
self.end == i.end
|
||||
)
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.start, self.end))
|
||||
|
||||
def __iter__(self):
|
||||
yield self.start
|
||||
yield self.end
|
||||
|
||||
def __cmp__(self, i):
|
||||
return (
|
||||
cmp(self.start, i.start) or
|
||||
cmp(self.end, i.end)
|
||||
)
|
||||
|
||||
@property
|
||||
def duration(self):
|
||||
return self.timedelta.total_seconds()
|
||||
|
||||
@property
|
||||
def timedelta(self):
|
||||
return timedelta(seconds=(self.end.epoch - self.start.epoch))
|
||||
|
||||
@property
|
||||
def is_instant(self):
|
||||
return self.timedelta == timedelta(seconds=0)
|
||||
|
||||
def intersects(self, i):
|
||||
return self & i is not None
|
||||
|
||||
@property
|
||||
def midpoint(self):
|
||||
return self.start.add(seconds=(self.duration / 2))
|
||||
|
||||
def combine(self, i):
|
||||
"""Returns a combined list of timespans, merged together."""
|
||||
ii = sorted([self, i])
|
||||
if self & i or self.is_adjacent(i):
|
||||
return [
|
||||
MayaInterval(
|
||||
ii[0].start,
|
||||
max(ii[0].end, ii[1].end),
|
||||
),
|
||||
]
|
||||
return ii
|
||||
|
||||
def subtract(self, i):
|
||||
""""Removes the given inerval."""
|
||||
if not self & i:
|
||||
return [self]
|
||||
elif i.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
|
||||
|
||||
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'
|
||||
start = self.start
|
||||
while start < self.end:
|
||||
if start + duration <= self.end:
|
||||
yield MayaInterval(start, start + duration)
|
||||
elif include_remainder:
|
||||
yield MayaInterval(start, self.end)
|
||||
start += duration
|
||||
|
||||
def quantize(self, duration, snap_out=False, timezone='UTC'):
|
||||
"""Returns a quanitzed interval."""
|
||||
|
||||
# Convert seconds to timedelta, if appropriate.
|
||||
duration = seconds_or_timedelta(duration)
|
||||
timezone = pytz.timezone(timezone)
|
||||
|
||||
assert duration > timedelta(seconds=0), 'cannot quantize by non-positive timedelta'
|
||||
epoch = timezone.localize(Datetime(1970, 1, 1))
|
||||
seconds = int(duration.total_seconds())
|
||||
|
||||
start_seconds = int((self.start.datetime(naive=False) - epoch).total_seconds())
|
||||
end_seconds = int((self.end.datetime(naive=False) - epoch).total_seconds())
|
||||
|
||||
if start_seconds % seconds and not snap_out:
|
||||
start_seconds += seconds
|
||||
if end_seconds % seconds and snap_out:
|
||||
end_seconds += seconds
|
||||
|
||||
start_seconds -= start_seconds % seconds
|
||||
end_seconds -= end_seconds % seconds
|
||||
|
||||
if start_seconds > end_seconds:
|
||||
start_seconds = end_seconds
|
||||
|
||||
return MayaInterval(
|
||||
start=MayaDT.from_datetime(epoch).add(seconds=start_seconds),
|
||||
end=MayaDT.from_datetime(epoch).add(seconds=end_seconds),
|
||||
)
|
||||
|
||||
def intersection(self, i):
|
||||
"""Returns the intersection between two intervals."""
|
||||
|
||||
start = max(self.start, i.start)
|
||||
end = min(self.end, i.end)
|
||||
|
||||
either_instant = self.is_instant or i.is_instant
|
||||
instant_overlap = (
|
||||
self.start == i.start or
|
||||
start <= end
|
||||
)
|
||||
if (either_instant and instant_overlap) or (start < end):
|
||||
return MayaInterval(start, end)
|
||||
|
||||
def contains(self, i):
|
||||
return (
|
||||
self.start <= i.start and
|
||||
self.end >= i.end
|
||||
)
|
||||
|
||||
def __contains__(self, item):
|
||||
if isinstance(item, MayaDT):
|
||||
return self.contains_dt(item)
|
||||
|
||||
return self.contains(item)
|
||||
|
||||
def contains_dt(self, dt):
|
||||
return self.start <= dt < self.end
|
||||
|
||||
def is_adjacent(self, i):
|
||||
return (
|
||||
self.start == i.end or
|
||||
self.end == i.start
|
||||
)
|
||||
|
||||
@property
|
||||
def icalendar(self):
|
||||
ical_dt_format = '%Y%m%dT%H%M%SZ'
|
||||
return """
|
||||
BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
DTSTART:{0}
|
||||
DTEND:{1}
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
""".format(
|
||||
self.start.datetime().strftime(ical_dt_format),
|
||||
self.end.datetime().strftime(ical_dt_format),
|
||||
).replace(' ', '').strip('\r\n').replace('\n', '\r\n')
|
||||
|
||||
@staticmethod
|
||||
def flatten(ii):
|
||||
return functools.reduce(lambda reduced, i: (
|
||||
(reduced[:-1] + i.combine(reduced[-1]))
|
||||
if reduced else [i]
|
||||
), sorted(ii), [])
|
||||
|
||||
@classmethod
|
||||
def from_datetime(cls, start_dt=None, end_dt=None, duration=None):
|
||||
start = MayaDT.from_datetime(start_dt) if start_dt else None
|
||||
end = MayaDT.from_datetime(end_dt) if end_dt else None
|
||||
return cls(start=start, end=end, duration=duration)
|
||||
|
||||
|
||||
def now():
|
||||
@@ -176,6 +524,7 @@ def now():
|
||||
epoch = time.time()
|
||||
return MayaDT(epoch=epoch)
|
||||
|
||||
|
||||
def when(string, timezone='UTC'):
|
||||
""""Returns a MayaDT instance for the human moment specified.
|
||||
|
||||
@@ -189,20 +538,42 @@ 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.')
|
||||
|
||||
return MayaDT.from_datetime(dt)
|
||||
|
||||
def parse(string):
|
||||
|
||||
def parse(string, day_first=False):
|
||||
""""Returns a MayaDT instance for the machine-produced moment specified.
|
||||
|
||||
Powered by dateutil. Accepts most known formats. Useful for working with data.
|
||||
Powered by pendulum. Accepts most known formats. Useful for working with data.
|
||||
|
||||
Keyword Arguments:
|
||||
string -- string to be parsed
|
||||
day_first -- if true, the first value (e.g. 01/05/2016) is parsed as day (default: False)
|
||||
"""
|
||||
dt = dateutil.parser.parse(string)
|
||||
return MayaDT.from_datetime(dt)
|
||||
dt = pendulum.parse(string, day_first=day_first)
|
||||
return MayaDT.from_datetime(dt)
|
||||
|
||||
|
||||
def seconds_or_timedelta(s):
|
||||
# Convert seconds into timedelta.
|
||||
if isinstance(s, int):
|
||||
s = timedelta(seconds=s)
|
||||
|
||||
return s
|
||||
|
||||
|
||||
def intervals(start, end, interval):
|
||||
"""Yields MayaDT objects between the start and end MayaDTs given, at a given interval (seconds or timedelta)."""
|
||||
|
||||
interval = seconds_or_timedelta(interval)
|
||||
|
||||
current_timestamp = start
|
||||
while current_timestamp.epoch < end.epoch:
|
||||
yield current_timestamp
|
||||
current_timestamp = current_timestamp.add(seconds=interval.seconds)
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
dateparser==0.5.0
|
||||
humanize==0.5.1
|
||||
iso8601==0.1.11
|
||||
jdatetime==1.8.1
|
||||
py==1.4.32
|
||||
pytest==3.0.5
|
||||
python-dateutil==2.6.0
|
||||
pytz==2016.10
|
||||
regex==2016.11.21
|
||||
ruamel.ordereddict==0.4.9
|
||||
ruamel.yaml==0.13.4
|
||||
six==1.10.0
|
||||
typing==3.5.2.2
|
||||
tzlocal==1.3
|
||||
umalqurra==0.2
|
||||
@@ -3,9 +3,22 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
import codecs
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
try:
|
||||
# Python 3
|
||||
from os import dirname
|
||||
except ImportError:
|
||||
# Python 2
|
||||
from os.path import dirname
|
||||
|
||||
here = os.path.abspath(dirname(__file__))
|
||||
|
||||
with codecs.open(os.path.join(here, 'README.rst'), encoding='utf-8') as f:
|
||||
long_description = '\n' + f.read()
|
||||
|
||||
|
||||
if sys.argv[-1] == "publish":
|
||||
os.system("python setup.py sdist bdist_wheel upload")
|
||||
@@ -15,19 +28,20 @@ required = [
|
||||
'humanize',
|
||||
'pytz',
|
||||
'dateparser',
|
||||
'iso8601',
|
||||
'python-dateutil'
|
||||
'ruamel.yaml',
|
||||
'tzlocal',
|
||||
'pendulum'
|
||||
]
|
||||
|
||||
setup(
|
||||
name='maya',
|
||||
version='0.1.0',
|
||||
version='0.3.1',
|
||||
description='Datetimes for Humans.',
|
||||
long_description=open('README.rst').read(),
|
||||
long_description=long_description,
|
||||
author='Kenneth Reitz',
|
||||
author_email='me@kennethreitz.com',
|
||||
author_email='me@kennethreitz.org',
|
||||
url='https://github.com/kennethreitz/maya',
|
||||
my_modules=['maya'],
|
||||
py_modules=['maya'],
|
||||
install_requires=required,
|
||||
license='MIT',
|
||||
classifiers=(
|
||||
|
||||
+179
-11
@@ -1,23 +1,74 @@
|
||||
import pytest
|
||||
import copy
|
||||
from datetime import timedelta
|
||||
|
||||
import maya
|
||||
|
||||
|
||||
def test_rfc2822():
|
||||
r = maya.now().rfc2822()
|
||||
r = maya.parse('February 21, 1994').rfc2822()
|
||||
d = maya.MayaDT.from_rfc2822(r)
|
||||
assert r == 'Mon, 21 Feb 1994 00:00:00 GMT'
|
||||
assert r == d.rfc2822()
|
||||
|
||||
|
||||
def test_iso8601():
|
||||
r = maya.now().iso8601()
|
||||
r = maya.parse('February 21, 1994').iso8601()
|
||||
d = maya.MayaDT.from_iso8601(r)
|
||||
assert r == '1994-02-21T00:00:00Z'
|
||||
assert r == d.iso8601()
|
||||
|
||||
|
||||
def test_parse_iso8601():
|
||||
string = '20161001T1430.4+05:30'
|
||||
expected = '2016-10-01T09:00:00.400000Z'
|
||||
d = maya.MayaDT.from_iso8601(string)
|
||||
|
||||
assert expected == d.iso8601()
|
||||
|
||||
string = '2016T14'
|
||||
expected = '2016-01-01T14:00:00Z'
|
||||
d = maya.MayaDT.from_iso8601(string)
|
||||
|
||||
assert expected == d.iso8601()
|
||||
|
||||
string = '2016-10T14'
|
||||
expected = '2016-10-01T14:00:00Z'
|
||||
d = maya.MayaDT.from_iso8601(string)
|
||||
|
||||
assert expected == d.iso8601()
|
||||
|
||||
string = '2012W05'
|
||||
expected = '2012-01-30T00:00:00Z'
|
||||
d = maya.MayaDT.from_iso8601(string)
|
||||
|
||||
assert expected == d.iso8601()
|
||||
|
||||
string = '2012W055'
|
||||
expected = '2012-02-03T00:00:00Z'
|
||||
d = maya.MayaDT.from_iso8601(string)
|
||||
|
||||
assert expected == d.iso8601()
|
||||
|
||||
string = '2012007'
|
||||
expected = '2012-01-07T00:00:00Z'
|
||||
d = maya.MayaDT.from_iso8601(string)
|
||||
|
||||
assert expected == d.iso8601()
|
||||
|
||||
string = '2016-W07T09'
|
||||
expected = '2016-02-15T09:00:00Z'
|
||||
d = maya.MayaDT.from_iso8601(string)
|
||||
|
||||
assert expected == d.iso8601()
|
||||
|
||||
|
||||
def test_human_when():
|
||||
r1 = maya.when('yesterday')
|
||||
r2 = maya.when('today')
|
||||
|
||||
assert r2.day - r1.day == 1
|
||||
assert (r2.day - r1.day) in (1, -30, -29, -28, -27)
|
||||
|
||||
|
||||
def test_machine_parse():
|
||||
r1 = maya.parse('August 14, 2015')
|
||||
@@ -29,24 +80,59 @@ def test_machine_parse():
|
||||
|
||||
def test_dt_tz_translation():
|
||||
d1 = maya.now().datetime()
|
||||
d2 = maya.now().datetime(to_timezone='US/Eastern')
|
||||
assert d1.hour - d2.hour == 5
|
||||
d2 = maya.now().datetime(to_timezone='EST')
|
||||
assert (d1.hour - d2.hour) % 24 == 5
|
||||
|
||||
|
||||
def test_dt_tz_naive():
|
||||
d1 = maya.now().datetime(naive=True)
|
||||
assert d1.tzinfo is None
|
||||
|
||||
d2 = maya.now().datetime(to_timezone='US/Eastern', naive=True)
|
||||
d2 = maya.now().datetime(to_timezone='EST', naive=True)
|
||||
assert d2.tzinfo is None
|
||||
assert d1.hour - d2.hour == 5
|
||||
assert (d1.hour - d2.hour) % 24 == 5
|
||||
|
||||
|
||||
def test_random_date():
|
||||
|
||||
# Test properties for maya.when()
|
||||
d1 = maya.when('11-17-11 08:09:10')
|
||||
assert d1.year == 2011
|
||||
assert d1.month == 11
|
||||
assert d1.day == 17
|
||||
assert d1.week == 46
|
||||
assert d1.weekday == 4
|
||||
assert d1.hour == 8
|
||||
assert d1.minute == 9
|
||||
assert d1.second == 10
|
||||
assert d1.microsecond == 0
|
||||
|
||||
# Test properties for maya.parse()
|
||||
d2 = maya.parse('February 29, 1992 13:12:34')
|
||||
assert d2.year == 1992
|
||||
assert d2.month == 2
|
||||
assert d2.day == 29
|
||||
assert d2.week == 9
|
||||
assert d2.weekday == 6
|
||||
assert d2.hour == 13
|
||||
assert d2.minute == 12
|
||||
assert d2.second == 34
|
||||
assert d2.microsecond == 0
|
||||
|
||||
|
||||
def test_print_date(capsys):
|
||||
d = maya.when('11-17-11')
|
||||
assert d.year == 2011
|
||||
assert d.month == 11
|
||||
assert d.day == 17
|
||||
|
||||
print(d)
|
||||
out, err = capsys.readouterr()
|
||||
|
||||
assert out == 'Thu, 17 Nov 2011 00:00:00 GMT\n'
|
||||
assert repr(d) == '<MayaDT epoch=1321488000.0>'
|
||||
|
||||
|
||||
def test_invalid_date():
|
||||
with pytest.raises(ValueError):
|
||||
maya.when('another day')
|
||||
|
||||
|
||||
def test_slang_date():
|
||||
@@ -58,4 +144,86 @@ def test_slang_time():
|
||||
d = maya.when('one hour ago')
|
||||
assert d.slang_time() == 'an hour ago'
|
||||
|
||||
# rand_day = maya.when('2011-02-07', timezone='US/Eastern')
|
||||
|
||||
def test_parse():
|
||||
d = maya.parse('February 21, 1994')
|
||||
assert format(d) == '1994-02-21 00:00:00+00:00'
|
||||
|
||||
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'
|
||||
|
||||
|
||||
def test_datetime_to_timezone():
|
||||
dt = maya.when('2016-01-01').datetime(to_timezone='US/Eastern')
|
||||
assert dt.tzinfo.zone == 'US/Eastern'
|
||||
|
||||
def test_rfc3339():
|
||||
mdt = maya.when('2016-01-01')
|
||||
out = mdt.rfc3339()
|
||||
mdt2 = maya.MayaDT.from_rfc3339(out)
|
||||
assert mdt.epoch == mdt2.epoch
|
||||
|
||||
|
||||
def test_comparison_operations():
|
||||
now = maya.now()
|
||||
now_copy = copy.deepcopy(now)
|
||||
tomorrow = maya.when('tomorrow')
|
||||
|
||||
assert (now == now_copy) is True
|
||||
assert (now == tomorrow) is False
|
||||
|
||||
assert (now != now_copy) is False
|
||||
assert (now != tomorrow) is True
|
||||
|
||||
assert (now < now_copy) is False
|
||||
assert (now < tomorrow) is True
|
||||
|
||||
assert (now <= now_copy) is True
|
||||
assert (now <= tomorrow) is True
|
||||
|
||||
assert (now > now_copy) is False
|
||||
assert (now > tomorrow) is False
|
||||
|
||||
assert (now >= now_copy) is True
|
||||
assert (now >= tomorrow) is False
|
||||
|
||||
# Check Exceptions
|
||||
with pytest.raises(TypeError):
|
||||
now == 1
|
||||
with pytest.raises(TypeError):
|
||||
now != 1
|
||||
with pytest.raises(TypeError):
|
||||
now < 1
|
||||
with pytest.raises(TypeError):
|
||||
now <= 1
|
||||
with pytest.raises(TypeError):
|
||||
now > 1
|
||||
with pytest.raises(TypeError):
|
||||
now >= 1
|
||||
|
||||
def test_intervals():
|
||||
now = maya.now()
|
||||
tomorrow = now.add(days=1)
|
||||
|
||||
assert len(list(maya.intervals(now, tomorrow, 60*60))) == 24
|
||||
|
||||
|
||||
def test_dunder_add():
|
||||
now = maya.now()
|
||||
assert now + 1 == now.add(seconds=1)
|
||||
assert now + timedelta(seconds=1) == now.add(seconds=1)
|
||||
|
||||
|
||||
def test_dunder_radd():
|
||||
now = maya.now()
|
||||
assert now.add(seconds=1) == now + 1
|
||||
assert now.add(seconds=1) == now + timedelta(seconds=1)
|
||||
|
||||
|
||||
def test_dunder_sub():
|
||||
now = maya.now()
|
||||
assert now - 1 == now.subtract(seconds=1)
|
||||
assert now - timedelta(seconds=1) == now.subtract(seconds=1)
|
||||
|
||||
Executable
+541
@@ -0,0 +1,541 @@
|
||||
import random
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
|
||||
import maya
|
||||
from compat import cmp
|
||||
|
||||
Los_Angeles = pytz.timezone('America/Los_Angeles')
|
||||
New_York = pytz.timezone('America/New_York')
|
||||
Melbourne = pytz.timezone('Australia/Melbourne')
|
||||
|
||||
|
||||
def test_interval_requires_2_of_start_end_duration():
|
||||
start = maya.now()
|
||||
end = start.add(hours=1)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
maya.MayaInterval(start=start)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
maya.MayaInterval(end=end)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
maya.MayaInterval(duration=60)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
maya.MayaInterval(start=start, end=end, duration=60)
|
||||
|
||||
maya.MayaInterval(start=start, end=end)
|
||||
maya.MayaInterval(start=start, duration=60)
|
||||
maya.MayaInterval(end=end, duration=60)
|
||||
|
||||
|
||||
def test_interval_requires_end_time_after_or_on_start_time():
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
maya.MayaInterval(start=maya.now(), duration=0)
|
||||
maya.MayaInterval(start=maya.now(), duration=-1)
|
||||
|
||||
|
||||
def test_interval_init_start_end():
|
||||
start = maya.now()
|
||||
end = start.add(hours=1)
|
||||
interval = maya.MayaInterval(start=start, end=end)
|
||||
assert interval.start == start
|
||||
assert interval.end == end
|
||||
|
||||
|
||||
def test_interval_init_start_duration():
|
||||
start = maya.now()
|
||||
duration = 1
|
||||
interval = maya.MayaInterval(start=start, duration=duration)
|
||||
assert interval.start == start
|
||||
assert interval.end == start.add(seconds=duration)
|
||||
|
||||
|
||||
def test_interval_init_end_duration():
|
||||
end = maya.now()
|
||||
duration = 1
|
||||
interval = maya.MayaInterval(end=end, duration=duration)
|
||||
assert interval.end == end
|
||||
assert interval.start == end.subtract(seconds=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)),
|
||||
), ids=(
|
||||
'overlapping',
|
||||
'non-overlapping',
|
||||
'adjacent',
|
||||
'equal',
|
||||
'instant overlapping start only',
|
||||
'instant equal',
|
||||
'instant disjoint',
|
||||
'instant overlapping',
|
||||
'instant overlapping start only (left)',
|
||||
'instant disjoint (left)',
|
||||
'instant overlapping (left)'
|
||||
))
|
||||
def test_interval_intersection(
|
||||
start_doy1, end_doy1, start_doy2, end_doy2, intersection_doys
|
||||
):
|
||||
base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
|
||||
interval1 = maya.MayaInterval(
|
||||
base.add(days=start_doy1),
|
||||
base.add(days=end_doy1),
|
||||
)
|
||||
interval2 = maya.MayaInterval(
|
||||
base.add(days=start_doy2),
|
||||
base.add(days=end_doy2),
|
||||
)
|
||||
|
||||
if intersection_doys:
|
||||
start_doy_intersection, end_doy_intersection = intersection_doys
|
||||
assert interval1 & interval2 == maya.MayaInterval(
|
||||
base.add(days=start_doy_intersection),
|
||||
base.add(days=end_doy_intersection),
|
||||
)
|
||||
else:
|
||||
assert (interval1 & interval2) is None
|
||||
|
||||
|
||||
def test_interval_intersects():
|
||||
base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
|
||||
interval = maya.MayaInterval(base, base.add(days=1))
|
||||
|
||||
assert interval.intersects(interval)
|
||||
assert not interval.intersects(maya.MayaInterval(
|
||||
base.add(days=2),
|
||||
base.add(days=3),
|
||||
))
|
||||
|
||||
|
||||
def test_and_operator():
|
||||
base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
|
||||
interval1 = maya.MayaInterval(base, base.add(days=2))
|
||||
interval2 = maya.MayaInterval(base.add(days=1), base.add(days=3))
|
||||
|
||||
assert (
|
||||
interval1 & interval2 ==
|
||||
interval2 & interval1 ==
|
||||
interval1.intersection(interval2)
|
||||
)
|
||||
|
||||
|
||||
def test_interval_eq_operator():
|
||||
start = maya.now()
|
||||
end = start.add(hours=1)
|
||||
interval = maya.MayaInterval(start=start, end=end)
|
||||
|
||||
assert interval == maya.MayaInterval(start=start, end=end)
|
||||
assert interval != maya.MayaInterval(start=start, end=end.add(days=1))
|
||||
|
||||
|
||||
def test_interval_timedelta():
|
||||
start = maya.now()
|
||||
delta = timedelta(hours=1)
|
||||
interval = maya.MayaInterval(start=start, duration=delta)
|
||||
|
||||
assert interval.timedelta == delta
|
||||
|
||||
|
||||
def test_interval_duration():
|
||||
start = maya.now()
|
||||
delta = timedelta(hours=1)
|
||||
interval = maya.MayaInterval(start=start, duration=delta)
|
||||
|
||||
assert interval.duration == delta.total_seconds()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start_doy1,end_doy1,start_doy2,end_doy2,expected', (
|
||||
(0, 2, 1, 3, False),
|
||||
(0, 2, 3, 4, False),
|
||||
(0, 2, 2, 3, False),
|
||||
(0, 1, 0, 1, True),
|
||||
(0, 3, 1, 2, True),
|
||||
), ids=(
|
||||
'overlapping',
|
||||
'non-overlapping',
|
||||
'adjacent',
|
||||
'equal',
|
||||
'subset',
|
||||
))
|
||||
def test_interval_contains(
|
||||
start_doy1, end_doy1, start_doy2, end_doy2, expected
|
||||
):
|
||||
base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
|
||||
interval1 = maya.MayaInterval(
|
||||
base.add(days=start_doy1),
|
||||
base.add(days=end_doy1),
|
||||
)
|
||||
interval2 = maya.MayaInterval(
|
||||
base.add(days=start_doy2),
|
||||
base.add(days=end_doy2),
|
||||
)
|
||||
|
||||
assert interval1.contains(interval2) is expected
|
||||
assert (interval2 in interval1) is expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start_doy,end_doy,dt_doy,expected', (
|
||||
(2, 4, 1, False),
|
||||
(2, 4, 2, True),
|
||||
(2, 4, 3, True),
|
||||
(2, 4, 4, False),
|
||||
(2, 4, 5, False),
|
||||
), ids=(
|
||||
'before-start',
|
||||
'on-start',
|
||||
'during',
|
||||
'on-end',
|
||||
'after-end',
|
||||
))
|
||||
def test_interval_in_operator_maya_dt(
|
||||
start_doy, end_doy, dt_doy, expected
|
||||
):
|
||||
base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
|
||||
interval = maya.MayaInterval(
|
||||
start=base.add(days=start_doy),
|
||||
end=base.add(days=end_doy),
|
||||
)
|
||||
dt = base.add(days=dt_doy)
|
||||
|
||||
assert (dt in interval) is expected
|
||||
|
||||
|
||||
def test_interval_hash():
|
||||
start = maya.now()
|
||||
end = start.add(hours=1)
|
||||
interval = maya.MayaInterval(start=start, end=end)
|
||||
|
||||
assert hash(interval) == hash(maya.MayaInterval(start=start, end=end))
|
||||
assert hash(interval) != hash(maya.MayaInterval(
|
||||
start=start, end=end.add(days=1)))
|
||||
|
||||
|
||||
def test_interval_iter():
|
||||
start = maya.now()
|
||||
end = start.add(days=1)
|
||||
|
||||
assert tuple(maya.MayaInterval(start=start, end=end)) == (start, end)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start1,end1,start2,end2,expected', [
|
||||
(1, 2, 1, 2, 0),
|
||||
(1, 3, 2, 4, -1),
|
||||
(2, 4, 1, 3, 1),
|
||||
(1, 2, 1, 3, -1),
|
||||
], ids=(
|
||||
'equal',
|
||||
'less-than',
|
||||
'greater-than',
|
||||
'use-end-time-if-start-time-identical',
|
||||
))
|
||||
def test_interval_cmp(start1, end1, start2, end2, expected):
|
||||
base = maya.now()
|
||||
interval1 = maya.MayaInterval(
|
||||
start=base.add(days=start1),
|
||||
end=base.add(days=end1),
|
||||
)
|
||||
interval2 = maya.MayaInterval(
|
||||
start=base.add(days=start2),
|
||||
end=base.add(days=end2),
|
||||
)
|
||||
assert cmp(interval1, interval2) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start1,end1,start2,end2,expected', [
|
||||
(1, 2, 2, 3, [(1, 3)]),
|
||||
(1, 3, 2, 4, [(1, 4)]),
|
||||
(1, 2, 3, 4, [(1, 2), (3, 4)]),
|
||||
(1, 5, 2, 3, [(1, 5)]),
|
||||
], ids=(
|
||||
'adjacent',
|
||||
'overlapping',
|
||||
'non-overlapping',
|
||||
'contains',
|
||||
))
|
||||
def test_interval_combine(start1, end1, start2, end2, expected):
|
||||
base = maya.now()
|
||||
interval1 = maya.MayaInterval(
|
||||
start=base.add(days=start1),
|
||||
end=base.add(days=end1),
|
||||
)
|
||||
interval2 = maya.MayaInterval(
|
||||
start=base.add(days=start2),
|
||||
end=base.add(days=end2),
|
||||
)
|
||||
expected_intervals = [maya.MayaInterval(
|
||||
start=base.add(days=start),
|
||||
end=base.add(days=end),
|
||||
) for start, end in expected]
|
||||
|
||||
assert interval1.combine(interval2) == expected_intervals
|
||||
assert interval2.combine(interval1) == expected_intervals
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start1,end1,start2,end2,expected', [
|
||||
(1, 2, 3, 4, [(1, 2)]),
|
||||
(1, 2, 2, 4, [(1, 2)]),
|
||||
(2, 3, 1, 4, []),
|
||||
(1, 4, 2, 3, [(1, 2), (3, 4)]),
|
||||
(1, 4, 0, 2, [(2, 4)]),
|
||||
(1, 4, 3, 5, [(1, 3)]),
|
||||
(1, 4, 1, 2, [(2, 4)]),
|
||||
(1, 4, 3, 4, [(1, 3)]),
|
||||
], ids=(
|
||||
'non-overlapping',
|
||||
'adjacent',
|
||||
'contains',
|
||||
'splits',
|
||||
'overlaps-left',
|
||||
'overlaps-right',
|
||||
'overlaps-left-identical-start',
|
||||
'overlaps-right-identical-end',
|
||||
))
|
||||
def test_interval_subtract(start1, end1, start2, end2, expected):
|
||||
base = maya.now()
|
||||
interval1 = maya.MayaInterval(
|
||||
start=base.add(days=start1),
|
||||
end=base.add(days=end1),
|
||||
)
|
||||
interval2 = maya.MayaInterval(
|
||||
start=base.add(days=start2),
|
||||
end=base.add(days=end2),
|
||||
)
|
||||
expected_intervals = [maya.MayaInterval(
|
||||
start=base.add(days=start),
|
||||
end=base.add(days=end),
|
||||
) for start, end in expected]
|
||||
|
||||
assert interval1.subtract(interval2) == expected_intervals
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start1,end1,start2,end2,expected', [
|
||||
(1, 2, 2, 3, True),
|
||||
(2, 3, 1, 2, True),
|
||||
(1, 3, 2, 3, False),
|
||||
(2, 3, 4, 5, False),
|
||||
], ids=(
|
||||
'adjacent-right',
|
||||
'adjacent-left',
|
||||
'overlapping',
|
||||
'non-overlapping',
|
||||
))
|
||||
def test_interval_is_adjacent(start1, end1, start2, end2, expected):
|
||||
base = maya.now()
|
||||
interval1 = maya.MayaInterval(
|
||||
start=base.add(days=start1),
|
||||
end=base.add(days=end1),
|
||||
)
|
||||
interval2 = maya.MayaInterval(
|
||||
start=base.add(days=start2),
|
||||
end=base.add(days=end2),
|
||||
)
|
||||
assert interval1.is_adjacent(interval2) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start,end,delta,include_remainder,expected', [
|
||||
(0, 10, 5, False, [(0, 5), (5, 10)]),
|
||||
(0, 10, 5, True, [(0, 5), (5, 10)]),
|
||||
(0, 10, 3, False, [(0, 3), (3, 6), (6, 9)]),
|
||||
(0, 10, 3, True, [(0, 3), (3, 6), (6, 9), (9, 10)]),
|
||||
(0, 2, 5, False, []),
|
||||
(0, 2, 5, True, [(0, 2)]),
|
||||
], ids=(
|
||||
'even-split',
|
||||
'even-split-include-partial',
|
||||
'uneven-split-do-not-include-partial',
|
||||
'uneven-split-include-partial',
|
||||
'delta-larger-than-timepsan-do-not-include-partial',
|
||||
'delta-larger-than-timepsan-include-partial',
|
||||
))
|
||||
def test_interval_split(start, end, delta, include_remainder, expected):
|
||||
base = maya.now()
|
||||
interval = maya.MayaInterval(
|
||||
start=base.add(days=start),
|
||||
end=base.add(days=end),
|
||||
)
|
||||
delta = timedelta(days=delta)
|
||||
|
||||
expected_intervals = [
|
||||
maya.MayaInterval(
|
||||
start=base.add(days=s),
|
||||
end=base.add(days=e),
|
||||
) for s, e in expected
|
||||
]
|
||||
|
||||
assert expected_intervals == list(interval.split(
|
||||
delta, include_remainder=include_remainder))
|
||||
|
||||
|
||||
def test_interval_split_non_positive_delta():
|
||||
start = maya.now()
|
||||
end = start.add(days=1)
|
||||
interval = maya.MayaInterval(start=start, end=end)
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
list(interval.split(timedelta(seconds=0)))
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
list(interval.split(timedelta(seconds=-10)))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start,end,minutes,timezone,snap_out,expected_start,expected_end', [
|
||||
((5, 12), (8, 48), 30, None, False, (5, 30), (8, 30)),
|
||||
((5, 12), (8, 48), 30, None, True, (5, 0), (9, 0)),
|
||||
((5, 15), (9, 0), 15, None, False, (5, 15), (9, 0)),
|
||||
((5, 15), (9, 0), 15, None, True, (5, 15), (9, 0)),
|
||||
((6, 50), (9, 15), 60, 'America/New_York', False, (7, 0), (9, 0)),
|
||||
((6, 50), (9, 15), 60, 'America/New_York', True, (6, 0), (10, 0)),
|
||||
((6, 20), (6, 50), 60, None, False, (6, 0), (6, 0)),
|
||||
((6, 20), (6, 50), 60, None, True, (6, 0), (7, 0)),
|
||||
((6, 20), (6, 50), 60, 'America/Chicago', False, (6, 0), (6, 0)),
|
||||
((6, 20), (6, 50), 60, 'America/Chicago', True, (6, 0), (7, 0)),
|
||||
], ids=(
|
||||
'normal',
|
||||
'normal-snap_out',
|
||||
'already-quantized',
|
||||
'already-quantized-snap_out',
|
||||
'with-timezone',
|
||||
'with-timezone-snap_out',
|
||||
'too-small',
|
||||
'too-small-snap_out',
|
||||
'too-small-with-timezone',
|
||||
'too-small-with-timezone-snap_out',
|
||||
))
|
||||
def test_quantize(start, end, minutes, timezone, snap_out, expected_start, expected_end):
|
||||
base = maya.MayaDT.from_datetime(datetime(2017, 1, 1))
|
||||
interval = maya.MayaInterval(
|
||||
start=base.add(hours=start[0], minutes=start[1]),
|
||||
end=base.add(hours=end[0], minutes=end[1]),
|
||||
)
|
||||
|
||||
kwargs = {'timezone': timezone} if timezone is not None else {}
|
||||
quantized_interval = interval.quantize(
|
||||
timedelta(minutes=minutes),
|
||||
snap_out=snap_out,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
assert quantized_interval == maya.MayaInterval(
|
||||
start=base.add(hours=expected_start[0], minutes=expected_start[1]),
|
||||
end=base.add(hours=expected_end[0], minutes=expected_end[1]),
|
||||
)
|
||||
|
||||
|
||||
def test_quantize_invalid_delta():
|
||||
start = maya.now()
|
||||
end = start.add(days=1)
|
||||
interval = maya.MayaInterval(start=start, end=end)
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
interval.quantize(timedelta(minutes=0))
|
||||
with pytest.raises(AssertionError):
|
||||
interval.quantize(timedelta(minutes=-1))
|
||||
|
||||
|
||||
def test_interval_flatten_non_overlapping():
|
||||
step = 2
|
||||
max_hour = 20
|
||||
base = maya.now()
|
||||
intervals = [maya.MayaInterval(
|
||||
start=base.add(hours=hour),
|
||||
duration=timedelta(hours=step - 1),
|
||||
) for hour in range(0, max_hour, step)]
|
||||
random.shuffle(intervals)
|
||||
|
||||
assert maya.MayaInterval.flatten(intervals) == sorted(intervals)
|
||||
|
||||
|
||||
def test_interval_flatten_adjacent():
|
||||
step = 2
|
||||
max_hour = 20
|
||||
base = maya.when('jan/1/2011')
|
||||
|
||||
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(
|
||||
start=base,
|
||||
duration=timedelta(hours=max_hour),
|
||||
)]
|
||||
|
||||
|
||||
def test_interval_flatten_intersecting():
|
||||
step = 2
|
||||
max_hour = 20
|
||||
base = maya.now()
|
||||
intervals = [maya.MayaInterval(
|
||||
start=base.add(hours=hour),
|
||||
duration=timedelta(hours=step, minutes=30),
|
||||
) for hour in range(0, max_hour, step)]
|
||||
random.shuffle(intervals)
|
||||
|
||||
assert maya.MayaInterval.flatten(intervals) == [maya.MayaInterval(
|
||||
start=base,
|
||||
duration=timedelta(hours=max_hour, minutes=30),
|
||||
)]
|
||||
|
||||
|
||||
def test_interval_flatten_containing():
|
||||
step = 2
|
||||
max_hour = 20
|
||||
base = maya.now()
|
||||
containing_interval = maya.MayaInterval(
|
||||
start=base,
|
||||
end=base.add(hours=max_hour + step),
|
||||
)
|
||||
intervals = [maya.MayaInterval(
|
||||
start=base.add(hours=hour),
|
||||
duration=timedelta(hours=step - 1),
|
||||
) for hour in range(2, max_hour, step)]
|
||||
intervals.append(containing_interval)
|
||||
random.shuffle(intervals)
|
||||
|
||||
assert maya.MayaInterval.flatten(intervals) == [containing_interval]
|
||||
|
||||
|
||||
def test_interval_from_datetime():
|
||||
start = maya.now()
|
||||
duration = timedelta(hours=1)
|
||||
end = start + duration
|
||||
|
||||
interval = maya.MayaInterval.from_datetime(
|
||||
start_dt=start.datetime(naive=False),
|
||||
end_dt=end.datetime(naive=False),
|
||||
)
|
||||
assert interval.start == start
|
||||
assert interval.end == end
|
||||
|
||||
interval2 = maya.MayaInterval.from_datetime(
|
||||
start_dt=start.datetime(naive=False),
|
||||
duration=duration,
|
||||
)
|
||||
assert interval2.start == start
|
||||
assert interval2.end == end
|
||||
|
||||
interval3 = maya.MayaInterval.from_datetime(
|
||||
end_dt=end.datetime(naive=False),
|
||||
duration=duration,
|
||||
)
|
||||
assert interval3.start == start
|
||||
assert interval3.end == end
|
||||
Reference in New Issue
Block a user