mirror of
https://github.com/kennethreitz/maya.git
synced 2026-06-05 14:50:19 +00:00
Compare commits
102 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b896237cca | |||
| 4e58e6455e | |||
| 42cc61e7a8 | |||
| 9abd618083 | |||
| 2c2a8d03c8 | |||
| bdf2bb332b | |||
| 6522525d7c | |||
| 3a83f10988 | |||
| 05eb0d0051 | |||
| 61eabc54e0 | |||
| 6bb380e7c5 | |||
| cc698817b4 | |||
| 9964e9305e | |||
| d87236a30d | |||
| d44a1dd2f6 | |||
| ed5d72b34c | |||
| 9c1e94f956 | |||
| 6755968f69 | |||
| 3760af2c41 | |||
| df09625e74 | |||
| 2d848bd62a | |||
| a99cddf76f | |||
| 1b432d6626 | |||
| f09d6eec63 | |||
| 62a6283200 | |||
| 9ef43a29d9 | |||
| a2bff42439 | |||
| 5525beda31 | |||
| eeb07d46db | |||
| 4056d1a9aa | |||
| 7a750a1cff | |||
| bc06315abd | |||
| 7cc767781a | |||
| 8468dd2ead | |||
| 046f005ca7 | |||
| cd0b2300d7 | |||
| d3ddb39d9d | |||
| 3c8fe4478c | |||
| a0983132bb | |||
| 9612d70707 | |||
| 363fae1aaf | |||
| be2cd96132 | |||
| 771a2b6ce2 | |||
| 8e644f655a | |||
| 6903f6eb63 | |||
| ca076ff625 | |||
| e0e33cc29f | |||
| bd34915f96 | |||
| 16690cfceb | |||
| 97af369f05 | |||
| 367dac4e62 | |||
| 59ec275ef6 | |||
| 76f99bc781 | |||
| bf56321545 | |||
| ee602ea56b | |||
| df3bdf231d | |||
| 1a58d4f710 | |||
| d0d5a8f75d | |||
| 00a0ceb3c8 | |||
| ee0fe3b810 | |||
| 0e0815c45a | |||
| 04a82f3078 | |||
| 5f6b5fc66d | |||
| e9592a3146 | |||
| 15a5c9eedb | |||
| 7c5d5871d3 | |||
| b05ca8707c | |||
| b411d5e6d1 | |||
| 5458b0bf15 | |||
| 02f91de523 | |||
| 00d174c9e2 | |||
| f87f705a53 | |||
| 9a850b4212 | |||
| 84a5f700c3 | |||
| 13e9f2d896 | |||
| 0c206ad0db | |||
| aa87723a0c | |||
| d51a6a57ec | |||
| 3cb65f227c | |||
| 0108ebe07e | |||
| a7d89be3c7 | |||
| 99ffb773bb | |||
| b3293f898e | |||
| 2d8015a13b | |||
| 9066ee862d | |||
| 5f362a8968 | |||
| 565fdd6a9e | |||
| 24063b5e1b | |||
| b57f4a6775 | |||
| 91e7f499e2 | |||
| be3a349b16 | |||
| f69a93b110 | |||
| 86586e733c | |||
| cd7b5b4aae | |||
| b70884a1ec | |||
| 773cdecab5 | |||
| ca865cd840 | |||
| 5cf40b2d2e | |||
| 93152fa7f4 | |||
| baa0660a9b | |||
| fd62815ce5 | |||
| c0092e74ae |
@@ -24,6 +24,7 @@ wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
Pipfile.lock
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
|
||||
+10
-1
@@ -2,8 +2,17 @@ language: python
|
||||
python:
|
||||
- "2.7"
|
||||
- "3.6"
|
||||
- "3.7-dev"
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- python: "3.7-dev"
|
||||
|
||||
# command to install dependencies
|
||||
install: pip install pipenv; pipenv lock; pipenv install --dev
|
||||
install:
|
||||
- pip install pipenv
|
||||
- pipenv install '-e .' --skip-lock --ignore-pipfile
|
||||
- pipenv install --dev --skip-lock
|
||||
|
||||
# command to run tests
|
||||
script: pipenv run pytest tests/
|
||||
|
||||
@@ -23,3 +23,6 @@ In chronological order:
|
||||
- Sébastien Eustace <sebastien@eustace.io> (`@sdispater <https://github.com/sdispater>`_)
|
||||
- Evan Mattiza <emattiza@gmail.com> (`@emattiza <https://github.com/emattiza>`_)
|
||||
- Dima Spivak <dima@spivak.ch> (`@dimaspivak <https://github.com/dimaspivak>`_)
|
||||
- Tom Barron <tusculum@gmail.com> (`@dtbarron <https://github.com/tbarron>`_)
|
||||
- Alex Ward <alxwrd@gmail.com> (`@alxwrd <https://github.com/alxwrd>`_)
|
||||
- Frank Tobia <frank.tobia@gmail.com> (`@ftobia <https://github.com/ftobia>`_)
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
[dev-packages]
|
||||
pytest = "*"
|
||||
Sphinx = "*"
|
||||
[[source]]
|
||||
name = "pypi"
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
|
||||
[packages]
|
||||
humanize = "*"
|
||||
pytz = "*"
|
||||
dateparser = "*"
|
||||
"ruamel.yaml" = "*"
|
||||
tzlocal = "*"
|
||||
pendulum = ">=1.0"
|
||||
[dev-packages]
|
||||
freezegun = "*"
|
||||
pytest = "*"
|
||||
sphinx = "*"
|
||||
|
||||
Generated
-72
@@ -1,72 +0,0 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "5617ff73ba51e60721267b24dc01e83f33d2a3692870b60e394b0f75ed2dc313"
|
||||
},
|
||||
"requires": {},
|
||||
"sources": [
|
||||
{
|
||||
"url": "https://pypi.python.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"dateparser": {
|
||||
"version": "==0.6.0"
|
||||
},
|
||||
"humanize": {
|
||||
"version": "==0.5.1"
|
||||
},
|
||||
"pendulum": {
|
||||
"version": "==1.2.0"
|
||||
},
|
||||
"python-dateutil": {
|
||||
"version": "==2.6.0"
|
||||
},
|
||||
"pytz": {
|
||||
"version": "==2017.2"
|
||||
},
|
||||
"pytzdata": {
|
||||
"version": "==2017.2"
|
||||
},
|
||||
"regex": {
|
||||
"version": "==2017.04.29"
|
||||
},
|
||||
"ruamel.ordereddict": {
|
||||
"version": "==0.4.9"
|
||||
},
|
||||
"ruamel.yaml": {
|
||||
"version": "==0.14.12"
|
||||
},
|
||||
"six": {
|
||||
"version": "==1.10.0"
|
||||
},
|
||||
"tzlocal": {
|
||||
"version": "==1.4"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"appdirs": {
|
||||
"version": "==1.4.3"
|
||||
},
|
||||
"packaging": {
|
||||
"version": "==16.8"
|
||||
},
|
||||
"py": {
|
||||
"version": "==1.4.33"
|
||||
},
|
||||
"pyparsing": {
|
||||
"version": "==2.2.0"
|
||||
},
|
||||
"pytest": {
|
||||
"version": "==3.0.7"
|
||||
},
|
||||
"setuptools": {
|
||||
"version": "==35.0.2"
|
||||
},
|
||||
"six": {
|
||||
"version": "==1.10.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
-8
@@ -22,6 +22,11 @@ Maya is mostly built around the headaches and use-cases around parsing datetime
|
||||
|
||||
.. image:: https://farm4.staticflickr.com/3702/33288285996_5b69d2b8f7_k_d.jpg
|
||||
|
||||
Art by `Sam Flores
|
||||
<https://www.instagram.com/samagram12/>`_ (Photo by `Kenneth Reitz
|
||||
<https://www.instagram.com/kennethreitz/>`_).
|
||||
|
||||
If you're interested in financially supporting Kenneth Reitz open source, consider `visiting this link <https://cash.me/$KennethReitz>`_. Your support helps tremendously with sustainability of motivation, as Open Source is no longer part of my day job.
|
||||
|
||||
☤ Basic Usage of Maya
|
||||
---------------------
|
||||
@@ -69,10 +74,14 @@ Behold, datetimes for humans!
|
||||
>>> m = maya.MayaDT.from_datetime(datetime.utcnow())
|
||||
>>> print(m)
|
||||
Wed, 20 Sep 2017 17:24:32 GMT
|
||||
|
||||
|
||||
>>> m = maya.MayaDT.from_struct(time.gmtime())
|
||||
>>> print(m)
|
||||
Wed, 20 Sep 2017 17:24:32 GMT
|
||||
Wed, 20 Sep 2017 17:24:32 GMT
|
||||
|
||||
>>> m = maya.MayaDT(time.time())
|
||||
>>> print(m)
|
||||
Wed, 20 Sep 2017 17:24:32 GMT
|
||||
|
||||
>>> rand_day.day
|
||||
7
|
||||
@@ -88,10 +97,15 @@ Behold, datetimes for humans!
|
||||
>>> maya.intervals(start=maya.now(), end=maya.now().add(days=1), interval=60*60)
|
||||
<generator object intervals at 0x105ba5820>
|
||||
|
||||
# snap modifiers
|
||||
>>> dt = maya.when('Mon, 21 Feb 1994 21:21:42 GMT')
|
||||
>>> dt.snap('@d+3h').rfc2822()
|
||||
'Mon, 21 Feb 1994 03:00:00 GMT'
|
||||
|
||||
☤ 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 finesse and ease.
|
||||
In addition to timestamps, Maya also includes a wonderfully powerful ``MayaInterval`` class, which represents a range of time (e.g. an event). With this class, you can perform a multitude of advanced calendar calculations with finesse and ease.
|
||||
|
||||
For example:
|
||||
|
||||
@@ -99,15 +113,15 @@ For example:
|
||||
|
||||
>>> from maya import MayaInterval
|
||||
|
||||
# Create an event that is one hour long, starting now.
|
||||
# Create an event that is one hour long, starting now.
|
||||
>>> event_start = maya.now()
|
||||
>>> event_end = event_start.add(hours=1)
|
||||
|
||||
|
||||
>>> event = MayaInterval(start=event_start, end=event_end)
|
||||
|
||||
From here, there are a number of methods available to you, which you can use to compare this event to another event.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
☤ Why is this useful?
|
||||
---------------------
|
||||
@@ -124,7 +138,7 @@ From here, there are a number of methods available to you, which you can use to
|
||||
☤ What about Delorean, Arrow, & Pendulum?
|
||||
-----------------------------------------
|
||||
|
||||
All these project complement each other, and are friends. Pendulum, for example, helps power Maya's parsing.
|
||||
All these projects complement each other, and are friends. Pendulum, for example, helps power Maya's parsing.
|
||||
|
||||
Arrow, for example, is a fantastic library, but isn't what I wanted in a datetime library. In many ways, it's better than Maya for certain things. In some ways, in my opinion, it's not.
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
__version__ = '0.3.2'
|
||||
__version__ = '0.6.1'
|
||||
|
||||
+11
-9
@@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
maya.compat
|
||||
~~~~~~~~~~~~~~~
|
||||
@@ -12,24 +11,19 @@ import sys
|
||||
# -------
|
||||
# Pythons
|
||||
# -------
|
||||
|
||||
# Syntax sugar.
|
||||
_ver = sys.version_info
|
||||
|
||||
#: Python 2.x?
|
||||
# : Python 2.x?
|
||||
is_py2 = (_ver[0] == 2)
|
||||
|
||||
#: Python 3.x?
|
||||
# : Python 3.x?
|
||||
is_py3 = (_ver[0] == 3)
|
||||
|
||||
# ---------
|
||||
# Specifics
|
||||
# ---------
|
||||
|
||||
if is_py2:
|
||||
cmp = cmp
|
||||
|
||||
elif is_py3:
|
||||
|
||||
def cmp(a, b):
|
||||
"""
|
||||
Compare two objects.
|
||||
@@ -38,8 +32,10 @@ elif is_py3:
|
||||
"""
|
||||
if a < b:
|
||||
return -1
|
||||
|
||||
elif a == b:
|
||||
return 0
|
||||
|
||||
else:
|
||||
return 1
|
||||
|
||||
@@ -59,36 +55,42 @@ def comparable(klass):
|
||||
c = self.__cmp__(other)
|
||||
if c is NotImplemented:
|
||||
return c
|
||||
|
||||
return c == 0
|
||||
|
||||
def __ne__(self, other):
|
||||
c = self.__cmp__(other)
|
||||
if c is NotImplemented:
|
||||
return c
|
||||
|
||||
return c != 0
|
||||
|
||||
def __lt__(self, other):
|
||||
c = self.__cmp__(other)
|
||||
if c is NotImplemented:
|
||||
return c
|
||||
|
||||
return c < 0
|
||||
|
||||
def __le__(self, other):
|
||||
c = self.__cmp__(other)
|
||||
if c is NotImplemented:
|
||||
return c
|
||||
|
||||
return c <= 0
|
||||
|
||||
def __gt__(self, other):
|
||||
c = self.__cmp__(other)
|
||||
if c is NotImplemented:
|
||||
return c
|
||||
|
||||
return c > 0
|
||||
|
||||
def __ge__(self, other):
|
||||
c = self.__cmp__(other)
|
||||
if c is NotImplemented:
|
||||
return c
|
||||
|
||||
return c >= 0
|
||||
|
||||
klass.__lt__ = __lt__
|
||||
|
||||
+292
-121
@@ -1,28 +1,23 @@
|
||||
# ___ __ ___ _ _ ___
|
||||
# || \/ | ||=|| \\// ||=||
|
||||
# || | || || // || ||
|
||||
|
||||
# Ignore warnings for yaml usage.
|
||||
import warnings
|
||||
import ruamel.yaml
|
||||
|
||||
warnings.simplefilter('ignore', ruamel.yaml.error.UnsafeLoaderWarning)
|
||||
|
||||
import email.utils
|
||||
import time
|
||||
import functools
|
||||
from datetime import timedelta, datetime as Datetime
|
||||
|
||||
import functools
|
||||
import re
|
||||
import pytz
|
||||
import humanize
|
||||
import dateparser
|
||||
import pendulum
|
||||
import snaptime
|
||||
from tzlocal import get_localzone
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from dateparser.languages.loader import default_loader
|
||||
|
||||
from .compat import cmp, comparable
|
||||
|
||||
_EPOCH_START = (1970, 1, 1)
|
||||
|
||||
|
||||
def validate_class_type_arguments(operator):
|
||||
"""
|
||||
@@ -31,14 +26,20 @@ def validate_class_type_arguments(operator):
|
||||
"""
|
||||
|
||||
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__))
|
||||
raise TypeError(
|
||||
'unorderable types: {}() {} {}()'.format(
|
||||
type(self).__name__, operator, type(arg).__name__
|
||||
)
|
||||
)
|
||||
|
||||
return function(self, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
@@ -51,26 +52,34 @@ def validate_arguments_type_of_function(param_type=None):
|
||||
|
||||
Note: Use this decorator on the functions of the class.
|
||||
"""
|
||||
|
||||
def inner(function):
|
||||
|
||||
def wrapper(self, *args, **kwargs):
|
||||
type_ = param_type or type(self)
|
||||
for arg in args + tuple(kwargs.values()):
|
||||
if not isinstance(arg, type_):
|
||||
raise TypeError(('Invalid Type: {}.{}() accepts only the '
|
||||
'arguments of type "<{}>"').format(
|
||||
type(self).__name__,
|
||||
function.__name__,
|
||||
type_.__name__,
|
||||
)
|
||||
)
|
||||
raise TypeError(
|
||||
(
|
||||
'Invalid Type: {}.{}() accepts only the '
|
||||
'arguments of type "<{}>"'
|
||||
).format(
|
||||
type(self).__name__,
|
||||
function.__name__,
|
||||
type_.__name__,
|
||||
)
|
||||
)
|
||||
|
||||
return function(self, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
class MayaDT(object):
|
||||
"""The Maya Datetime object."""
|
||||
__EPOCH_START = (1970, 1, 1)
|
||||
|
||||
def __init__(self, epoch):
|
||||
super(MayaDT, self).__init__()
|
||||
@@ -114,26 +123,49 @@ class MayaDT(object):
|
||||
return hash(int(self.epoch))
|
||||
|
||||
def __add__(self, duration):
|
||||
return self.add(seconds=_seconds_or_timedelta(duration).total_seconds())
|
||||
return self.add(
|
||||
seconds=_seconds_or_timedelta(duration).total_seconds()
|
||||
)
|
||||
|
||||
def __radd__(self, duration):
|
||||
return self + duration
|
||||
|
||||
def __sub__(self, duration):
|
||||
return self.subtract(
|
||||
seconds=_seconds_or_timedelta(duration).total_seconds())
|
||||
def __sub__(self, duration_or_date):
|
||||
if isinstance(duration_or_date, MayaDT):
|
||||
return self.subtract_date(dt=duration_or_date)
|
||||
|
||||
else:
|
||||
return self.subtract(
|
||||
seconds=_seconds_or_timedelta(duration_or_date).total_seconds()
|
||||
)
|
||||
|
||||
def add(self, **kwargs):
|
||||
""""Returns a new MayaDT object with the given offsets."""
|
||||
return self.from_datetime(pendulum.instance(self.datetime()).add(**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))
|
||||
"""Returns a new MayaDT object with the given offsets."""
|
||||
return self.from_datetime(
|
||||
pendulum.instance(self.datetime()).subtract(**kwargs)
|
||||
)
|
||||
|
||||
def subtract_date(self, **kwargs):
|
||||
"""Returns a timedelta object with the duration between the dates"""
|
||||
return timedelta(seconds=self.epoch - kwargs['dt'].epoch)
|
||||
|
||||
def snap(self, instruction):
|
||||
"""
|
||||
Returns a new MayaDT object modified by the given instruction.
|
||||
|
||||
Powered by snaptime. See https://github.com/zartstrom/snaptime
|
||||
for a complete documentation about the snaptime instructions.
|
||||
"""
|
||||
return self.from_datetime(snaptime.snap(self.datetime(), instruction))
|
||||
|
||||
# Timezone Crap
|
||||
# -------------
|
||||
|
||||
@property
|
||||
def timezone(self):
|
||||
"""Returns the UTC tzinfo name. It's always UTC. Always."""
|
||||
@@ -146,9 +178,10 @@ class MayaDT(object):
|
||||
|
||||
@property
|
||||
def local_timezone(self):
|
||||
"""Returns the name of the local timezone, for informational purposes."""
|
||||
"""Returns the name of the local timezone."""
|
||||
if self._local_tz.zone in pytz.all_timezones:
|
||||
return self._local_tz.zone
|
||||
|
||||
return self.timezone
|
||||
|
||||
@property
|
||||
@@ -160,28 +193,28 @@ class MayaDT(object):
|
||||
@validate_arguments_type_of_function(Datetime)
|
||||
def __dt_to_epoch(dt):
|
||||
"""Converts a datetime into an epoch."""
|
||||
|
||||
# Assume UTC if no datetime is provided.
|
||||
if dt.tzinfo is None:
|
||||
dt = dt.replace(tzinfo=pytz.utc)
|
||||
|
||||
epoch_start = Datetime(*_EPOCH_START, tzinfo=pytz.timezone('UTC'))
|
||||
epoch_start = Datetime(*MayaDT.__EPOCH_START, tzinfo=pytz.timezone('UTC'))
|
||||
return (dt - epoch_start).total_seconds()
|
||||
|
||||
# Importers
|
||||
# ---------
|
||||
|
||||
@classmethod
|
||||
@validate_arguments_type_of_function(Datetime)
|
||||
def from_datetime(klass, dt):
|
||||
"""Returns MayaDT instance from datetime."""
|
||||
return klass(klass.__dt_to_epoch(dt))
|
||||
|
||||
|
||||
@classmethod
|
||||
@validate_arguments_type_of_function(time.struct_time)
|
||||
def from_struct(klass, struct, timezone=pytz.UTC):
|
||||
"""Returns MayaDT instance from a 9-tuple struct"""
|
||||
struct_time = time.mktime(struct)
|
||||
"""Returns MayaDT instance from a 9-tuple struct
|
||||
|
||||
It's assumed to be from gmtime().
|
||||
"""
|
||||
struct_time = time.mktime(struct) - utc_offset(struct)
|
||||
dt = Datetime.fromtimestamp(struct_time, timezone)
|
||||
return klass(klass.__dt_to_epoch(dt))
|
||||
|
||||
@@ -202,30 +235,37 @@ class MayaDT(object):
|
||||
|
||||
# Exporters
|
||||
# ---------
|
||||
|
||||
def datetime(self, to_timezone=None, naive=False):
|
||||
"""Returns a timezone-aware datetime...
|
||||
Defaulting to UTC (as it should).
|
||||
|
||||
Keyword Arguments:
|
||||
to_timezone {string} -- timezone to convert to (default: None/UTC)
|
||||
naive {boolean} -- if True, the tzinfo is simply dropped (default: False)
|
||||
to_timezone {str} -- timezone to convert to (default: None/UTC)
|
||||
naive {bool} -- if True,
|
||||
the tzinfo is simply dropped (default: False)
|
||||
"""
|
||||
if to_timezone:
|
||||
dt = self.datetime().astimezone(pytz.timezone(to_timezone))
|
||||
else:
|
||||
dt = Datetime.utcfromtimestamp(self._epoch)
|
||||
dt.replace(tzinfo=self._tz)
|
||||
|
||||
# Strip the timezone info if requested to do so.
|
||||
if naive:
|
||||
return dt.replace(tzinfo=None)
|
||||
|
||||
else:
|
||||
if dt.tzinfo is None:
|
||||
dt = dt.replace(tzinfo=self._tz)
|
||||
|
||||
return dt
|
||||
|
||||
def local_datetime(self):
|
||||
"""Returns a local timezone-aware datetime object
|
||||
|
||||
It's the same as:
|
||||
mayaDt.datetime(to_timezone=mayaDt.local_timezone)
|
||||
"""
|
||||
return self.datetime(to_timezone=self.local_timezone, naive=False)
|
||||
|
||||
def iso8601(self):
|
||||
"""Returns an ISO 8601 representation of the MayaDT."""
|
||||
# Get a timezone-naive datetime.
|
||||
@@ -238,11 +278,10 @@ class MayaDT(object):
|
||||
|
||||
def rfc3339(self):
|
||||
"""Returns an RFC 3339 representation of the MayaDT."""
|
||||
return self.datetime().strftime("%Y-%m-%dT%H:%M:%S.%f")[:-4] + "Z"
|
||||
return self.datetime().strftime("%Y-%m-%dT%H:%M:%S.%f")[:-5] + "Z"
|
||||
|
||||
# Properties
|
||||
# ----------
|
||||
|
||||
@property
|
||||
def year(self):
|
||||
return self.datetime().year
|
||||
@@ -255,13 +294,20 @@ class MayaDT(object):
|
||||
def day(self):
|
||||
return self.datetime().day
|
||||
|
||||
@property
|
||||
def date(self):
|
||||
return self.datetime().date()
|
||||
|
||||
@property
|
||||
def week(self):
|
||||
return self.datetime().isocalendar()[1]
|
||||
|
||||
@property
|
||||
def weekday(self):
|
||||
"""Return the day of the week as an integer. Monday is 1 and Sunday is 7"""
|
||||
"""Return the day of the week as an integer.
|
||||
|
||||
Monday is 1 and Sunday is 7.
|
||||
"""
|
||||
return self.datetime().isoweekday()
|
||||
|
||||
@property
|
||||
@@ -286,27 +332,72 @@ class MayaDT(object):
|
||||
|
||||
# Human Slang Extras
|
||||
# ------------------
|
||||
def slang_date(self, locale="en"):
|
||||
""""Returns human slang representation of date.
|
||||
|
||||
def slang_date(self):
|
||||
""""Returns human slang representation of date."""
|
||||
dt = self.datetime(naive=True, to_timezone=self.local_timezone)
|
||||
return humanize.naturaldate(dt)
|
||||
Keyword Arguments:
|
||||
locale -- locale to translate to, e.g. 'fr' for french.
|
||||
(default: 'en' - English)
|
||||
"""
|
||||
dt = pendulum.instance(self.datetime())
|
||||
|
||||
def slang_time(self):
|
||||
""""Returns human slang representation of time."""
|
||||
dt = self.datetime(naive=True, to_timezone=self.local_timezone)
|
||||
return humanize.naturaltime(dt)
|
||||
try:
|
||||
return _translate(dt, locale)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
delta = humanize.time.abs_timedelta(
|
||||
timedelta(seconds=(self.epoch - now().epoch)))
|
||||
|
||||
format_string = "DD MMM"
|
||||
if delta.days >= 365:
|
||||
format_string += " YYYY"
|
||||
|
||||
return dt.format(format_string, locale=locale).title()
|
||||
|
||||
def slang_time(self, locale="en"):
|
||||
""""Returns human slang representation of time.
|
||||
|
||||
Keyword Arguments:
|
||||
locale -- locale to translate to, e.g. 'fr' for french.
|
||||
(default: 'en' - English)
|
||||
"""
|
||||
dt = self.datetime()
|
||||
return pendulum.instance(dt).diff_for_humans(locale=locale)
|
||||
|
||||
|
||||
def utc_offset(time_struct=None):
|
||||
"""
|
||||
Returns the time offset from UTC accounting for DST
|
||||
|
||||
Keyword Arguments:
|
||||
time_struct {time.struct_time} -- the struct time for which to
|
||||
return the UTC offset.
|
||||
If None, use current local time.
|
||||
"""
|
||||
if time_struct:
|
||||
ts = time_struct
|
||||
else:
|
||||
ts = time.localtime()
|
||||
|
||||
if ts[-1]:
|
||||
offset = time.altzone
|
||||
else:
|
||||
offset = time.timezone
|
||||
return offset
|
||||
|
||||
|
||||
def to_utc_offset_naive(dt):
|
||||
if dt.tzinfo is None:
|
||||
return dt
|
||||
|
||||
return dt.astimezone(pytz.utc).replace(tzinfo=None)
|
||||
|
||||
|
||||
def to_utc_offset_aware(dt):
|
||||
if dt.tzinfo is not None:
|
||||
return dt
|
||||
|
||||
return pytz.utc.localize(dt)
|
||||
|
||||
|
||||
@@ -315,40 +406,46 @@ def to_iso8601(dt):
|
||||
|
||||
|
||||
def end_of_day_midnight(dt):
|
||||
return dt if dt.time() == time.min else \
|
||||
(dt.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=1))
|
||||
if dt.time() == time.min:
|
||||
return dt
|
||||
|
||||
else:
|
||||
return (
|
||||
dt.replace(hour=0, minute=0, second=0, microsecond=0) +
|
||||
timedelta(days=1)
|
||||
)
|
||||
|
||||
|
||||
@comparable
|
||||
class MayaInterval(object):
|
||||
"""
|
||||
A MayaInterval represents a range between two datetimes, inclusive of the start
|
||||
and exclusive of the end.
|
||||
A MayaInterval represents a range between two datetimes,
|
||||
inclusive of the start and exclusive of the end.
|
||||
"""
|
||||
|
||||
def __init__(self, start=None, end=None, duration=None):
|
||||
try:
|
||||
# Ensure that proper arguments were passed.
|
||||
assert any((
|
||||
(start and end),
|
||||
(start and duration is not None),
|
||||
(end and duration is not None),
|
||||
))
|
||||
assert any(
|
||||
(
|
||||
(start and end),
|
||||
(start and duration is not None),
|
||||
(end and duration is not None),
|
||||
)
|
||||
)
|
||||
assert not all((start, end, duration is not None))
|
||||
except AssertionError:
|
||||
raise ValueError(
|
||||
'Exactly 2 of start, end, and duration must be specified')
|
||||
'Exactly 2 of start, end, and duration must be specified'
|
||||
)
|
||||
|
||||
# Convert duration to timedelta if seconds were provided.
|
||||
if duration:
|
||||
duration = _seconds_or_timedelta(duration)
|
||||
|
||||
if not start:
|
||||
start = end - duration
|
||||
|
||||
if not end:
|
||||
end = start + duration
|
||||
|
||||
if start > end:
|
||||
raise ValueError('MayaInterval cannot end before it starts')
|
||||
|
||||
@@ -356,28 +453,56 @@ class MayaInterval(object):
|
||||
self.end = end
|
||||
|
||||
def __repr__(self):
|
||||
return '<MayaInterval start={0!r} end={1!r}>'.format(self.start, self.end)
|
||||
return '<MayaInterval start={0!r} end={1!r}>'.format(
|
||||
self.start, self.end
|
||||
)
|
||||
|
||||
def iso8601(self):
|
||||
"""Returns an ISO 8601 representation of the MayaInterval."""
|
||||
return '{0}/{1}'.format(self.start.iso8601(), self.end.iso8601())
|
||||
|
||||
@classmethod
|
||||
def parse_iso8601_duration(cls, duration, start=None, end=None):
|
||||
match = re.match(
|
||||
r'(?:P(?P<weeks>\d+)W)|(?:P(?:(?:(?P<years>\d+)Y)?(?:(?P<months>\d+)M)?(?:(?P<days>\d+)D))?(?:T(?:(?P<hours>\d+)H)?(?:(?P<minutes>\d+)M)?(?:(?P<seconds>\d+)S)?)?)',
|
||||
duration
|
||||
)
|
||||
|
||||
time_components = {}
|
||||
if match:
|
||||
time_components = match.groupdict(0)
|
||||
for key, value in time_components.items():
|
||||
time_components[key] = int(value)
|
||||
|
||||
duration = relativedelta(**time_components)
|
||||
|
||||
if start:
|
||||
return parse(start.datetime() + duration)
|
||||
|
||||
if end:
|
||||
return parse(end.datetime() - duration)
|
||||
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def from_iso8601(cls, s):
|
||||
# # Start and end, such as "2007-03-01T13:00:00Z/2008-05-11T15:30:00Z"
|
||||
# start, end = s.split('/')
|
||||
# try:
|
||||
# start = parse(start)
|
||||
# except pendulum.parsing.exceptions.ParserError:
|
||||
# start = self._parse_iso8601_duration(start)
|
||||
# try:
|
||||
# end = parse(start)
|
||||
# except pendulum.parsing.exceptions.ParserError as e:
|
||||
# end = self._parse_iso8601_duration(start)
|
||||
start, end = s.split('/')
|
||||
try:
|
||||
start = parse(start)
|
||||
except pendulum.parsing.exceptions.ParserError:
|
||||
# start = self._parse_iso8601_duration(start, end=end)
|
||||
raise NotImplementedError()
|
||||
|
||||
try:
|
||||
end = parse(end)
|
||||
except (pendulum.parsing.exceptions.ParserError, TypeError) as e:
|
||||
end = cls.parse_iso8601_duration(end, start=start)
|
||||
|
||||
return cls(start=start, end=end)
|
||||
|
||||
# # Start and duration, such as "2007-03-01T13:00:00Z/P1Y2M10DT2H30M"
|
||||
# # Duration and end, such as "P1Y2M10DT2H30M/2008-05-11T15:30:00Z"
|
||||
raise NotImplementedError()
|
||||
|
||||
@validate_arguments_type_of_function()
|
||||
def __and__(self, maya_interval):
|
||||
@@ -390,8 +515,7 @@ class MayaInterval(object):
|
||||
@validate_arguments_type_of_function()
|
||||
def __eq__(self, maya_interval):
|
||||
return (
|
||||
self.start == maya_interval.start and
|
||||
self.end == maya_interval.end
|
||||
self.start == maya_interval.start and self.end == maya_interval.end
|
||||
)
|
||||
|
||||
def __hash__(self):
|
||||
@@ -404,8 +528,8 @@ class MayaInterval(object):
|
||||
@validate_arguments_type_of_function()
|
||||
def __cmp__(self, maya_interval):
|
||||
return (
|
||||
cmp(self.start, maya_interval.start) or
|
||||
cmp(self.end, maya_interval.end)
|
||||
cmp(self.start, maya_interval.start)
|
||||
or cmp(self.end, maya_interval.end)
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -436,8 +560,9 @@ class MayaInterval(object):
|
||||
MayaInterval(
|
||||
interval_list[0].start,
|
||||
max(interval_list[0].end, interval_list[1].end),
|
||||
),
|
||||
)
|
||||
]
|
||||
|
||||
return interval_list
|
||||
|
||||
@validate_arguments_type_of_function()
|
||||
@@ -445,6 +570,7 @@ class MayaInterval(object):
|
||||
""""Removes the given interval."""
|
||||
if not self & maya_interval:
|
||||
return [self]
|
||||
|
||||
elif maya_interval.contains(self):
|
||||
return []
|
||||
|
||||
@@ -456,10 +582,8 @@ class MayaInterval(object):
|
||||
return interval_list
|
||||
|
||||
def split(self, duration, include_remainder=True):
|
||||
|
||||
# Convert seconds to timedelta, if appropriate.
|
||||
duration = _seconds_or_timedelta(duration)
|
||||
|
||||
if duration <= timedelta(seconds=0):
|
||||
raise ValueError('cannot call split with a non-positive timedelta')
|
||||
|
||||
@@ -467,38 +591,36 @@ class MayaInterval(object):
|
||||
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)
|
||||
|
||||
|
||||
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())
|
||||
|
||||
start_seconds = int((self.start.datetime(naive=False) - epoch).total_seconds())
|
||||
end_seconds = int((self.end.datetime(naive=False) - epoch).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),
|
||||
@@ -507,23 +629,17 @@ class MayaInterval(object):
|
||||
@validate_arguments_type_of_function()
|
||||
def intersection(self, maya_interval):
|
||||
"""Returns the intersection between two intervals."""
|
||||
|
||||
start = max(self.start, maya_interval.start)
|
||||
end = min(self.end, maya_interval.end)
|
||||
|
||||
either_instant = self.is_instant or maya_interval.is_instant
|
||||
instant_overlap = (
|
||||
self.start == maya_interval.start or
|
||||
start <= end
|
||||
)
|
||||
instant_overlap = (self.start == maya_interval.start or start <= end)
|
||||
if (either_instant and instant_overlap) or (start < end):
|
||||
return MayaInterval(start, end)
|
||||
|
||||
@validate_arguments_type_of_function()
|
||||
def contains(self, maya_interval):
|
||||
return (
|
||||
self.start <= maya_interval.start and
|
||||
self.end >= maya_interval.end
|
||||
self.start <= maya_interval.start and self.end >= maya_interval.end
|
||||
)
|
||||
|
||||
def __contains__(self, maya_dt):
|
||||
@@ -538,8 +654,7 @@ class MayaInterval(object):
|
||||
@validate_arguments_type_of_function()
|
||||
def is_adjacent(self, maya_interval):
|
||||
return (
|
||||
self.start == maya_interval.end or
|
||||
self.end == maya_interval.start
|
||||
self.start == maya_interval.end or self.end == maya_interval.start
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -556,14 +671,28 @@ class MayaInterval(object):
|
||||
""".format(
|
||||
self.start.datetime().strftime(ical_dt_format),
|
||||
self.end.datetime().strftime(ical_dt_format),
|
||||
).replace(' ', '').strip('\r\n').replace('\n', '\r\n')
|
||||
).replace(
|
||||
' ', ''
|
||||
).strip(
|
||||
'\r\n'
|
||||
).replace(
|
||||
'\n', '\r\n'
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def flatten(interval_list):
|
||||
return functools.reduce(lambda reduced, maya_interval: (
|
||||
(reduced[:-1] + maya_interval.combine(reduced[-1]))
|
||||
if reduced else [maya_interval]
|
||||
), sorted(interval_list), [])
|
||||
return functools.reduce(
|
||||
lambda reduced,
|
||||
maya_interval: (
|
||||
(
|
||||
reduced[:-1] + maya_interval.combine(reduced[-1])
|
||||
) if reduced else [
|
||||
maya_interval
|
||||
]
|
||||
),
|
||||
sorted(interval_list),
|
||||
[],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_datetime(cls, start_dt=None, end_dt=None, duration=None):
|
||||
@@ -578,7 +707,7 @@ def now():
|
||||
return MayaDT(epoch=epoch)
|
||||
|
||||
|
||||
def when(string, timezone='UTC'):
|
||||
def when(string, timezone='UTC', prefer_dates_from='current_period'):
|
||||
""""Returns a MayaDT instance for the human moment specified.
|
||||
|
||||
Powered by dateparser. Useful for scraping websites.
|
||||
@@ -589,27 +718,52 @@ def when(string, timezone='UTC'):
|
||||
Keyword Arguments:
|
||||
string -- string to be parsed
|
||||
timezone -- timezone referenced from (default: 'UTC')
|
||||
prefer_dates_from -- what dates are prefered when `string` is ambigous.
|
||||
options are 'past', 'future', and 'current_period'
|
||||
(default: 'current_period'). see: [1]
|
||||
|
||||
Reference:
|
||||
[1] dateparser.readthedocs.io/en/latest/usage.html#handling-incomplete-dates
|
||||
"""
|
||||
dt = dateparser.parse(string,
|
||||
settings={'TIMEZONE': timezone, 'RETURN_AS_TIMEZONE_AWARE': True, 'TO_TIMEZONE': 'UTC'})
|
||||
settings = {
|
||||
'TIMEZONE': timezone,
|
||||
'RETURN_AS_TIMEZONE_AWARE': True,
|
||||
'TO_TIMEZONE': 'UTC',
|
||||
'PREFER_DATES_FROM': prefer_dates_from,
|
||||
}
|
||||
|
||||
dt = dateparser.parse(string, settings=settings)
|
||||
if dt is None:
|
||||
raise ValueError('invalid datetime input specified.')
|
||||
|
||||
return MayaDT.from_datetime(dt)
|
||||
|
||||
|
||||
def parse(string, day_first=False):
|
||||
def parse(string, timezone='UTC', day_first=False, year_first=True, strict=False):
|
||||
""""Returns a MayaDT instance for the machine-produced moment specified.
|
||||
|
||||
Powered by pendulum. Accepts most known formats. Useful for working with data.
|
||||
Powered by pendulum.
|
||||
Accepts most known formats. Useful for working with data.
|
||||
|
||||
Keyword Arguments:
|
||||
string -- string to be parsed
|
||||
day_first -- if true, the first value (e.g. 01/05/2016) is parsed as day (default: False)
|
||||
timezone -- timezone referenced from (default: 'UTC')
|
||||
day_first -- if true, the first value (e.g. 01/05/2016)
|
||||
is parsed as day.
|
||||
if year_first is set to True, this distinguishes
|
||||
between YDM and YMD. (default: False)
|
||||
year_first -- if true, the first value (e.g. 2016/05/01)
|
||||
is parsed as year (default: True)
|
||||
strict -- if False, allow pendulum to fall back on datetime parsing
|
||||
if pendulum's own parsing fails
|
||||
"""
|
||||
dt = pendulum.parse(string, day_first=day_first)
|
||||
options = {}
|
||||
options['tz'] = timezone
|
||||
options['day_first'] = day_first
|
||||
options['year_first'] = year_first
|
||||
options['strict'] = strict
|
||||
|
||||
dt = pendulum.parse(str(string), **options)
|
||||
return MayaDT.from_datetime(dt)
|
||||
|
||||
|
||||
@@ -624,17 +778,34 @@ def _seconds_or_timedelta(duration):
|
||||
elif isinstance(duration, timedelta):
|
||||
dt_timedelta = duration
|
||||
else:
|
||||
raise TypeError('Expects argument as `datetime.timedelta` object '
|
||||
'or seconds in `int` format')
|
||||
raise TypeError(
|
||||
'Expects argument as `datetime.timedelta` object '
|
||||
'or seconds in `int` format'
|
||||
)
|
||||
|
||||
return dt_timedelta
|
||||
|
||||
|
||||
def _translate(dt, target_locale):
|
||||
en = default_loader.get_locale("en")
|
||||
target = default_loader.get_locale(target_locale)
|
||||
naturaldate = humanize.naturaldate(dt)
|
||||
|
||||
base = en.translate(naturaldate, settings=dateparser.conf.settings)
|
||||
|
||||
return target.info["relative-type"][base][-1]
|
||||
|
||||
|
||||
def intervals(start, end, interval):
|
||||
"""Yields MayaDT objects between the start and end MayaDTs given, at a given interval (seconds or timedelta)."""
|
||||
|
||||
"""
|
||||
Yields MayaDT objects between the start and end MayaDTs given,
|
||||
at a given interval (seconds or timedelta).
|
||||
"""
|
||||
interval = _seconds_or_timedelta(interval)
|
||||
|
||||
current_timestamp = start
|
||||
while current_timestamp.epoch < end.epoch:
|
||||
yield current_timestamp
|
||||
current_timestamp = current_timestamp.add(seconds=interval.seconds)
|
||||
|
||||
current_timestamp = current_timestamp.add(
|
||||
seconds=interval.total_seconds()
|
||||
)
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import codecs
|
||||
from shutil import rmtree
|
||||
|
||||
from setuptools import setup
|
||||
from setuptools import find_packages, setup, Command
|
||||
|
||||
try:
|
||||
# Python 3
|
||||
from os import dirname
|
||||
except ImportError:
|
||||
# Python 2
|
||||
from os.path import dirname
|
||||
|
||||
here = os.path.abspath(dirname(__file__))
|
||||
here = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
with codecs.open(os.path.join(here, 'README.rst'), encoding='utf-8') as f:
|
||||
long_description = '\n' + f.read()
|
||||
@@ -27,19 +23,56 @@ if sys.argv[-1] == "publish":
|
||||
required = [
|
||||
'humanize',
|
||||
'pytz',
|
||||
'dateparser',
|
||||
'ruamel.yaml',
|
||||
'dateparser>=0.7.0',
|
||||
'tzlocal',
|
||||
'pendulum'
|
||||
'pendulum>=2.0.2',
|
||||
'snaptime'
|
||||
]
|
||||
|
||||
packages = [
|
||||
'maya',
|
||||
]
|
||||
|
||||
class UploadCommand(Command):
|
||||
"""Support setup.py upload."""
|
||||
|
||||
description = 'Build and publish the package.'
|
||||
user_options = []
|
||||
|
||||
@staticmethod
|
||||
def status(s):
|
||||
"""Prints things in bold."""
|
||||
print('\033[1m{0}\033[0m'.format(s))
|
||||
|
||||
def initialize_options(self):
|
||||
pass
|
||||
|
||||
def finalize_options(self):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
self.status('Removing previous builds…')
|
||||
rmtree(os.path.join(here, 'dist'))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
self.status('Building Source and Wheel (universal) distribution…')
|
||||
os.system('{0} setup.py sdist bdist_wheel --universal'.format(sys.executable))
|
||||
|
||||
self.status('Uploading the package to PyPi via Twine…')
|
||||
os.system('twine upload dist/*')
|
||||
|
||||
self.status('Pushing git tags…')
|
||||
os.system('git tag v{0}'.format(about['__version__']))
|
||||
os.system('git push --tags')
|
||||
|
||||
sys.exit()
|
||||
|
||||
|
||||
# About dict to store version and package info
|
||||
about = dict()
|
||||
with open(os.path.join(here, 'maya', '__version__.py'), 'r', encoding='utf-8') as f:
|
||||
with codecs.open(os.path.join(here, 'maya', '__version__.py'), 'r', encoding='utf-8') as f:
|
||||
exec(f.read(), about)
|
||||
|
||||
setup(
|
||||
@@ -67,4 +100,7 @@ setup(
|
||||
'Programming Language :: Python :: Implementation :: CPython',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules'
|
||||
),
|
||||
cmdclass={
|
||||
'upload': UploadCommand,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
from freezegun import freeze_time
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(params=[
|
||||
("2018-03-25T00:00:00", 2),
|
||||
("2018-03-25T01:00:00", 2),
|
||||
("2018-03-25T02:00:00", 2),
|
||||
("2018-03-25T02:30:00", 2),
|
||||
("2018-03-25T03:00:00", 2),
|
||||
("2018-03-25T04:00:00", 2),
|
||||
|
||||
("2018-10-28T00:00:00", 2),
|
||||
("2018-10-28T01:00:00", 2),
|
||||
("2018-10-28T02:00:00", 2),
|
||||
("2018-10-28T02:30:00", 2),
|
||||
("2018-10-28T03:00:00", 2),
|
||||
("2018-10-28T04:00:00", 2),
|
||||
|
||||
], ids=lambda x: x[0] + "_off_" + str(x[1]))
|
||||
def frozen_now(request):
|
||||
now_string, tz_offset = request.param
|
||||
with freeze_time(now_string, tz_offset=tz_offset):
|
||||
yield
|
||||
+210
-84
@@ -1,115 +1,121 @@
|
||||
import pytest
|
||||
import pytz
|
||||
import copy
|
||||
import time
|
||||
from datetime import date, timedelta, datetime as Datetime
|
||||
import calendar
|
||||
from datetime import timedelta, datetime as Datetime
|
||||
|
||||
import pytz
|
||||
import pytest
|
||||
|
||||
import maya
|
||||
from maya.core import _seconds_or_timedelta # import private function
|
||||
|
||||
|
||||
def test_rfc2822():
|
||||
r = maya.parse('February 21, 1994').rfc2822()
|
||||
@pytest.mark.parametrize("string,expected", [
|
||||
('February 21, 1994',
|
||||
'Mon, 21 Feb 1994 00:00:00 GMT'),
|
||||
])
|
||||
def test_rfc2822(string, expected):
|
||||
r = maya.parse(string).rfc2822()
|
||||
d = maya.MayaDT.from_rfc2822(r)
|
||||
assert r == 'Mon, 21 Feb 1994 00:00:00 GMT'
|
||||
assert r == expected
|
||||
assert r == d.rfc2822()
|
||||
|
||||
|
||||
def test_iso8601():
|
||||
r = maya.parse('February 21, 1994').iso8601()
|
||||
@pytest.mark.parametrize("string,expected", [
|
||||
('February 21, 1994',
|
||||
'1994-02-21T00:00:00Z'),
|
||||
])
|
||||
def test_iso8601(string, expected):
|
||||
r = maya.parse(string).iso8601()
|
||||
d = maya.MayaDT.from_iso8601(r)
|
||||
assert r == '1994-02-21T00:00:00Z'
|
||||
assert r == expected
|
||||
assert r == d.iso8601()
|
||||
|
||||
|
||||
def test_parse_iso8601():
|
||||
string = '20161001T1430.4+05:30'
|
||||
expected = '2016-10-01T09:00:00.400000Z'
|
||||
d = maya.MayaDT.from_iso8601(string)
|
||||
@pytest.mark.parametrize("string,expected", [
|
||||
('20161001T1430.4+05:30',
|
||||
'2016-10-01T09:00:00.400000Z'),
|
||||
|
||||
('2016T14',
|
||||
'2016-01-01T14:00:00Z'),
|
||||
|
||||
('2016-10T14',
|
||||
'2016-10-01T14:00:00Z'),
|
||||
|
||||
('2012W05',
|
||||
'2012-01-30T00:00:00Z'),
|
||||
|
||||
('2012W055',
|
||||
'2012-02-03T00:00:00Z'),
|
||||
|
||||
('2012007',
|
||||
'2012-01-07T00:00:00Z'),
|
||||
|
||||
('2016-W07T09',
|
||||
'2016-02-15T09:00:00Z'),
|
||||
])
|
||||
def test_parse_iso8601(string, expected):
|
||||
d = maya.MayaDT.from_iso8601(string)
|
||||
assert expected == d.iso8601()
|
||||
|
||||
string = '2016T14'
|
||||
expected = '2016-01-01T14:00:00Z'
|
||||
d = maya.MayaDT.from_iso8601(string)
|
||||
|
||||
assert expected == d.iso8601()
|
||||
|
||||
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()
|
||||
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_struct():
|
||||
ts = time.gmtime()
|
||||
now = round(time.time())
|
||||
ts = time.gmtime(now)
|
||||
m = maya.MayaDT.from_struct(ts)
|
||||
dt = Datetime.fromtimestamp(time.mktime(ts), pytz.UTC)
|
||||
assert m._epoch != None
|
||||
dt = Datetime.fromtimestamp(now, pytz.UTC)
|
||||
assert m._epoch is not None
|
||||
assert m.datetime() == dt
|
||||
ts = time.localtime(now)
|
||||
m = maya.MayaDT.from_struct(ts)
|
||||
dt = Datetime.fromtimestamp(
|
||||
time.mktime(ts) - maya.core.utc_offset(ts), pytz.UTC
|
||||
)
|
||||
assert m._epoch is not None
|
||||
assert m.datetime() == dt
|
||||
|
||||
ts = time.localtime()
|
||||
m = maya.MayaDT.from_struct(ts)
|
||||
dt = Datetime.fromtimestamp(time.mktime(ts), pytz.UTC)
|
||||
assert m._epoch != None
|
||||
assert m.datetime() == dt
|
||||
|
||||
def test_issue_104():
|
||||
e = 1507756331
|
||||
t = Datetime.utcfromtimestamp(e)
|
||||
t = maya.MayaDT.from_datetime(t)
|
||||
assert str(t) == 'Wed, 11 Oct 2017 21:12:11 GMT'
|
||||
t = time.gmtime(e)
|
||||
t = maya.MayaDT.from_struct(t)
|
||||
assert str(t) == 'Wed, 11 Oct 2017 21:12:11 GMT'
|
||||
|
||||
|
||||
def test_human_when():
|
||||
r1 = maya.when('yesterday')
|
||||
r2 = maya.when('today')
|
||||
|
||||
assert (r2.day - r1.day) in (1, -30, -29, -28, -27)
|
||||
|
||||
|
||||
def test_machine_parse():
|
||||
r1 = maya.parse('August 14, 2015')
|
||||
assert r1.day == 14
|
||||
|
||||
r2 = maya.parse('August 15, 2015')
|
||||
assert r2.day == 15
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_dt_tz_translation():
|
||||
d1 = maya.now().datetime()
|
||||
d2 = maya.now().datetime(to_timezone='EST')
|
||||
assert (d1.hour - d2.hour) % 24 == 5
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_dt_tz_naive():
|
||||
d1 = maya.now().datetime(naive=True)
|
||||
assert d1.tzinfo is None
|
||||
|
||||
d2 = maya.now().datetime(to_timezone='EST', naive=True)
|
||||
assert d2.tzinfo is None
|
||||
assert (d1.hour - d2.hour) % 24 == 5
|
||||
|
||||
|
||||
def test_random_date():
|
||||
|
||||
# Test properties for maya.when()
|
||||
d1 = maya.when('11-17-11 08:09:10')
|
||||
assert d1.year == 2011
|
||||
@@ -121,7 +127,6 @@ def test_random_date():
|
||||
assert d1.minute == 9
|
||||
assert d1.second == 10
|
||||
assert d1.microsecond == 0
|
||||
|
||||
# Test properties for maya.parse()
|
||||
d2 = maya.parse('February 29, 1992 13:12:34')
|
||||
assert d2.year == 1992
|
||||
@@ -137,10 +142,8 @@ def test_random_date():
|
||||
|
||||
def test_print_date(capsys):
|
||||
d = maya.when('11-17-11')
|
||||
|
||||
print(d)
|
||||
out, err = capsys.readouterr()
|
||||
|
||||
assert out == 'Thu, 17 Nov 2011 00:00:00 GMT\n'
|
||||
assert repr(d) == '<MayaDT epoch=1321488000.0>'
|
||||
|
||||
@@ -155,56 +158,128 @@ def test_slang_date():
|
||||
assert d.slang_date() == 'tomorrow'
|
||||
|
||||
|
||||
def test_slang_date_locale():
|
||||
d = maya.when('tomorrow')
|
||||
assert d.slang_date(locale='fr') == 'demain'
|
||||
|
||||
|
||||
def test_slang_time():
|
||||
d = maya.when('one hour ago')
|
||||
assert d.slang_time() == 'an hour ago'
|
||||
d = maya.when('1 hour ago')
|
||||
assert d.slang_time() == '1 hour ago'
|
||||
|
||||
|
||||
def test_parse():
|
||||
d = maya.parse('February 21, 1994')
|
||||
assert format(d) == '1994-02-21 00:00:00+00:00'
|
||||
def test_slang_time_locale():
|
||||
d = maya.when('1 hour ago')
|
||||
assert d.slang_time(locale='de') == 'vor 1 Stunde'
|
||||
|
||||
d = maya.parse('01/05/2016')
|
||||
assert format(d) == '2016-01-05 00:00:00+00:00'
|
||||
|
||||
d = maya.parse('01/05/2016', day_first=True)
|
||||
assert format(d) == '2016-05-01 00:00:00+00:00'
|
||||
@pytest.mark.parametrize("string,kwds,expected", [
|
||||
('February 21, 1994', {},
|
||||
'1994-02-21 00:00:00+00:00'),
|
||||
|
||||
('01/05/2016', {},
|
||||
'2016-01-05 00:00:00+00:00'),
|
||||
|
||||
('01/05/2016', dict(day_first=True),
|
||||
'2016-05-01 00:00:00+00:00'),
|
||||
|
||||
('2016/05/01', dict(year_first=True, day_first=False),
|
||||
'2016-05-01 00:00:00+00:00'),
|
||||
|
||||
('2016/01/05', dict(year_first=True, day_first=True),
|
||||
'2016-05-01 00:00:00+00:00'),
|
||||
|
||||
('01/05/2016', dict(timezone='UTC'),
|
||||
'2016-01-05 00:00:00+00:00'),
|
||||
|
||||
('01/05/2016', dict(timezone='US/Central'),
|
||||
'2016-01-05 06:00:00+00:00'),
|
||||
])
|
||||
def test_parse(string, kwds, expected):
|
||||
d = maya.parse(string, **kwds)
|
||||
assert format(d) == expected
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_when_past():
|
||||
two_days_away = maya.now().add(days=2)
|
||||
|
||||
past_date = maya.when(
|
||||
two_days_away.slang_date(),
|
||||
prefer_dates_from='past')
|
||||
|
||||
assert past_date < maya.now()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_when_future():
|
||||
two_days_away = maya.now().add(days=2)
|
||||
|
||||
future_date = maya.when(
|
||||
two_days_away.slang_date(),
|
||||
prefer_dates_from='future')
|
||||
|
||||
assert future_date > maya.now()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_when_past_day_name():
|
||||
two_days_away = maya.now().add(days=2)
|
||||
|
||||
past_date = maya.when(
|
||||
calendar.day_name[two_days_away.weekday],
|
||||
prefer_dates_from='past')
|
||||
|
||||
assert past_date < maya.now()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_when_future_day_name():
|
||||
two_days_away = maya.now().add(days=2)
|
||||
|
||||
future_date = maya.when(
|
||||
calendar.day_name[two_days_away.weekday],
|
||||
prefer_dates_from='future')
|
||||
|
||||
assert future_date > maya.now()
|
||||
|
||||
|
||||
def test_datetime_to_timezone():
|
||||
dt = maya.when('2016-01-01').datetime(to_timezone='US/Eastern')
|
||||
assert dt.tzinfo.zone == 'US/Eastern'
|
||||
|
||||
def test_rfc3339():
|
||||
mdt = maya.when('2016-01-01')
|
||||
|
||||
def test_rfc3339_epoch():
|
||||
mdt = maya.when('2016-01-01')
|
||||
out = mdt.rfc3339()
|
||||
mdt2 = maya.MayaDT.from_rfc3339(out)
|
||||
assert mdt.epoch == mdt2.epoch
|
||||
|
||||
|
||||
def test_rfc3339_format():
|
||||
rfc3339 = maya.MayaDT.rfc3339(maya.when('2016-01-01T12:03:03Z'))
|
||||
# it's important that the string has got a "max 1-digit millis" fragment
|
||||
# as per https://tools.ietf.org/html/rfc3339#section-5.6
|
||||
assert rfc3339 == '2016-01-01T12:03:03.0Z'
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_comparison_operations():
|
||||
now = maya.now()
|
||||
now_copy = copy.deepcopy(now)
|
||||
tomorrow = maya.when('tomorrow')
|
||||
|
||||
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
|
||||
@@ -230,39 +305,90 @@ def test_seconds_or_timedelta():
|
||||
_seconds_or_timedelta('invalid interval')
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_intervals():
|
||||
now = maya.now()
|
||||
tomorrow = now.add(days=1)
|
||||
|
||||
assert len(list(maya.intervals(now, tomorrow, 60*60))) == 24
|
||||
assert len(list(maya.intervals(now, tomorrow, 60 * 60))) == 24
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_dunder_add():
|
||||
now = maya.now()
|
||||
assert now + 1 == now.add(seconds=1)
|
||||
assert now + timedelta(seconds=1) == now.add(seconds=1)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_dunder_radd():
|
||||
now = maya.now()
|
||||
assert now.add(seconds=1) == now + 1
|
||||
assert now.add(seconds=1) == now + timedelta(seconds=1)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_dunder_sub():
|
||||
now = maya.now()
|
||||
assert now - 1 == now.subtract(seconds=1)
|
||||
assert now - timedelta(seconds=1) == now.subtract(seconds=1)
|
||||
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_mayaDT_sub():
|
||||
now = maya.now()
|
||||
then = now.add(days=1)
|
||||
assert then - now == timedelta(seconds=24 * 60 * 60)
|
||||
assert now - then == timedelta(seconds=-24 * 60 * 60)
|
||||
|
||||
|
||||
def test_core_local_timezone(monkeypatch):
|
||||
|
||||
@property
|
||||
def mock_local_tz(self):
|
||||
|
||||
class StaticTzInfo(object):
|
||||
zone = 'local'
|
||||
|
||||
def __repr__(self):
|
||||
return "<StaticTzInfo 'local'>"
|
||||
|
||||
return StaticTzInfo()
|
||||
|
||||
monkeypatch.setattr(maya.MayaDT, '_local_tz', mock_local_tz)
|
||||
mdt = maya.MayaDT(0)
|
||||
assert mdt.local_timezone == 'UTC'
|
||||
|
||||
|
||||
def test_getting_datetime_for_local_timezone(monkeypatch):
|
||||
|
||||
@property
|
||||
def mock_local_tz(self):
|
||||
class StaticTzInfo(object):
|
||||
zone = 'Europe/Zurich'
|
||||
|
||||
def __repr__(self):
|
||||
return "<StaticTzInfo 'Europe/Zurich'>"
|
||||
|
||||
return StaticTzInfo()
|
||||
|
||||
monkeypatch.setattr(maya.MayaDT, '_local_tz', mock_local_tz)
|
||||
|
||||
d = maya.parse('1994-02-21T12:00:00+05:30')
|
||||
|
||||
dt = pytz.timezone('Europe/Zurich').localize(
|
||||
Datetime(1994, 2, 21, 7, 30))
|
||||
|
||||
assert d.local_datetime() == dt
|
||||
|
||||
|
||||
@pytest.mark.parametrize("when_str,snap_str,expected_when", [
|
||||
('Mon, 21 Feb 1994 21:21:42 GMT', '@d',
|
||||
'Mon, 21 Feb 1994 00:00:00 GMT'),
|
||||
])
|
||||
def test_snaptime(when_str, snap_str, expected_when):
|
||||
# given
|
||||
dt = maya.when(when_str)
|
||||
# when
|
||||
dt = dt.snap(snap_str)
|
||||
# then
|
||||
assert dt == maya.when(expected_when)
|
||||
|
||||
+253
-214
@@ -15,26 +15,20 @@ Melbourne = pytz.timezone('Australia/Melbourne')
|
||||
def test_interval_requires_2_of_start_end_duration():
|
||||
start = maya.now()
|
||||
end = start.add(hours=1)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
maya.MayaInterval(start=start)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
maya.MayaInterval(end=end)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
maya.MayaInterval(duration=60)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
maya.MayaInterval(start=start, end=end, duration=60)
|
||||
|
||||
maya.MayaInterval(start=start, end=end)
|
||||
maya.MayaInterval(start=start, duration=60)
|
||||
maya.MayaInterval(end=end, duration=60)
|
||||
|
||||
|
||||
def test_interval_requires_end_time_after_or_on_start_time():
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
maya.MayaInterval(start=maya.now(), duration=0)
|
||||
maya.MayaInterval(start=maya.now(), duration=-1)
|
||||
@@ -64,7 +58,9 @@ def test_interval_init_end_duration():
|
||||
assert interval.start == end.subtract(seconds=duration)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start_doy1,end_doy1,start_doy2,end_doy2,intersection_doys', (
|
||||
@pytest.mark.parametrize(
|
||||
'start_doy1,end_doy1,start_doy2,end_doy2,intersection_doys',
|
||||
(
|
||||
(0, 2, 1, 3, (1, 2)),
|
||||
(0, 2, 3, 4, None),
|
||||
(0, 2, 2, 3, None),
|
||||
@@ -76,7 +72,8 @@ def test_interval_init_end_duration():
|
||||
(1, 3, 1, 1, (1, 1)),
|
||||
(2, 3, 1, 1, None),
|
||||
(1, 3, 2, 2, (2, 2)),
|
||||
), ids=(
|
||||
),
|
||||
ids=(
|
||||
'overlapping',
|
||||
'non-overlapping',
|
||||
'adjacent',
|
||||
@@ -87,21 +84,19 @@ def test_interval_init_end_duration():
|
||||
'instant overlapping',
|
||||
'instant overlapping start only (left)',
|
||||
'instant disjoint (left)',
|
||||
'instant overlapping (left)'
|
||||
))
|
||||
'instant overlapping (left)',
|
||||
),
|
||||
)
|
||||
def test_interval_intersection(
|
||||
start_doy1, end_doy1, start_doy2, end_doy2, intersection_doys
|
||||
start_doy1, end_doy1, start_doy2, end_doy2, intersection_doys
|
||||
):
|
||||
base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
|
||||
interval1 = maya.MayaInterval(
|
||||
base.add(days=start_doy1),
|
||||
base.add(days=end_doy1),
|
||||
base.add(days=start_doy1), base.add(days=end_doy1)
|
||||
)
|
||||
interval2 = maya.MayaInterval(
|
||||
base.add(days=start_doy2),
|
||||
base.add(days=end_doy2),
|
||||
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(
|
||||
@@ -110,7 +105,6 @@ def test_interval_intersection(
|
||||
)
|
||||
else:
|
||||
assert (interval1 & interval2) is None
|
||||
|
||||
# check invalid argument
|
||||
with pytest.raises(TypeError):
|
||||
interval1 & 'invalid type'
|
||||
@@ -119,13 +113,10 @@ def test_interval_intersection(
|
||||
def test_interval_intersects():
|
||||
base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
|
||||
interval = maya.MayaInterval(base, base.add(days=1))
|
||||
|
||||
assert interval.intersects(interval)
|
||||
assert not interval.intersects(maya.MayaInterval(
|
||||
base.add(days=2),
|
||||
base.add(days=3),
|
||||
))
|
||||
|
||||
assert not interval.intersects(
|
||||
maya.MayaInterval(base.add(days=2), base.add(days=3))
|
||||
)
|
||||
# check invalid argument
|
||||
with pytest.raises(TypeError):
|
||||
interval.intersects('invalid type')
|
||||
@@ -135,13 +126,11 @@ 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)
|
||||
)
|
||||
|
||||
# check invalid argument
|
||||
with pytest.raises(TypeError):
|
||||
interval1.intersection('invalid type')
|
||||
@@ -151,14 +140,11 @@ def test_interval_eq_operator():
|
||||
start = maya.now()
|
||||
end = start.add(hours=1)
|
||||
interval = maya.MayaInterval(start=start, end=end)
|
||||
|
||||
assert interval == maya.MayaInterval(start=start, end=end)
|
||||
assert interval != maya.MayaInterval(start=start, end=end.add(days=1))
|
||||
|
||||
# check invalid argument
|
||||
with pytest.raises(TypeError):
|
||||
interval == 'invalid type'
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
interval != 'invalid type'
|
||||
|
||||
@@ -167,7 +153,6 @@ def test_interval_timedelta():
|
||||
start = maya.now()
|
||||
delta = timedelta(hours=1)
|
||||
interval = maya.MayaInterval(start=start, duration=delta)
|
||||
|
||||
assert interval.timedelta == delta
|
||||
|
||||
|
||||
@@ -175,69 +160,55 @@ def test_interval_duration():
|
||||
start = maya.now()
|
||||
delta = timedelta(hours=1)
|
||||
interval = maya.MayaInterval(start=start, duration=delta)
|
||||
|
||||
assert interval.duration == delta.total_seconds()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start_doy1,end_doy1,start_doy2,end_doy2,expected', (
|
||||
@pytest.mark.parametrize(
|
||||
'start_doy1,end_doy1,start_doy2,end_doy2,expected',
|
||||
(
|
||||
(0, 2, 1, 3, False),
|
||||
(0, 2, 3, 4, False),
|
||||
(0, 2, 2, 3, False),
|
||||
(0, 1, 0, 1, True),
|
||||
(0, 3, 1, 2, True),
|
||||
), ids=(
|
||||
'overlapping',
|
||||
'non-overlapping',
|
||||
'adjacent',
|
||||
'equal',
|
||||
'subset',
|
||||
))
|
||||
),
|
||||
ids=('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(
|
||||
base.add(days=start_doy1),
|
||||
base.add(days=end_doy1),
|
||||
base.add(days=start_doy1), base.add(days=end_doy1)
|
||||
)
|
||||
interval2 = maya.MayaInterval(
|
||||
base.add(days=start_doy2),
|
||||
base.add(days=end_doy2),
|
||||
base.add(days=start_doy2), base.add(days=end_doy2)
|
||||
)
|
||||
|
||||
assert interval1.contains(interval2) is expected
|
||||
assert (interval2 in interval1) is expected
|
||||
|
||||
# check invalid argument
|
||||
with pytest.raises(TypeError):
|
||||
interval1.contains('invalid type')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start_doy,end_doy,dt_doy,expected', (
|
||||
@pytest.mark.parametrize(
|
||||
'start_doy,end_doy,dt_doy,expected',
|
||||
(
|
||||
(2, 4, 1, False),
|
||||
(2, 4, 2, True),
|
||||
(2, 4, 3, True),
|
||||
(2, 4, 4, False),
|
||||
(2, 4, 5, False),
|
||||
), ids=(
|
||||
'before-start',
|
||||
'on-start',
|
||||
'during',
|
||||
'on-end',
|
||||
'after-end',
|
||||
))
|
||||
def test_interval_in_operator_maya_dt(
|
||||
start_doy, end_doy, dt_doy, expected
|
||||
):
|
||||
),
|
||||
ids=('before-start', 'on-start', 'during', 'on-end', 'after-end'),
|
||||
)
|
||||
def test_interval_in_operator_maya_dt(start_doy, end_doy, dt_doy, expected):
|
||||
base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
|
||||
interval = maya.MayaInterval(
|
||||
start=base.add(days=start_doy),
|
||||
end=base.add(days=end_doy),
|
||||
start=base.add(days=start_doy), end=base.add(days=end_doy)
|
||||
)
|
||||
dt = base.add(days=dt_doy)
|
||||
|
||||
assert (dt in interval) is expected
|
||||
|
||||
# check invalid argument
|
||||
with pytest.raises(TypeError):
|
||||
'invalid type' in interval
|
||||
@@ -247,91 +218,84 @@ def test_interval_hash():
|
||||
start = maya.now()
|
||||
end = start.add(hours=1)
|
||||
interval = maya.MayaInterval(start=start, end=end)
|
||||
|
||||
assert hash(interval) == hash(maya.MayaInterval(start=start, end=end))
|
||||
assert hash(interval) != hash(maya.MayaInterval(
|
||||
start=start, end=end.add(days=1)))
|
||||
assert hash(interval) != hash(
|
||||
maya.MayaInterval(start=start, end=end.add(days=1))
|
||||
)
|
||||
|
||||
|
||||
def test_interval_iter():
|
||||
start = maya.now()
|
||||
end = start.add(days=1)
|
||||
|
||||
assert tuple(maya.MayaInterval(start=start, end=end)) == (start, end)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start1,end1,start2,end2,expected', [
|
||||
(1, 2, 1, 2, 0),
|
||||
(1, 3, 2, 4, -1),
|
||||
(2, 4, 1, 3, 1),
|
||||
(1, 2, 1, 3, -1),
|
||||
], ids=(
|
||||
@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),
|
||||
start=base.add(days=start1), end=base.add(days=end1)
|
||||
)
|
||||
interval2 = maya.MayaInterval(
|
||||
start=base.add(days=start2),
|
||||
end=base.add(days=end2),
|
||||
start=base.add(days=start2), end=base.add(days=end2)
|
||||
)
|
||||
assert cmp(interval1, interval2) == expected
|
||||
|
||||
# check invalid argument
|
||||
with pytest.raises(TypeError):
|
||||
cmp(interval1, 'invalid type')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start1,end1,start2,end2,expected', [
|
||||
(1, 2, 2, 3, [(1, 3)]),
|
||||
(1, 3, 2, 4, [(1, 4)]),
|
||||
(1, 2, 3, 4, [(1, 2), (3, 4)]),
|
||||
(1, 5, 2, 3, [(1, 5)]),
|
||||
], ids=(
|
||||
'adjacent',
|
||||
'overlapping',
|
||||
'non-overlapping',
|
||||
'contains',
|
||||
))
|
||||
@pytest.mark.parametrize(
|
||||
'start1,end1,start2,end2,expected',
|
||||
[
|
||||
(1, 2, 2, 3, [(1, 3)]),
|
||||
(1, 3, 2, 4, [(1, 4)]),
|
||||
(1, 2, 3, 4, [(1, 2), (3, 4)]),
|
||||
(1, 5, 2, 3, [(1, 5)]),
|
||||
],
|
||||
ids=('adjacent', 'overlapping', 'non-overlapping', 'contains'),
|
||||
)
|
||||
def test_interval_combine(start1, end1, start2, end2, expected):
|
||||
base = maya.now()
|
||||
interval1 = maya.MayaInterval(
|
||||
start=base.add(days=start1),
|
||||
end=base.add(days=end1),
|
||||
start=base.add(days=start1), end=base.add(days=end1)
|
||||
)
|
||||
interval2 = maya.MayaInterval(
|
||||
start=base.add(days=start2),
|
||||
end=base.add(days=end2),
|
||||
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]
|
||||
|
||||
expected_intervals = [
|
||||
maya.MayaInterval(start=base.add(days=start), end=base.add(days=end))
|
||||
for start, end in expected
|
||||
]
|
||||
assert interval1.combine(interval2) == expected_intervals
|
||||
assert interval2.combine(interval1) == expected_intervals
|
||||
|
||||
# check invalid argument
|
||||
with pytest.raises(TypeError):
|
||||
interval2.combine('invalid type')
|
||||
|
||||
|
||||
@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=(
|
||||
@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',
|
||||
@@ -340,115 +304,109 @@ def test_interval_combine(start1, end1, start2, end2, expected):
|
||||
'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),
|
||||
start=base.add(days=start1), end=base.add(days=end1)
|
||||
)
|
||||
interval2 = maya.MayaInterval(
|
||||
start=base.add(days=start2),
|
||||
end=base.add(days=end2),
|
||||
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]
|
||||
|
||||
expected_intervals = [
|
||||
maya.MayaInterval(start=base.add(days=start), end=base.add(days=end))
|
||||
for start, end in expected
|
||||
]
|
||||
assert interval1.subtract(interval2) == expected_intervals
|
||||
|
||||
# check invalid argument
|
||||
with pytest.raises(TypeError):
|
||||
interval1.subtract('invalid type')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start1,end1,start2,end2,expected', [
|
||||
(1, 2, 2, 3, True),
|
||||
(2, 3, 1, 2, True),
|
||||
(1, 3, 2, 3, False),
|
||||
(2, 3, 4, 5, False),
|
||||
], ids=(
|
||||
'adjacent-right',
|
||||
'adjacent-left',
|
||||
'overlapping',
|
||||
'non-overlapping',
|
||||
))
|
||||
@pytest.mark.parametrize(
|
||||
'start1,end1,start2,end2,expected',
|
||||
[
|
||||
(1, 2, 2, 3, True),
|
||||
(2, 3, 1, 2, True),
|
||||
(1, 3, 2, 3, False),
|
||||
(2, 3, 4, 5, False),
|
||||
],
|
||||
ids=('adjacent-right', 'adjacent-left', 'overlapping', 'non-overlapping'),
|
||||
)
|
||||
def test_interval_is_adjacent(start1, end1, start2, end2, expected):
|
||||
base = maya.now()
|
||||
interval1 = maya.MayaInterval(
|
||||
start=base.add(days=start1),
|
||||
end=base.add(days=end1),
|
||||
start=base.add(days=start1), end=base.add(days=end1)
|
||||
)
|
||||
interval2 = maya.MayaInterval(
|
||||
start=base.add(days=start2),
|
||||
end=base.add(days=end2),
|
||||
start=base.add(days=start2), end=base.add(days=end2)
|
||||
)
|
||||
assert interval1.is_adjacent(interval2) == expected
|
||||
|
||||
# check invalid argument
|
||||
with pytest.raises(TypeError):
|
||||
interval1.is_adjacent('invalid type')
|
||||
|
||||
|
||||
@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=(
|
||||
@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),
|
||||
start=base.add(days=start), end=base.add(days=end)
|
||||
)
|
||||
delta = timedelta(days=delta)
|
||||
|
||||
expected_intervals = [
|
||||
maya.MayaInterval(
|
||||
start=base.add(days=s),
|
||||
end=base.add(days=e),
|
||||
) for s, e in expected
|
||||
maya.MayaInterval(start=base.add(days=s), end=base.add(days=e))
|
||||
for s, e in expected
|
||||
]
|
||||
|
||||
assert expected_intervals == list(interval.split(
|
||||
delta, include_remainder=include_remainder))
|
||||
assert expected_intervals == list(
|
||||
interval.split(delta, include_remainder=include_remainder)
|
||||
)
|
||||
|
||||
|
||||
def test_interval_split_non_positive_delta():
|
||||
start = maya.now()
|
||||
end = start.add(days=1)
|
||||
interval = maya.MayaInterval(start=start, end=end)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
list(interval.split(timedelta(seconds=0)))
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
list(interval.split(timedelta(seconds=-10)))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start,end,minutes,timezone,snap_out,expected_start,expected_end', [
|
||||
((5, 12), (8, 48), 30, None, False, (5, 30), (8, 30)),
|
||||
((5, 12), (8, 48), 30, None, True, (5, 0), (9, 0)),
|
||||
((5, 15), (9, 0), 15, None, False, (5, 15), (9, 0)),
|
||||
((5, 15), (9, 0), 15, None, True, (5, 15), (9, 0)),
|
||||
((6, 50), (9, 15), 60, 'America/New_York', False, (7, 0), (9, 0)),
|
||||
((6, 50), (9, 15), 60, 'America/New_York', True, (6, 0), (10, 0)),
|
||||
((6, 20), (6, 50), 60, None, False, (6, 0), (6, 0)),
|
||||
((6, 20), (6, 50), 60, None, True, (6, 0), (7, 0)),
|
||||
((6, 20), (6, 50), 60, 'America/Chicago', False, (6, 0), (6, 0)),
|
||||
((6, 20), (6, 50), 60, 'America/Chicago', True, (6, 0), (7, 0)),
|
||||
], ids=(
|
||||
@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',
|
||||
@@ -459,21 +417,20 @@ def test_interval_split_non_positive_delta():
|
||||
'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):
|
||||
),
|
||||
)
|
||||
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
|
||||
timedelta(minutes=minutes), snap_out=snap_out, **kwargs
|
||||
)
|
||||
|
||||
assert quantized_interval == maya.MayaInterval(
|
||||
start=base.add(hours=expected_start[0], minutes=expected_start[1]),
|
||||
end=base.add(hours=expected_end[0], minutes=expected_end[1]),
|
||||
@@ -484,7 +441,6 @@ def test_quantize_invalid_delta():
|
||||
start = maya.now()
|
||||
end = start.add(days=1)
|
||||
interval = maya.MayaInterval(start=start, end=end)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
interval.quantize(timedelta(minutes=0))
|
||||
with pytest.raises(ValueError):
|
||||
@@ -495,12 +451,13 @@ def test_interval_flatten_non_overlapping():
|
||||
step = 2
|
||||
max_hour = 20
|
||||
base = maya.now()
|
||||
intervals = [maya.MayaInterval(
|
||||
start=base.add(hours=hour),
|
||||
duration=timedelta(hours=step - 1),
|
||||
) for hour in range(0, max_hour, step)]
|
||||
intervals = [
|
||||
maya.MayaInterval(
|
||||
start=base.add(hours=hour), duration=timedelta(hours=step - 1)
|
||||
)
|
||||
for hour in range(0, max_hour, step)
|
||||
]
|
||||
random.shuffle(intervals)
|
||||
|
||||
assert maya.MayaInterval.flatten(intervals) == sorted(intervals)
|
||||
|
||||
|
||||
@@ -508,35 +465,35 @@ 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)
|
||||
start=base.add(hours=hour), duration=timedelta(hours=step)
|
||||
)
|
||||
for hour in range(0, max_hour, step)
|
||||
]
|
||||
random.shuffle(intervals)
|
||||
|
||||
assert maya.MayaInterval.flatten(intervals) == [maya.MayaInterval(
|
||||
start=base,
|
||||
duration=timedelta(hours=max_hour),
|
||||
)]
|
||||
assert maya.MayaInterval.flatten(intervals) == [
|
||||
maya.MayaInterval(start=base, duration=timedelta(hours=max_hour))
|
||||
]
|
||||
|
||||
|
||||
def test_interval_flatten_intersecting():
|
||||
step = 2
|
||||
max_hour = 20
|
||||
base = maya.now()
|
||||
intervals = [maya.MayaInterval(
|
||||
start=base.add(hours=hour),
|
||||
duration=timedelta(hours=step, minutes=30),
|
||||
) for hour in range(0, max_hour, step)]
|
||||
intervals = [
|
||||
maya.MayaInterval(
|
||||
start=base.add(hours=hour),
|
||||
duration=timedelta(hours=step, minutes=30),
|
||||
)
|
||||
for hour in range(0, max_hour, step)
|
||||
]
|
||||
random.shuffle(intervals)
|
||||
|
||||
assert maya.MayaInterval.flatten(intervals) == [maya.MayaInterval(
|
||||
start=base,
|
||||
duration=timedelta(hours=max_hour, minutes=30),
|
||||
)]
|
||||
assert maya.MayaInterval.flatten(intervals) == [
|
||||
maya.MayaInterval(
|
||||
start=base, duration=timedelta(hours=max_hour, minutes=30)
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def test_interval_flatten_containing():
|
||||
@@ -544,16 +501,16 @@ def test_interval_flatten_containing():
|
||||
max_hour = 20
|
||||
base = maya.now()
|
||||
containing_interval = maya.MayaInterval(
|
||||
start=base,
|
||||
end=base.add(hours=max_hour + step),
|
||||
start=base, end=base.add(hours=max_hour + step)
|
||||
)
|
||||
intervals = [maya.MayaInterval(
|
||||
start=base.add(hours=hour),
|
||||
duration=timedelta(hours=step - 1),
|
||||
) for hour in range(2, max_hour, step)]
|
||||
intervals = [
|
||||
maya.MayaInterval(
|
||||
start=base.add(hours=hour), duration=timedelta(hours=step - 1)
|
||||
)
|
||||
for hour in range(2, max_hour, step)
|
||||
]
|
||||
intervals.append(containing_interval)
|
||||
random.shuffle(intervals)
|
||||
|
||||
assert maya.MayaInterval.flatten(intervals) == [containing_interval]
|
||||
|
||||
|
||||
@@ -561,24 +518,18 @@ def test_interval_from_datetime():
|
||||
start = maya.now()
|
||||
duration = timedelta(hours=1)
|
||||
end = start + duration
|
||||
|
||||
interval = maya.MayaInterval.from_datetime(
|
||||
start_dt=start.datetime(naive=False),
|
||||
end_dt=end.datetime(naive=False),
|
||||
start_dt=start.datetime(naive=False), end_dt=end.datetime(naive=False)
|
||||
)
|
||||
assert interval.start == start
|
||||
assert interval.end == end
|
||||
|
||||
interval2 = maya.MayaInterval.from_datetime(
|
||||
start_dt=start.datetime(naive=False),
|
||||
duration=duration,
|
||||
start_dt=start.datetime(naive=False), duration=duration
|
||||
)
|
||||
assert interval2.start == start
|
||||
assert interval2.end == end
|
||||
|
||||
interval3 = maya.MayaInterval.from_datetime(
|
||||
end_dt=end.datetime(naive=False),
|
||||
duration=duration,
|
||||
end_dt=end.datetime(naive=False), duration=duration
|
||||
)
|
||||
assert interval3.start == start
|
||||
assert interval3.end == end
|
||||
@@ -588,3 +539,91 @@ def test_interval_iso8601():
|
||||
start = maya.when('11-17-11 08:09:10')
|
||||
interval = maya.MayaInterval(start=start, duration=1)
|
||||
assert interval.iso8601() == '2011-11-17T08:09:10Z/2011-11-17T08:09:11Z'
|
||||
|
||||
|
||||
def test_interval_from_iso8601():
|
||||
interval = maya.MayaInterval.from_iso8601(
|
||||
"2018-03-18T14:27:18Z/2018-04-01T04:15:27Z"
|
||||
)
|
||||
s = maya.when("2018-03-18T14:27:18Z")
|
||||
e = maya.when("2018-04-01T04:15:27Z")
|
||||
|
||||
assert interval.start == s
|
||||
assert interval.end == e
|
||||
|
||||
|
||||
def test_interval_from_iso8601_duration():
|
||||
interval = maya.MayaInterval.from_iso8601(
|
||||
"2018-03-18T14:27:18Z/P13DT13H48M9S"
|
||||
)
|
||||
s = maya.when("2018-03-18T14:27:18Z")
|
||||
e = maya.when("2018-04-01T04:15:27Z")
|
||||
|
||||
assert interval.start == s
|
||||
assert interval.end == e
|
||||
|
||||
interval = maya.MayaInterval.from_iso8601(
|
||||
"2018-03-05T14:27:18Z/P2W"
|
||||
)
|
||||
s = maya.when("2018-03-05T14:27:18Z")
|
||||
e = maya.when("2018-03-19T14:27:18Z")
|
||||
|
||||
assert interval.start == s
|
||||
assert interval.end == e
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"start_string,end_string,interval,expected_count",
|
||||
[
|
||||
("2019-01-03 11:40:00Z", "2019-01-03 11:40:20Z", 2, 10),
|
||||
(
|
||||
"2019-01-03 11:40:00Z",
|
||||
"2019-01-03 11:40:30Z",
|
||||
timedelta(seconds=2),
|
||||
15,
|
||||
),
|
||||
("2019-01-03 11:40:00Z", "2019-01-03 11:45:00Z", 2 * 60, 3),
|
||||
(
|
||||
"2019-01-03 11:40:00Z",
|
||||
"2019-01-03 11:51:00Z",
|
||||
timedelta(minutes=1),
|
||||
11,
|
||||
),
|
||||
("2019-01-03 11:40:00Z", "2019-01-03 21:40:00Z", 3 * 60 * 60, 4),
|
||||
(
|
||||
"2019-01-03 11:40:00Z",
|
||||
"2019-01-03 13:41:00Z",
|
||||
timedelta(hours=1),
|
||||
3,
|
||||
),
|
||||
("2019-01-03 11:40:00Z", "2019-01-09 11:40:00Z", 3 * 60 * 60 * 24, 2),
|
||||
("2019-01-03 11:40:00Z", "2019-01-05 12:00:00Z", timedelta(days=2), 2),
|
||||
],
|
||||
ids=(
|
||||
"seconds",
|
||||
"seconds-timedelta",
|
||||
"minutes",
|
||||
"minutes-timedelta",
|
||||
"hours",
|
||||
"hours-timedelta",
|
||||
"days",
|
||||
"days-timedelta",
|
||||
),
|
||||
)
|
||||
def test_intervals(start_string, end_string, interval, expected_count):
|
||||
start = maya.parse(start_string)
|
||||
end = maya.parse(end_string)
|
||||
assert len(list(maya.intervals(start, end, interval))) == expected_count
|
||||
|
||||
|
||||
def test_issue_168_regression():
|
||||
start = maya.now()
|
||||
end = start.add(weeks=1)
|
||||
gen = maya.intervals(start=start, end=end, interval=60 * 60 * 24)
|
||||
# Since the bug causes the generator to never end, first sanity
|
||||
# check that two results are not the same.
|
||||
assert next(gen) != next(gen)
|
||||
assert (
|
||||
len(list(maya.intervals(start=start, end=end, interval=60 * 60 * 24)))
|
||||
== 7
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user