Compare commits

..

1 Commits

Author SHA1 Message Date
kennethreitz 52ca9a1cc4 Update README.rst 2017-05-29 14:41:27 -04:00
12 changed files with 416 additions and 909 deletions
-6
View File
@@ -2,14 +2,8 @@ language: python
python: python:
- "2.7" - "2.7"
- "3.6" - "3.6"
- "3.7-dev"
matrix:
allow_failures:
- python: "3.7-dev"
# command to install dependencies # command to install dependencies
install: pip install pipenv; pipenv lock; pipenv install --dev install: pip install pipenv; pipenv lock; pipenv install --dev
# command to run tests # command to run tests
script: pipenv run pytest tests/ script: pipenv run pytest tests/
-2
View File
@@ -22,5 +22,3 @@ In chronological order:
- Joshua Li <joshua.r.li.98@gmail.com> (`@JoshuaRLi <https://github.com/JoshuaRLi>`_) - Joshua Li <joshua.r.li.98@gmail.com> (`@JoshuaRLi <https://github.com/JoshuaRLi>`_)
- Sébastien Eustace <sebastien@eustace.io> (`@sdispater <https://github.com/sdispater>`_) - Sébastien Eustace <sebastien@eustace.io> (`@sdispater <https://github.com/sdispater>`_)
- Evan Mattiza <emattiza@gmail.com> (`@emattiza <https://github.com/emattiza>`_) - 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>`_)
-2
View File
@@ -1,5 +1,3 @@
.PHONY: tests docs
tests: tests:
pytest tests/ pytest tests/
docs: docs:
+1 -5
View File
@@ -1,15 +1,11 @@
[dev-packages] [dev-packages]
pytest = "*" pytest = "*"
sphinx = "*" Sphinx = "*"
[packages] [packages]
humanize = "*" humanize = "*"
pytz = "*" pytz = "*"
dateparser = "*" dateparser = "*"
"ruamel.yaml" = "*" "ruamel.yaml" = "*"
tzlocal = "*" tzlocal = "*"
pendulum = ">=1.0" pendulum = ">=1.0"
snaptime = "*"
Generated
+21 -266
View File
@@ -1,13 +1,11 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "3abd33d2f9beea083faca673fa3864252623b284fa24f9a8b939aed12428318c" "sha256": "5617ff73ba51e60721267b24dc01e83f33d2a3692870b60e394b0f75ed2dc313"
}, },
"pipfile-spec": 6,
"requires": {}, "requires": {},
"sources": [ "sources": [
{ {
"name": "pypi",
"url": "https://pypi.python.org/simple", "url": "https://pypi.python.org/simple",
"verify_ssl": true "verify_ssl": true
} }
@@ -15,303 +13,60 @@
}, },
"default": { "default": {
"dateparser": { "dateparser": {
"hashes": [ "version": "==0.6.0"
"sha256:940828183c937bcec530753211b70f673c0a9aab831e43273489b310538dff86",
"sha256:b452ef8b36cd78ae86a50721794bc674aa3994e19b570f7ba92810f4e0a2ae03"
],
"index": "pypi",
"version": "==0.7.0"
}, },
"humanize": { "humanize": {
"hashes": [
"sha256:a43f57115831ac7c70de098e6ac46ac13be00d69abbf60bdcac251344785bb19"
],
"index": "pypi",
"version": "==0.5.1" "version": "==0.5.1"
}, },
"pendulum": { "pendulum": {
"hashes": [ "version": "==1.2.0"
"sha256:0c14388546db6605a860b8b7112cb69d0b11c9ce5e072210504544e0d4575799",
"sha256:39a255776528afe11ea0d57814f9bf3729c1e0b99063af2e5c6cfd750c3e1f7f",
"sha256:3c85e8cbc91f45e1cc916cc9180b34153cd6aaaaacfb51a48b3156318314fa82",
"sha256:8199206c479b13947dcac63c025575d035331bb3819d1783dc1d568a11962906",
"sha256:8798aeca58b3dd7ffdc5a4993c9eaafedc4048165429e8f499ddd62c73bf3964",
"sha256:881efe37328de0785c0731d462e1485a45712f2cd5cb55907d6c15458460ebeb",
"sha256:bcca072f82e84b419efec1320cd3ee5c230d263f3a601b146651ed4db77d89f0",
"sha256:ff0c5fa3af4a471a218408c448b804ac6bccb105127727474f4e83c0e4072e97"
],
"index": "pypi",
"version": "==1.4.2"
}, },
"python-dateutil": { "python-dateutil": {
"hashes": [ "version": "==2.6.0"
"sha256:07009062406cffd554a9b4135cd2ff167c9bf6b7aac61fe946c93e69fad1bbd8",
"sha256:8f95bb7e6edbb2456a51a1fb58c8dca942024b4f5844cae62c90aa88afe6e300"
],
"version": "==2.7.0"
}, },
"pytz": { "pytz": {
"hashes": [ "version": "==2017.2"
"sha256:07edfc3d4d2705a20a6e99d97f0c4b61c800b8232dc1c04d87e8554f130148dd",
"sha256:3a47ff71597f821cd84a162e71593004286e5be07a340fd462f0d33a760782b5",
"sha256:410bcd1d6409026fbaa65d9ed33bf6dd8b1e94a499e32168acfc7b332e4095c0",
"sha256:5bd55c744e6feaa4d599a6cbd8228b4f8f9ba96de2c38d56f08e534b3c9edf0d",
"sha256:61242a9abc626379574a166dc0e96a66cd7c3b27fc10868003fa210be4bff1c9",
"sha256:887ab5e5b32e4d0c86efddd3d055c1f363cbaa583beb8da5e22d2fa2f64d51ef",
"sha256:ba18e6a243b3625513d85239b3e49055a2f0318466e0b8a92b8fb8ca7ccdf55f",
"sha256:ed6509d9af298b7995d69a440e2822288f2eca1681b8cce37673dbb10091e5fe",
"sha256:f93ddcdd6342f94cea379c73cddb5724e0d6d0a1c91c9bdef364dc0368ba4fda"
],
"index": "pypi",
"version": "==2018.3"
}, },
"pytzdata": { "pytzdata": {
"hashes": [ "version": "==2017.2"
"sha256:4e2cceb54335cd6c28caea46b15cd592e2aec5e8b05b0241cbccfb1b23c02ae7",
"sha256:7cd949123e2c2060fd12793de3a4a449e36b5dea5e169b810a3ac3f0b9877cfa"
],
"version": "==2018.3"
}, },
"regex": { "regex": {
"hashes": [ "version": "==2017.04.29"
"sha256:1b428a296531ea1642a7da48562746309c5c06471a97bd0c02dd6a82e9cecee8", },
"sha256:27d72bb42dffb32516c28d218bb054ce128afd3e18464f30837166346758af67", "ruamel.ordereddict": {
"sha256:32cf4743debee9ea12d3626ee21eae83052763740e04086304e7a74778bf58c9", "version": "==0.4.9"
"sha256:32f6408dbca35040bc65f9f4ae1444d5546411fde989cb71443a182dd643305e",
"sha256:333687d9a44738c486735955993f83bd22061a416c48f5a5f9e765e90cf1b0c9",
"sha256:35eeccf17af3b017a54d754e160af597036435c58eceae60f1dd1364ae1250c7",
"sha256:361a1fd703a35580a4714ec28d85e29780081a4c399a99bbfb2aee695d72aedb",
"sha256:494bed6396a20d3aa6376bdf2d3fbb1005b8f4339558d8ac7b53256755f80303",
"sha256:5b9c0ddd5b4afa08c9074170a2ea9b34ea296e32aeea522faaaaeeeb2fe0af2e",
"sha256:a50532f61b23d4ab9d216a6214f359dd05c911c1a1ad20986b6738a782926c1a",
"sha256:a9243d7b359b72c681a2c32eaa7ace8d346b7e8ce09d172a683acf6853161d9c",
"sha256:b44624a38d07d3c954c84ad302c29f7930f4bf01443beef5589e9157b14e2a29",
"sha256:be42a601aaaeb7a317f818490a39d153952a97c40c6e9beeb2a1103616405348",
"sha256:eee4d94b1a626490fc8170ffd788883f8c641b576e11ba9b4a29c9f6623371e0",
"sha256:f69d1201a4750f763971ea8364ed95ee888fc128968b39d38883a72a4d005895"
],
"version": "==2018.2.21"
}, },
"ruamel.yaml": { "ruamel.yaml": {
"hashes": [ "version": "==0.14.12"
"sha256:01e30ecb1b1c0ebf9fce814dc20dace402571517277799291202b61b22096c24",
"sha256:02babffd019911841ba01b76e23dfec7c9e9b2725503fb2698c4982fa1a6e835",
"sha256:072f6364a89972e8dc0afdce3335a709d5464dfeaa4f736d092a54574338b874",
"sha256:14d161558e3bf89e87d77c218098be22fa9a0d6d0bea40250fce525b1d0cbee2",
"sha256:5504398fc755a2b14c9983b2101161a8591a4b30812590cc1c365e7fcc117dfa",
"sha256:68c8f2986bcb91b6db1aea8698941769840c7257e951a9377048f7eff35be773",
"sha256:6d05c5a5baf829c70916c226ef3200650846a7227de226bca8a59efaf88bb973",
"sha256:6d7929b24e329d662fa43b657fddfee5260e2d35d0a543065cd755d4e17a9b2f",
"sha256:8dc74821e4bb6b21fb1ab35964e159391d99ee44981d07d57bf96e2395f3ef75",
"sha256:9225c83952d28f302cfc23c3d9a6f8231bfd581476d7aff1e3c7de49eecb4ee9",
"sha256:b6c5d5f03ba78e3f27c7188a00c4e09b6a4507fe3154ba40a294e09cb30ee016",
"sha256:c0908896e34b617ead40552cab03c1769bdc43d1da02419160dc900c5dfddde2",
"sha256:c41e04b526d0153c9246cfab87d7ddefdc9f165cb8886a8ec48ba7a2b73069f6",
"sha256:e2d2715bf92156bec5fb42e92e95dac1c4d9904f8a3d4e2d0c438758fe9092d7",
"sha256:e3bbfe0d294e08fdbb0cb05485435a2ceb4e168e98b5dc611f051c1864986b4b",
"sha256:f2d02a4af5a13b09d0b823cdd0317b54f3e0115e50b5ac4d9840c3a1b566817f",
"sha256:fcfc24a21594c071cc4588e84b7657a1f47ebcf6037c6c43fa15c4bbd3989ec2"
],
"index": "pypi",
"version": "==0.15.35"
}, },
"six": { "six": {
"hashes": [ "version": "==1.10.0"
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
],
"version": "==1.11.0"
},
"snaptime": {
"hashes": [
"sha256:e3f1eb89043d58d30721ab98cb65023f1a4c2740e3b197704298b163c92d508b"
],
"index": "pypi",
"version": "==0.2.4"
}, },
"tzlocal": { "tzlocal": {
"hashes": [ "version": "==1.4"
"sha256:4ebeb848845ac898da6519b9b31879cf13b6626f7184c496037b818e238f2c4e"
],
"index": "pypi",
"version": "==1.5.1"
} }
}, },
"develop": { "develop": {
"alabaster": { "appdirs": {
"hashes": [ "version": "==1.4.3"
"sha256:2eef172f44e8d301d25aff8068fddd65f767a3f04b5f15b0f4922f113aa1c732",
"sha256:37cdcb9e9954ed60912ebc1ca12a9d12178c26637abdf124e3cde2341c257fe0"
],
"version": "==0.7.10"
},
"attrs": {
"hashes": [
"sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9",
"sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450"
],
"version": "==17.4.0"
},
"babel": {
"hashes": [
"sha256:8ce4cb6fdd4393edd323227cba3a077bceb2a6ce5201c902c65e730046f41f14",
"sha256:ad209a68d7162c4cff4b29cdebe3dec4cef75492df501b0049a9433c96ce6f80"
],
"version": "==2.5.3"
},
"certifi": {
"hashes": [
"sha256:14131608ad2fd56836d33a71ee60fa1c82bc9d2c8d98b7bdbc631fe1b3cd1296",
"sha256:edbc3f203427eef571f79a7692bb160a2b0f7ccaa31953e99bd17e307cf63f7d"
],
"version": "==2018.1.18"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"docutils": {
"hashes": [
"sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6",
"sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274",
"sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6"
],
"version": "==0.14"
},
"idna": {
"hashes": [
"sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f",
"sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4"
],
"version": "==2.6"
},
"imagesize": {
"hashes": [
"sha256:3620cc0cadba3f7475f9940d22431fc4d407269f1be59ec9b8edcca26440cf18",
"sha256:5b326e4678b6925158ccc66a9fa3122b6106d7c876ee32d7de6ce59385b96315"
],
"version": "==1.0.0"
},
"jinja2": {
"hashes": [
"sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
"sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
],
"version": "==2.10"
},
"markupsafe": {
"hashes": [
"sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"
],
"version": "==1.0"
}, },
"packaging": { "packaging": {
"hashes": [ "version": "==16.8"
"sha256:e9215d2d2535d3ae866c3d6efc77d5b24a0192cce0ff20e42896cc0664f889c0",
"sha256:f019b770dd64e585a99714f1fd5e01c7a8f11b45635aa953fd41c689a657375b"
],
"version": "==17.1"
},
"pluggy": {
"hashes": [
"sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff"
],
"version": "==0.6.0"
}, },
"py": { "py": {
"hashes": [ "version": "==1.4.33"
"sha256:8cca5c229d225f8c1e3085be4fcf306090b00850fefad892f9d96c7b6e2f310f",
"sha256:ca18943e28235417756316bfada6cd96b23ce60dd532642690dcfdaba988a76d"
],
"version": "==1.5.2"
},
"pygments": {
"hashes": [
"sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d",
"sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc"
],
"version": "==2.2.0"
}, },
"pyparsing": { "pyparsing": {
"hashes": [
"sha256:0832bcf47acd283788593e7a0f542407bd9550a55a8a8435214a1960e04bcb04",
"sha256:281683241b25fe9b80ec9d66017485f6deff1af5cde372469134b56ca8447a07",
"sha256:8f1e18d3fd36c6795bb7e02a39fd05c611ffc2596c1e0d995d34d67630426c18",
"sha256:9e8143a3e15c13713506886badd96ca4b579a87fbdf49e550dbfc057d6cb218e",
"sha256:b8b3117ed9bdf45e14dcc89345ce638ec7e0e29b2b579fa1ecf32ce45ebac8a5",
"sha256:e4d45427c6e20a59bf4f88c639dcc03ce30d193112047f94012102f235853a58",
"sha256:fee43f17a9c4087e7ed1605bd6df994c6173c1e977d7ade7b651292fab2bd010"
],
"version": "==2.2.0" "version": "==2.2.0"
}, },
"pytest": { "pytest": {
"hashes": [ "version": "==3.0.7"
"sha256:062027955bccbc04d2fcd5d79690947e018ba31abe4c90b2c6721abec734261b",
"sha256:117bad36c1a787e1a8a659df35de53ba05f9f3398fb9e4ac17e80ad5903eb8c5"
],
"index": "pypi",
"version": "==3.4.2"
}, },
"pytz": { "setuptools": {
"hashes": [ "version": "==35.0.2"
"sha256:07edfc3d4d2705a20a6e99d97f0c4b61c800b8232dc1c04d87e8554f130148dd",
"sha256:3a47ff71597f821cd84a162e71593004286e5be07a340fd462f0d33a760782b5",
"sha256:410bcd1d6409026fbaa65d9ed33bf6dd8b1e94a499e32168acfc7b332e4095c0",
"sha256:5bd55c744e6feaa4d599a6cbd8228b4f8f9ba96de2c38d56f08e534b3c9edf0d",
"sha256:61242a9abc626379574a166dc0e96a66cd7c3b27fc10868003fa210be4bff1c9",
"sha256:887ab5e5b32e4d0c86efddd3d055c1f363cbaa583beb8da5e22d2fa2f64d51ef",
"sha256:ba18e6a243b3625513d85239b3e49055a2f0318466e0b8a92b8fb8ca7ccdf55f",
"sha256:ed6509d9af298b7995d69a440e2822288f2eca1681b8cce37673dbb10091e5fe",
"sha256:f93ddcdd6342f94cea379c73cddb5724e0d6d0a1c91c9bdef364dc0368ba4fda"
],
"index": "pypi",
"version": "==2018.3"
},
"requests": {
"hashes": [
"sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b",
"sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e"
],
"version": "==2.18.4"
}, },
"six": { "six": {
"hashes": [ "version": "==1.10.0"
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
],
"version": "==1.11.0"
},
"snowballstemmer": {
"hashes": [
"sha256:919f26a68b2c17a7634da993d91339e288964f93c274f1343e3bbbe2096e1128",
"sha256:9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89"
],
"version": "==1.2.1"
},
"sphinx": {
"hashes": [
"sha256:41ae26acc6130ccf6ed47e5cca73742b80d55a134f0ab897c479bba8d3640b8e",
"sha256:da987de5fcca21a4acc7f67a86a363039e67ac3e8827161e61b91deb131c0ee8"
],
"index": "pypi",
"version": "==1.7.1"
},
"sphinxcontrib-websupport": {
"hashes": [
"sha256:7a85961326aa3a400cd4ad3c816d70ed6f7c740acd7ce5d78cd0a67825072eb9",
"sha256:f4932e95869599b89bf4f80fc3989132d83c9faa5bf633e7b5e0c25dffb75da2"
],
"version": "==1.0.1"
},
"urllib3": {
"hashes": [
"sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b",
"sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f"
],
"version": "==1.22"
} }
} }
} }
+11 -26
View File
@@ -10,6 +10,7 @@ Maya: Datetimes for Humans™
.. image:: https://img.shields.io/badge/SayThanks-!-1EAEDB.svg .. image:: https://img.shields.io/badge/SayThanks-!-1EAEDB.svg
:target: https://saythanks.io/to/kennethreitz :target: https://saythanks.io/to/kennethreitz
test
Datetimes are very frustrating to work with in Python, especially when dealing Datetimes are very frustrating to work with in Python, especially when dealing
with different locales on different systems. This library exists to make the with different locales on different systems. This library exists to make the
@@ -20,8 +21,6 @@ Datetimes should be interacted with via an API written for humans.
Maya is mostly built around the headaches and use-cases around parsing datetime data from websites. Maya is mostly built around the headaches and use-cases around parsing datetime data from websites.
.. image:: https://farm4.staticflickr.com/3702/33288285996_5b69d2b8f7_k_d.jpg
☤ Basic Usage of Maya ☤ Basic Usage of Maya
--------------------- ---------------------
@@ -65,15 +64,6 @@ Behold, datetimes for humans!
>>> rand_day = maya.when('2011-02-07', timezone='US/Eastern') >>> rand_day = maya.when('2011-02-07', timezone='US/Eastern')
<MayaDT epoch=1297036800.0> <MayaDT epoch=1297036800.0>
# Maya speaks Python.
>>> m = maya.MayaDT.from_datetime(datetime.utcnow())
>>> print(m)
Wed, 20 Sep 2017 17:24:32 GMT
>>> m = maya.MayaDT.from_struct(time.gmtime())
>>> print(m)
Wed, 20 Sep 2017 17:24:32 GMT
>>> rand_day.day >>> rand_day.day
7 7
@@ -85,18 +75,13 @@ Behold, datetimes for humans!
UTC UTC
# Range of hours in a day: # Range of hours in a day:
>>> maya.intervals(start=maya.now(), end=maya.now().add(days=1), interval=60*60) >>> maya.interval(start=maya.now(), end=maya.now().add(days=1), interval=60*60)
<generator object intervals at 0x105ba5820> <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 ☤ Advanced Usage of Maya
------------------------ ------------------------
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. 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: For example:
@@ -104,15 +89,15 @@ For example:
>>> from maya import MayaInterval >>> 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_start = maya.now()
>>> event_end = event_start.add(hours=1) >>> event_end = event_start.add(hours=1)
>>> event = MayaInterval(start=event_start, end=event_end) >>> 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. 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? ☤ Why is this useful?
--------------------- ---------------------
@@ -129,7 +114,7 @@ From here, there are a number of methods available to you, which you can use to
☤ What about Delorean, Arrow, & Pendulum? ☤ What about Delorean, Arrow, & Pendulum?
----------------------------------------- -----------------------------------------
All these project complement each other, and are friends. Pendulum, for example, helps power Maya's parsing. All these 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. 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.
@@ -141,9 +126,9 @@ I think these projects complement each-other, personally. Maya is great for pars
☤ Installing Maya ☤ Installing Maya
----------------- -----------------
Installation is easy, with `pipenv <http://pipenv.org/>`_:: Installation is easy, with pip::
$ pipenv install maya $ pip install maya
✨🍰✨ ✨🍰✨
+1 -1
View File
@@ -1 +1 @@
__version__ = '0.3.4' __version__ = '0.3.2'
+9 -11
View File
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
maya.compat maya.compat
~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
@@ -11,19 +12,24 @@ import sys
# ------- # -------
# Pythons # Pythons
# ------- # -------
# Syntax sugar. # Syntax sugar.
_ver = sys.version_info _ver = sys.version_info
# : Python 2.x?
#: Python 2.x?
is_py2 = (_ver[0] == 2) is_py2 = (_ver[0] == 2)
# : Python 3.x?
#: Python 3.x?
is_py3 = (_ver[0] == 3) is_py3 = (_ver[0] == 3)
# --------- # ---------
# Specifics # Specifics
# --------- # ---------
if is_py2: if is_py2:
cmp = cmp cmp = cmp
elif is_py3:
elif is_py3:
def cmp(a, b): def cmp(a, b):
""" """
Compare two objects. Compare two objects.
@@ -32,10 +38,8 @@ elif is_py3:
""" """
if a < b: if a < b:
return -1 return -1
elif a == b: elif a == b:
return 0 return 0
else: else:
return 1 return 1
@@ -55,42 +59,36 @@ def comparable(klass):
c = self.__cmp__(other) c = self.__cmp__(other)
if c is NotImplemented: if c is NotImplemented:
return c return c
return c == 0 return c == 0
def __ne__(self, other): def __ne__(self, other):
c = self.__cmp__(other) c = self.__cmp__(other)
if c is NotImplemented: if c is NotImplemented:
return c return c
return c != 0 return c != 0
def __lt__(self, other): def __lt__(self, other):
c = self.__cmp__(other) c = self.__cmp__(other)
if c is NotImplemented: if c is NotImplemented:
return c return c
return c < 0 return c < 0
def __le__(self, other): def __le__(self, other):
c = self.__cmp__(other) c = self.__cmp__(other)
if c is NotImplemented: if c is NotImplemented:
return c return c
return c <= 0 return c <= 0
def __gt__(self, other): def __gt__(self, other):
c = self.__cmp__(other) c = self.__cmp__(other)
if c is NotImplemented: if c is NotImplemented:
return c return c
return c > 0 return c > 0
def __ge__(self, other): def __ge__(self, other):
c = self.__cmp__(other) c = self.__cmp__(other)
if c is NotImplemented: if c is NotImplemented:
return c return c
return c >= 0 return c >= 0
klass.__lt__ = __lt__ klass.__lt__ = __lt__
+111 -247
View File
@@ -1,6 +1,7 @@
# ___ __ ___ _ _ ___ # ___ __ ___ _ _ ___
# || \/ | ||=|| \\// ||=|| # || \/ | ||=|| \\// ||=||
# || | || || // || || # || | || || // || ||
# Ignore warnings for yaml usage. # Ignore warnings for yaml usage.
import warnings import warnings
import ruamel.yaml import ruamel.yaml
@@ -9,20 +10,19 @@ warnings.simplefilter('ignore', ruamel.yaml.error.UnsafeLoaderWarning)
import email.utils import email.utils
import time import time
import functools
from datetime import timedelta, datetime as Datetime from datetime import timedelta, datetime as Datetime
import re import functools
import pytz import pytz
import humanize import humanize
import dateparser import dateparser
import pendulum import pendulum
import snaptime
from tzlocal import get_localzone from tzlocal import get_localzone
from dateutil.relativedelta import relativedelta
from .compat import cmp, comparable from .compat import cmp, comparable
_EPOCH_START = (1970, 1, 1)
def validate_class_type_arguments(operator): def validate_class_type_arguments(operator):
""" """
@@ -31,20 +31,14 @@ def validate_class_type_arguments(operator):
""" """
def inner(function): def inner(function):
def wrapper(self, *args, **kwargs): def wrapper(self, *args, **kwargs):
for arg in args + tuple(kwargs.values()): for arg in args + tuple(kwargs.values()):
if not isinstance(arg, self.__class__): if not isinstance(arg, self.__class__):
raise TypeError( raise TypeError('unorderable types: {}() {} {}()'.format(
'unorderable types: {}() {} {}()'.format( type(self).__name__, operator, type(arg).__name__))
type(self).__name__, operator, type(arg).__name__
)
)
return function(self, *args, **kwargs) return function(self, *args, **kwargs)
return wrapper return wrapper
return inner return inner
@@ -57,34 +51,26 @@ def validate_arguments_type_of_function(param_type=None):
Note: Use this decorator on the functions of the class. Note: Use this decorator on the functions of the class.
""" """
def inner(function): def inner(function):
def wrapper(self, *args, **kwargs): def wrapper(self, *args, **kwargs):
type_ = param_type or type(self) type_ = param_type or type(self)
for arg in args + tuple(kwargs.values()): for arg in args + tuple(kwargs.values()):
if not isinstance(arg, type_): if not isinstance(arg, type_):
raise TypeError( raise TypeError(('Invalid Type: {}.{}() accepts only the '
( 'arguments of type "<{}>"').format(
'Invalid Type: {}.{}() accepts only the ' type(self).__name__,
'arguments of type "<{}>"' function.__name__,
).format( type_.__name__,
type(self).__name__, )
function.__name__, )
type_.__name__,
)
)
return function(self, *args, **kwargs) return function(self, *args, **kwargs)
return wrapper return wrapper
return inner return inner
class MayaDT(object): class MayaDT(object):
"""The Maya Datetime object.""" """The Maya Datetime object."""
__EPOCH_START = (1970, 1, 1)
def __init__(self, epoch): def __init__(self, epoch):
super(MayaDT, self).__init__() super(MayaDT, self).__init__()
@@ -128,49 +114,26 @@ class MayaDT(object):
return hash(int(self.epoch)) return hash(int(self.epoch))
def __add__(self, duration): def __add__(self, duration):
return self.add( return self.add(seconds=seconds_or_timedelta(duration).total_seconds())
seconds=_seconds_or_timedelta(duration).total_seconds()
)
def __radd__(self, duration): def __radd__(self, duration):
return self + duration return self + duration
def __sub__(self, duration_or_date): def __sub__(self, duration):
if isinstance(duration_or_date, MayaDT): return self.subtract(
return self.subtract_date(dt=duration_or_date) seconds=seconds_or_timedelta(duration).total_seconds())
else:
return self.subtract(
seconds=_seconds_or_timedelta(duration_or_date).total_seconds()
)
def add(self, **kwargs): def add(self, **kwargs):
"""Returns a new MayaDT object with the given offsets.""" """"Returns a new MayaDT object with the given offsets."""
return self.from_datetime( return self.from_datetime(pendulum.instance(self.datetime()).add(**kwargs))
pendulum.instance(self.datetime()).add(**kwargs)
)
def subtract(self, **kwargs): def subtract(self, **kwargs):
"""Returns a new MayaDT object with the given offsets.""" """"Returns a new MayaDT object with the given offsets."""
return self.from_datetime( return self.from_datetime(pendulum.instance(self.datetime()).subtract(**kwargs))
pendulum.instance(self.datetime()).subtract(**kwargs)
)
def subtract_date(self, **kwargs):
"""Returns a timedelta object with the duration between the dates"""
return timedelta(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 # Timezone Crap
# ------------- # -------------
@property @property
def timezone(self): def timezone(self):
"""Returns the UTC tzinfo name. It's always UTC. Always.""" """Returns the UTC tzinfo name. It's always UTC. Always."""
@@ -183,11 +146,8 @@ class MayaDT(object):
@property @property
def local_timezone(self): def local_timezone(self):
"""Returns the name of the local timezone.""" """Returns the name of the local timezone, for informational purposes."""
if self._local_tz.zone in pytz.all_timezones: return self._local_tz.zone
return self._local_tz.zone
return self.timezone
@property @property
def _local_tz(self): def _local_tz(self):
@@ -198,31 +158,23 @@ class MayaDT(object):
@validate_arguments_type_of_function(Datetime) @validate_arguments_type_of_function(Datetime)
def __dt_to_epoch(dt): def __dt_to_epoch(dt):
"""Converts a datetime into an epoch.""" """Converts a datetime into an epoch."""
# Assume UTC if no datetime is provided. # Assume UTC if no datetime is provided.
if dt.tzinfo is None: if dt.tzinfo is None:
dt = dt.replace(tzinfo=pytz.utc) dt = dt.replace(tzinfo=pytz.utc)
epoch_start = Datetime(*MayaDT.__EPOCH_START, tzinfo=pytz.timezone('UTC'))
epoch_start = Datetime(*_EPOCH_START, tzinfo=pytz.timezone('UTC'))
return (dt - epoch_start).total_seconds() return (dt - epoch_start).total_seconds()
# Importers # Importers
# --------- # ---------
@classmethod @classmethod
@validate_arguments_type_of_function(Datetime) @validate_arguments_type_of_function(Datetime)
def from_datetime(klass, dt): def from_datetime(klass, dt):
"""Returns MayaDT instance from datetime.""" """Returns MayaDT instance from datetime."""
return klass(klass.__dt_to_epoch(dt)) return klass(klass.__dt_to_epoch(dt))
@classmethod
@validate_arguments_type_of_function(time.struct_time)
def from_struct(klass, struct, timezone=pytz.UTC):
"""Returns MayaDT instance from a 9-tuple struct
It's assumed to be from gmtime().
"""
struct_time = time.mktime(struct) - utc_offset(struct)
dt = Datetime.fromtimestamp(struct_time, timezone)
return klass(klass.__dt_to_epoch(dt))
@classmethod @classmethod
def from_iso8601(klass, iso8601_string): def from_iso8601(klass, iso8601_string):
"""Returns MayaDT instance from iso8601 string.""" """Returns MayaDT instance from iso8601 string."""
@@ -240,27 +192,28 @@ class MayaDT(object):
# Exporters # Exporters
# --------- # ---------
def datetime(self, to_timezone=None, naive=False): def datetime(self, to_timezone=None, naive=False):
"""Returns a timezone-aware datetime... """Returns a timezone-aware datetime...
Defaulting to UTC (as it should). Defaulting to UTC (as it should).
Keyword Arguments: Keyword Arguments:
to_timezone {str} -- timezone to convert to (default: None/UTC) to_timezone {string} -- timezone to convert to (default: None/UTC)
naive {bool} -- if True, naive {boolean} -- if True, the tzinfo is simply dropped (default: False)
the tzinfo is simply dropped (default: False)
""" """
if to_timezone: if to_timezone:
dt = self.datetime().astimezone(pytz.timezone(to_timezone)) dt = self.datetime().astimezone(pytz.timezone(to_timezone))
else: else:
dt = Datetime.utcfromtimestamp(self._epoch) dt = Datetime.utcfromtimestamp(self._epoch)
dt.replace(tzinfo=self._tz) dt.replace(tzinfo=self._tz)
# Strip the timezone info if requested to do so. # Strip the timezone info if requested to do so.
if naive: if naive:
return dt.replace(tzinfo=None) return dt.replace(tzinfo=None)
else: else:
if dt.tzinfo is None: if dt.tzinfo is None:
dt = dt.replace(tzinfo=self._tz) dt = dt.replace(tzinfo=self._tz)
return dt return dt
def iso8601(self): def iso8601(self):
@@ -279,6 +232,7 @@ class MayaDT(object):
# Properties # Properties
# ---------- # ----------
@property @property
def year(self): def year(self):
return self.datetime().year return self.datetime().year
@@ -291,20 +245,13 @@ class MayaDT(object):
def day(self): def day(self):
return self.datetime().day return self.datetime().day
@property
def date(self):
return self.datetime().date()
@property @property
def week(self): def week(self):
return self.datetime().isocalendar()[1] return self.datetime().isocalendar()[1]
@property @property
def weekday(self): def weekday(self):
"""Return the day of the week as an integer. """Return the day of the week as an integer. Monday is 1 and Sunday is 7"""
Monday is 1 and Sunday is 7.
"""
return self.datetime().isoweekday() return self.datetime().isoweekday()
@property @property
@@ -329,6 +276,7 @@ class MayaDT(object):
# Human Slang Extras # Human Slang Extras
# ------------------ # ------------------
def slang_date(self): def slang_date(self):
""""Returns human slang representation of date.""" """"Returns human slang representation of date."""
dt = self.datetime(naive=True, to_timezone=self.local_timezone) dt = self.datetime(naive=True, to_timezone=self.local_timezone)
@@ -340,38 +288,15 @@ class MayaDT(object):
return humanize.naturaltime(dt) return humanize.naturaltime(dt)
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): def to_utc_offset_naive(dt):
if dt.tzinfo is None: if dt.tzinfo is None:
return dt return dt
return dt.astimezone(pytz.utc).replace(tzinfo=None) return dt.astimezone(pytz.utc).replace(tzinfo=None)
def to_utc_offset_aware(dt): def to_utc_offset_aware(dt):
if dt.tzinfo is not None: if dt.tzinfo is not None:
return dt return dt
return pytz.utc.localize(dt) return pytz.utc.localize(dt)
@@ -380,46 +305,40 @@ def to_iso8601(dt):
def end_of_day_midnight(dt): def end_of_day_midnight(dt):
if dt.time() == time.min: return dt if dt.time() == time.min else \
return dt (dt.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=1))
else:
return (
dt.replace(hour=0, minute=0, second=0, microsecond=0) +
timedelta(days=1)
)
@comparable @comparable
class MayaInterval(object): class MayaInterval(object):
""" """
A MayaInterval represents a range between two datetimes, A MayaInterval represents a range between two datetimes, inclusive of the start
inclusive of the start and exclusive of the end. and exclusive of the end.
""" """
def __init__(self, start=None, end=None, duration=None): def __init__(self, start=None, end=None, duration=None):
try: try:
# Ensure that proper arguments were passed. # Ensure that proper arguments were passed.
assert any( assert any((
( (start and end),
(start and end), (start and duration is not None),
(start and duration is not None), (end and duration is not None),
(end and duration is not None), ))
)
)
assert not all((start, end, duration is not None)) assert not all((start, end, duration is not None))
except AssertionError: except AssertionError:
raise ValueError( raise ValueError(
'Exactly 2 of start, end, and duration must be specified' 'Exactly 2 of start, end, and duration must be specified')
)
# Convert duration to timedelta if seconds were provided. # Convert duration to timedelta if seconds were provided.
if duration: if duration:
duration = _seconds_or_timedelta(duration) duration = seconds_or_timedelta(duration)
if not start: if not start:
start = end - duration start = end - duration
if not end: if not end:
end = start + duration end = start + duration
if start > end: if start > end:
raise ValueError('MayaInterval cannot end before it starts') raise ValueError('MayaInterval cannot end before it starts')
@@ -427,56 +346,28 @@ class MayaInterval(object):
self.end = end self.end = end
def __repr__(self): def __repr__(self):
return '<MayaInterval start={0!r} end={1!r}>'.format( return '<MayaInterval start={0!r} end={1!r}>'.format(self.start, self.end)
self.start, self.end
)
def iso8601(self): def iso8601(self):
"""Returns an ISO 8601 representation of the MayaInterval.""" """Returns an ISO 8601 representation of the MayaInterval."""
return '{0}/{1}'.format(self.start.iso8601(), self.end.iso8601()) return '{0}/{1}'.format(self.start.iso6801, 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 @classmethod
def from_iso8601(cls, s): def from_iso8601(cls, s):
# # Start and end, such as "2007-03-01T13:00:00Z/2008-05-11T15:30:00Z" # # Start and end, such as "2007-03-01T13:00:00Z/2008-05-11T15:30:00Z"
start, end = s.split('/') # start, end = s.split('/')
try: # try:
start = parse(start) # start = parse(start)
except pendulum.parsing.exceptions.ParserError: # except pendulum.parsing.exceptions.ParserError:
# start = self._parse_iso8601_duration(start, end=end) # start = self._parse_iso8601_duration(start)
raise NotImplementedError() # try:
# end = parse(start)
try: # except pendulum.parsing.exceptions.ParserError as e:
end = parse(end) # end = self._parse_iso8601_duration(start)
except pendulum.parsing.exceptions.ParserError 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" # # Start and duration, such as "2007-03-01T13:00:00Z/P1Y2M10DT2H30M"
# # Duration and end, such as "P1Y2M10DT2H30M/2008-05-11T15:30:00Z" # # Duration and end, such as "P1Y2M10DT2H30M/2008-05-11T15:30:00Z"
raise NotImplementedError()
@validate_arguments_type_of_function() @validate_arguments_type_of_function()
def __and__(self, maya_interval): def __and__(self, maya_interval):
@@ -489,7 +380,8 @@ class MayaInterval(object):
@validate_arguments_type_of_function() @validate_arguments_type_of_function()
def __eq__(self, maya_interval): def __eq__(self, maya_interval):
return ( return (
self.start == maya_interval.start and self.end == maya_interval.end self.start == maya_interval.start and
self.end == maya_interval.end
) )
def __hash__(self): def __hash__(self):
@@ -502,8 +394,8 @@ class MayaInterval(object):
@validate_arguments_type_of_function() @validate_arguments_type_of_function()
def __cmp__(self, maya_interval): def __cmp__(self, maya_interval):
return ( return (
cmp(self.start, maya_interval.start) cmp(self.start, maya_interval.start) or
or cmp(self.end, maya_interval.end) cmp(self.end, maya_interval.end)
) )
@property @property
@@ -534,9 +426,8 @@ class MayaInterval(object):
MayaInterval( MayaInterval(
interval_list[0].start, interval_list[0].start,
max(interval_list[0].end, interval_list[1].end), max(interval_list[0].end, interval_list[1].end),
) ),
] ]
return interval_list return interval_list
@validate_arguments_type_of_function() @validate_arguments_type_of_function()
@@ -544,7 +435,6 @@ class MayaInterval(object):
""""Removes the given interval.""" """"Removes the given interval."""
if not self & maya_interval: if not self & maya_interval:
return [self] return [self]
elif maya_interval.contains(self): elif maya_interval.contains(self):
return [] return []
@@ -556,8 +446,10 @@ class MayaInterval(object):
return interval_list return interval_list
def split(self, duration, include_remainder=True): def split(self, duration, include_remainder=True):
# Convert seconds to timedelta, if appropriate. # Convert seconds to timedelta, if appropriate.
duration = _seconds_or_timedelta(duration) duration = seconds_or_timedelta(duration)
if duration <= timedelta(seconds=0): if duration <= timedelta(seconds=0):
raise ValueError('cannot call split with a non-positive timedelta') raise ValueError('cannot call split with a non-positive timedelta')
@@ -565,36 +457,38 @@ class MayaInterval(object):
while start < self.end: while start < self.end:
if start + duration <= self.end: if start + duration <= self.end:
yield MayaInterval(start, start + duration) yield MayaInterval(start, start + duration)
elif include_remainder: elif include_remainder:
yield MayaInterval(start, self.end) yield MayaInterval(start, self.end)
start += duration start += duration
def quantize(self, duration, snap_out=False, timezone='UTC'): def quantize(self, duration, snap_out=False, timezone='UTC'):
"""Returns a quanitzed interval.""" """Returns a quanitzed interval."""
# Convert seconds to timedelta, if appropriate. # Convert seconds to timedelta, if appropriate.
duration = _seconds_or_timedelta(duration) duration = seconds_or_timedelta(duration)
timezone = pytz.timezone(timezone) timezone = pytz.timezone(timezone)
if duration <= timedelta(seconds=0): if duration <= timedelta(seconds=0):
raise ValueError('cannot quantize by non-positive timedelta') raise ValueError('cannot quantize by non-positive timedelta')
epoch = timezone.localize(Datetime(1970, 1, 1)) epoch = timezone.localize(Datetime(1970, 1, 1))
seconds = int(duration.total_seconds()) seconds = int(duration.total_seconds())
start_seconds = int(
(self.start.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())
end_seconds = int(
(self.end.datetime(naive=False) - epoch).total_seconds()
)
if start_seconds % seconds and not snap_out: if start_seconds % seconds and not snap_out:
start_seconds += seconds start_seconds += seconds
if end_seconds % seconds and snap_out: if end_seconds % seconds and snap_out:
end_seconds += seconds end_seconds += seconds
start_seconds -= start_seconds % seconds start_seconds -= start_seconds % seconds
end_seconds -= end_seconds % seconds end_seconds -= end_seconds % seconds
if start_seconds > end_seconds: if start_seconds > end_seconds:
start_seconds = end_seconds start_seconds = end_seconds
return MayaInterval( return MayaInterval(
start=MayaDT.from_datetime(epoch).add(seconds=start_seconds), start=MayaDT.from_datetime(epoch).add(seconds=start_seconds),
end=MayaDT.from_datetime(epoch).add(seconds=end_seconds), end=MayaDT.from_datetime(epoch).add(seconds=end_seconds),
@@ -603,17 +497,23 @@ class MayaInterval(object):
@validate_arguments_type_of_function() @validate_arguments_type_of_function()
def intersection(self, maya_interval): def intersection(self, maya_interval):
"""Returns the intersection between two intervals.""" """Returns the intersection between two intervals."""
start = max(self.start, maya_interval.start) start = max(self.start, maya_interval.start)
end = min(self.end, maya_interval.end) end = min(self.end, maya_interval.end)
either_instant = self.is_instant or maya_interval.is_instant either_instant = self.is_instant or maya_interval.is_instant
instant_overlap = (self.start == maya_interval.start or start <= end) instant_overlap = (
self.start == maya_interval.start or
start <= end
)
if (either_instant and instant_overlap) or (start < end): if (either_instant and instant_overlap) or (start < end):
return MayaInterval(start, end) return MayaInterval(start, end)
@validate_arguments_type_of_function() @validate_arguments_type_of_function()
def contains(self, maya_interval): def contains(self, maya_interval):
return ( return (
self.start <= maya_interval.start and self.end >= maya_interval.end self.start <= maya_interval.start and
self.end >= maya_interval.end
) )
def __contains__(self, maya_dt): def __contains__(self, maya_dt):
@@ -628,7 +528,8 @@ class MayaInterval(object):
@validate_arguments_type_of_function() @validate_arguments_type_of_function()
def is_adjacent(self, maya_interval): def is_adjacent(self, maya_interval):
return ( return (
self.start == maya_interval.end or self.end == maya_interval.start self.start == maya_interval.end or
self.end == maya_interval.start
) )
@property @property
@@ -645,28 +546,14 @@ class MayaInterval(object):
""".format( """.format(
self.start.datetime().strftime(ical_dt_format), self.start.datetime().strftime(ical_dt_format),
self.end.datetime().strftime(ical_dt_format), self.end.datetime().strftime(ical_dt_format),
).replace( ).replace(' ', '').strip('\r\n').replace('\n', '\r\n')
' ', ''
).strip(
'\r\n'
).replace(
'\n', '\r\n'
)
@staticmethod @staticmethod
def flatten(interval_list): def flatten(interval_list):
return functools.reduce( return functools.reduce(lambda reduced, maya_interval: (
lambda reduced, (reduced[:-1] + maya_interval.combine(reduced[-1]))
maya_interval: ( if reduced else [maya_interval]
( ), sorted(interval_list), [])
reduced[:-1] + maya_interval.combine(reduced[-1])
) if reduced else [
maya_interval
]
),
sorted(interval_list),
[],
)
@classmethod @classmethod
def from_datetime(cls, start_dt=None, end_dt=None, duration=None): def from_datetime(cls, start_dt=None, end_dt=None, duration=None):
@@ -681,7 +568,7 @@ def now():
return MayaDT(epoch=epoch) return MayaDT(epoch=epoch)
def when(string, timezone='UTC', prefer_past=False): def when(string, timezone='UTC'):
""""Returns a MayaDT instance for the human moment specified. """"Returns a MayaDT instance for the human moment specified.
Powered by dateparser. Useful for scraping websites. Powered by dateparser. Useful for scraping websites.
@@ -692,49 +579,31 @@ def when(string, timezone='UTC', prefer_past=False):
Keyword Arguments: Keyword Arguments:
string -- string to be parsed string -- string to be parsed
timezone -- timezone referenced from (default: 'UTC') timezone -- timezone referenced from (default: 'UTC')
prefer_past -- prefer parsing ambiguous date as in the past
""" """
settings = { dt = dateparser.parse(string,
'TIMEZONE': timezone, settings={'TIMEZONE': timezone, 'RETURN_AS_TIMEZONE_AWARE': True, 'TO_TIMEZONE': 'UTC'})
'RETURN_AS_TIMEZONE_AWARE': True,
'TO_TIMEZONE': 'UTC',
}
if prefer_past:
settings['PREFER_DATES_FROM'] = 'past'
dt = dateparser.parse(string, settings=settings)
if dt is None: if dt is None:
raise ValueError('invalid datetime input specified.') raise ValueError('invalid datetime input specified.')
return MayaDT.from_datetime(dt) return MayaDT.from_datetime(dt)
def parse(string, timezone='UTC', day_first=False, year_first=True): def parse(string, day_first=False):
""""Returns a MayaDT instance for the machine-produced moment specified. """"Returns a MayaDT instance for the machine-produced moment specified.
Powered by pendulum. Powered by pendulum. Accepts most known formats. Useful for working with data.
Accepts most known formats. Useful for working with data.
Keyword Arguments: Keyword Arguments:
string -- string to be parsed string -- string to be parsed
timezone -- timezone referenced from (default: 'UTC') day_first -- if true, the first value (e.g. 01/05/2016) is parsed as day (default: False)
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)
""" """
options = {} dt = pendulum.parse(string, day_first=day_first)
options['tz'] = timezone
options['day_first'] = day_first
options['year_first'] = year_first
dt = pendulum.parse(str(string), **options)
return MayaDT.from_datetime(dt) return MayaDT.from_datetime(dt)
def _seconds_or_timedelta(duration): def seconds_or_timedelta(duration):
"""Returns `datetime.timedelta` object for the passed duration. """Returns `datetime.timedelta` object for the passed duration.
Keyword Arguments: Keyword Arguments:
@@ -745,22 +614,17 @@ def _seconds_or_timedelta(duration):
elif isinstance(duration, timedelta): elif isinstance(duration, timedelta):
dt_timedelta = duration dt_timedelta = duration
else: else:
raise TypeError( raise TypeError('Expects argument as `datetime.timedelta` object '
'Expects argument as `datetime.timedelta` object ' 'or seconds in `int` format')
'or seconds in `int` format'
)
return dt_timedelta return dt_timedelta
def intervals(start, end, interval): 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)
"""
interval = _seconds_or_timedelta(interval)
current_timestamp = start current_timestamp = start
while current_timestamp.epoch < end.epoch: while current_timestamp.epoch < end.epoch:
yield current_timestamp yield current_timestamp
current_timestamp = current_timestamp.add(seconds=interval.seconds) current_timestamp = current_timestamp.add(seconds=interval.seconds)
+10 -47
View File
@@ -1,16 +1,20 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import io
import os import os
import sys import sys
import codecs import codecs
from shutil import rmtree
from setuptools import find_packages, setup, Command from setuptools import setup
try:
# Python 3
from os import dirname
except ImportError:
# Python 2
from os.path import dirname
here = os.path.abspath(os.path.dirname(__file__)) here = os.path.abspath(dirname(__file__))
with codecs.open(os.path.join(here, 'README.rst'), encoding='utf-8') as f: with codecs.open(os.path.join(here, 'README.rst'), encoding='utf-8') as f:
long_description = '\n' + f.read() long_description = '\n' + f.read()
@@ -26,54 +30,16 @@ required = [
'dateparser', 'dateparser',
'ruamel.yaml', 'ruamel.yaml',
'tzlocal', 'tzlocal',
'pendulum', 'pendulum'
'snaptime'
] ]
packages = [ packages = [
'maya', '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 to store version and package info
about = dict() about = dict()
with codecs.open(os.path.join(here, 'maya', '__version__.py'), 'r', encoding='utf-8') as f: with open(os.path.join(here, 'maya', '__version__.py'), 'r', 'utf-8') as f:
exec(f.read(), about) exec(f.read(), about)
setup( setup(
@@ -101,7 +67,4 @@ setup(
'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: CPython',
'Topic :: Software Development :: Libraries :: Python Modules' 'Topic :: Software Development :: Libraries :: Python Modules'
), ),
cmdclass={
'upload': UploadCommand,
},
) )
+38 -94
View File
@@ -1,12 +1,8 @@
import copy
import time
from datetime import timedelta, datetime as Datetime
import pytz
import pytest import pytest
import copy
from datetime import timedelta
import maya import maya
from maya.core import _seconds_or_timedelta # import private function
def test_rfc2822(): def test_rfc2822():
@@ -27,68 +23,57 @@ def test_parse_iso8601():
string = '20161001T1430.4+05:30' string = '20161001T1430.4+05:30'
expected = '2016-10-01T09:00:00.400000Z' expected = '2016-10-01T09:00:00.400000Z'
d = maya.MayaDT.from_iso8601(string) d = maya.MayaDT.from_iso8601(string)
assert expected == d.iso8601() assert expected == d.iso8601()
string = '2016T14' string = '2016T14'
expected = '2016-01-01T14:00:00Z' expected = '2016-01-01T14:00:00Z'
d = maya.MayaDT.from_iso8601(string) d = maya.MayaDT.from_iso8601(string)
assert expected == d.iso8601() assert expected == d.iso8601()
string = '2016-10T14' string = '2016-10T14'
expected = '2016-10-01T14:00:00Z' expected = '2016-10-01T14:00:00Z'
d = maya.MayaDT.from_iso8601(string) d = maya.MayaDT.from_iso8601(string)
assert expected == d.iso8601() assert expected == d.iso8601()
string = '2012W05' string = '2012W05'
expected = '2012-01-30T00:00:00Z' expected = '2012-01-30T00:00:00Z'
d = maya.MayaDT.from_iso8601(string) d = maya.MayaDT.from_iso8601(string)
assert expected == d.iso8601() assert expected == d.iso8601()
string = '2012W055' string = '2012W055'
expected = '2012-02-03T00:00:00Z' expected = '2012-02-03T00:00:00Z'
d = maya.MayaDT.from_iso8601(string) d = maya.MayaDT.from_iso8601(string)
assert expected == d.iso8601() assert expected == d.iso8601()
string = '2012007' string = '2012007'
expected = '2012-01-07T00:00:00Z' expected = '2012-01-07T00:00:00Z'
d = maya.MayaDT.from_iso8601(string) d = maya.MayaDT.from_iso8601(string)
assert expected == d.iso8601() assert expected == d.iso8601()
string = '2016-W07T09' string = '2016-W07T09'
expected = '2016-02-15T09:00:00Z' expected = '2016-02-15T09:00:00Z'
d = maya.MayaDT.from_iso8601(string) d = maya.MayaDT.from_iso8601(string)
assert expected == d.iso8601() assert expected == d.iso8601()
def test_struct():
now = round(time.time())
ts = time.gmtime(now)
m = maya.MayaDT.from_struct(ts)
dt = Datetime.fromtimestamp(now, pytz.UTC)
assert m._epoch is not None
assert m.datetime() == dt
ts = time.localtime(now)
m = maya.MayaDT.from_struct(ts)
dt = Datetime.fromtimestamp(
time.mktime(ts) - maya.core.utc_offset(), pytz.UTC
)
assert m._epoch is not 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(): def test_human_when():
r1 = maya.when('yesterday') r1 = maya.when('yesterday')
r2 = maya.when('today') r2 = maya.when('today')
assert (r2.day - r1.day) in (1, -30, -29, -28, -27) assert (r2.day - r1.day) in (1, -30, -29, -28, -27)
def test_machine_parse(): def test_machine_parse():
r1 = maya.parse('August 14, 2015') r1 = maya.parse('August 14, 2015')
assert r1.day == 14 assert r1.day == 14
r2 = maya.parse('August 15, 2015') r2 = maya.parse('August 15, 2015')
assert r2.day == 15 assert r2.day == 15
@@ -102,12 +87,14 @@ def test_dt_tz_translation():
def test_dt_tz_naive(): def test_dt_tz_naive():
d1 = maya.now().datetime(naive=True) d1 = maya.now().datetime(naive=True)
assert d1.tzinfo is None assert d1.tzinfo is None
d2 = maya.now().datetime(to_timezone='EST', naive=True) d2 = maya.now().datetime(to_timezone='EST', naive=True)
assert d2.tzinfo is None assert d2.tzinfo is None
assert (d1.hour - d2.hour) % 24 == 5 assert (d1.hour - d2.hour) % 24 == 5
def test_random_date(): def test_random_date():
# Test properties for maya.when() # Test properties for maya.when()
d1 = maya.when('11-17-11 08:09:10') d1 = maya.when('11-17-11 08:09:10')
assert d1.year == 2011 assert d1.year == 2011
@@ -119,6 +106,7 @@ def test_random_date():
assert d1.minute == 9 assert d1.minute == 9
assert d1.second == 10 assert d1.second == 10
assert d1.microsecond == 0 assert d1.microsecond == 0
# Test properties for maya.parse() # Test properties for maya.parse()
d2 = maya.parse('February 29, 1992 13:12:34') d2 = maya.parse('February 29, 1992 13:12:34')
assert d2.year == 1992 assert d2.year == 1992
@@ -134,8 +122,10 @@ def test_random_date():
def test_print_date(capsys): def test_print_date(capsys):
d = maya.when('11-17-11') d = maya.when('11-17-11')
print(d) print(d)
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert out == 'Thu, 17 Nov 2011 00:00:00 GMT\n' assert out == 'Thu, 17 Nov 2011 00:00:00 GMT\n'
assert repr(d) == '<MayaDT epoch=1321488000.0>' assert repr(d) == '<MayaDT epoch=1321488000.0>'
@@ -151,47 +141,27 @@ def test_slang_date():
def test_slang_time(): def test_slang_time():
d = maya.when('1 hour ago') d = maya.when('one hour ago')
assert d.slang_time() == 'an hour ago' assert d.slang_time() == 'an hour ago'
def test_parse(): def test_parse():
d = maya.parse('February 21, 1994') d = maya.parse('February 21, 1994')
assert format(d) == '1994-02-21 00:00:00+00:00' assert format(d) == '1994-02-21 00:00:00+00:00'
d = maya.parse('01/05/2016') d = maya.parse('01/05/2016')
assert format(d) == '2016-01-05 00:00:00+00:00' assert format(d) == '2016-01-05 00:00:00+00:00'
d = maya.parse('01/05/2016', day_first=True) d = maya.parse('01/05/2016', day_first=True)
assert format(d) == '2016-05-01 00:00:00+00:00' assert format(d) == '2016-05-01 00:00:00+00:00'
d = maya.parse('2016/05/01', year_first=True, day_first=False)
assert format(d) == '2016-05-01 00:00:00+00:00'
d = maya.parse('2016/01/05', year_first=True, day_first=True)
assert format(d) == '2016-05-01 00:00:00+00:00'
d = maya.parse('01/05/2016', timezone='UTC')
assert format(d) == '2016-01-05 00:00:00+00:00'
d = maya.parse('01/05/2016', timezone='US/Central')
assert format(d) == '2016-01-05 06:00:00+00:00'
def test_when_past():
next_month = str(maya.now().add(months=1).month)
this_year = maya.now().year
last_year = this_year - 1
future_date = maya.when(next_month)
past_date = maya.when(next_month, prefer_past=True)
assert future_date.year == this_year
if next_month == '1':
assert past_date.year == this_year
else:
assert past_date.year == last_year
def test_datetime_to_timezone(): def test_datetime_to_timezone():
dt = maya.when('2016-01-01').datetime(to_timezone='US/Eastern') dt = maya.when('2016-01-01').datetime(to_timezone='US/Eastern')
assert dt.tzinfo.zone == 'US/Eastern' assert dt.tzinfo.zone == 'US/Eastern'
def test_rfc3339(): def test_rfc3339():
mdt = maya.when('2016-01-01') mdt = maya.when('2016-01-01')
out = mdt.rfc3339() out = mdt.rfc3339()
mdt2 = maya.MayaDT.from_rfc3339(out) mdt2 = maya.MayaDT.from_rfc3339(out)
assert mdt.epoch == mdt2.epoch assert mdt.epoch == mdt2.epoch
@@ -201,18 +171,25 @@ def test_comparison_operations():
now = maya.now() now = maya.now()
now_copy = copy.deepcopy(now) now_copy = copy.deepcopy(now)
tomorrow = maya.when('tomorrow') tomorrow = maya.when('tomorrow')
assert (now == now_copy) is True assert (now == now_copy) is True
assert (now == tomorrow) is False assert (now == tomorrow) is False
assert (now != now_copy) is False assert (now != now_copy) is False
assert (now != tomorrow) is True assert (now != tomorrow) is True
assert (now < now_copy) is False assert (now < now_copy) is False
assert (now < tomorrow) is True assert (now < tomorrow) is True
assert (now <= now_copy) is True assert (now <= now_copy) is True
assert (now <= tomorrow) is True assert (now <= tomorrow) is True
assert (now > now_copy) is False assert (now > now_copy) is False
assert (now > tomorrow) is False assert (now > tomorrow) is False
assert (now >= now_copy) is True assert (now >= now_copy) is True
assert (now >= tomorrow) is False assert (now >= tomorrow) is False
# Check Exceptions # Check Exceptions
with pytest.raises(TypeError): with pytest.raises(TypeError):
now == 1 now == 1
@@ -230,18 +207,19 @@ def test_comparison_operations():
def test_seconds_or_timedelta(): def test_seconds_or_timedelta():
# test for value in seconds # test for value in seconds
assert _seconds_or_timedelta(1234) == timedelta(0, 1234) assert maya.seconds_or_timedelta(1234) == timedelta(0, 1234)
# test for value as `datetime.timedelta` # test for value as `datetime.timedelta`
assert _seconds_or_timedelta(timedelta(0, 1234)) == timedelta(0, 1234) assert maya.seconds_or_timedelta(timedelta(0, 1234)) == timedelta(0, 1234)
# test for invalid value # test for invalid value
with pytest.raises(TypeError): with pytest.raises(TypeError):
_seconds_or_timedelta('invalid interval') maya.seconds_or_timedelta('invalid interval')
def test_intervals(): def test_intervals():
now = maya.now() now = maya.now()
tomorrow = now.add(days=1) tomorrow = now.add(days=1)
assert len(list(maya.intervals(now, tomorrow, 60 * 60))) == 24
assert len(list(maya.intervals(now, tomorrow, 60*60))) == 24
def test_dunder_add(): def test_dunder_add():
@@ -260,37 +238,3 @@ def test_dunder_sub():
now = maya.now() now = maya.now()
assert now - 1 == now.subtract(seconds=1) assert now - 1 == now.subtract(seconds=1)
assert now - timedelta(seconds=1) == now.subtract(seconds=1) assert now - timedelta(seconds=1) == now.subtract(seconds=1)
def test_mayaDT_sub():
now = maya.now()
then = now.add(days=1)
assert then - now == timedelta(24 * 60 * 60)
assert now - then == timedelta(-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_snaptime():
# given
dt = maya.when('Mon, 21 Feb 1994 21:21:42 GMT')
# when
dt = dt.snap('@d')
# then
assert dt == maya.when('Mon, 21 Feb 1994 00:00:00 GMT')
+214 -202
View File
@@ -15,20 +15,26 @@ Melbourne = pytz.timezone('Australia/Melbourne')
def test_interval_requires_2_of_start_end_duration(): def test_interval_requires_2_of_start_end_duration():
start = maya.now() start = maya.now()
end = start.add(hours=1) end = start.add(hours=1)
with pytest.raises(ValueError): with pytest.raises(ValueError):
maya.MayaInterval(start=start) maya.MayaInterval(start=start)
with pytest.raises(ValueError): with pytest.raises(ValueError):
maya.MayaInterval(end=end) maya.MayaInterval(end=end)
with pytest.raises(ValueError): with pytest.raises(ValueError):
maya.MayaInterval(duration=60) maya.MayaInterval(duration=60)
with pytest.raises(ValueError): with pytest.raises(ValueError):
maya.MayaInterval(start=start, end=end, duration=60) maya.MayaInterval(start=start, end=end, duration=60)
maya.MayaInterval(start=start, end=end) maya.MayaInterval(start=start, end=end)
maya.MayaInterval(start=start, duration=60) maya.MayaInterval(start=start, duration=60)
maya.MayaInterval(end=end, duration=60) maya.MayaInterval(end=end, duration=60)
def test_interval_requires_end_time_after_or_on_start_time(): def test_interval_requires_end_time_after_or_on_start_time():
with pytest.raises(ValueError): with pytest.raises(ValueError):
maya.MayaInterval(start=maya.now(), duration=0) maya.MayaInterval(start=maya.now(), duration=0)
maya.MayaInterval(start=maya.now(), duration=-1) maya.MayaInterval(start=maya.now(), duration=-1)
@@ -58,9 +64,7 @@ def test_interval_init_end_duration():
assert interval.start == end.subtract(seconds=duration) assert interval.start == end.subtract(seconds=duration)
@pytest.mark.parametrize( @pytest.mark.parametrize('start_doy1,end_doy1,start_doy2,end_doy2,intersection_doys', (
'start_doy1,end_doy1,start_doy2,end_doy2,intersection_doys',
(
(0, 2, 1, 3, (1, 2)), (0, 2, 1, 3, (1, 2)),
(0, 2, 3, 4, None), (0, 2, 3, 4, None),
(0, 2, 2, 3, None), (0, 2, 2, 3, None),
@@ -72,8 +76,7 @@ def test_interval_init_end_duration():
(1, 3, 1, 1, (1, 1)), (1, 3, 1, 1, (1, 1)),
(2, 3, 1, 1, None), (2, 3, 1, 1, None),
(1, 3, 2, 2, (2, 2)), (1, 3, 2, 2, (2, 2)),
), ), ids=(
ids=(
'overlapping', 'overlapping',
'non-overlapping', 'non-overlapping',
'adjacent', 'adjacent',
@@ -84,19 +87,21 @@ def test_interval_init_end_duration():
'instant overlapping', 'instant overlapping',
'instant overlapping start only (left)', 'instant overlapping start only (left)',
'instant disjoint (left)', 'instant disjoint (left)',
'instant overlapping (left)', 'instant overlapping (left)'
), ))
)
def test_interval_intersection( def test_interval_intersection(
start_doy1, end_doy1, start_doy2, end_doy2, intersection_doys start_doy1, end_doy1, start_doy2, end_doy2, intersection_doys
): ):
base = maya.MayaDT.from_datetime(datetime(2016, 1, 1)) base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
interval1 = maya.MayaInterval( interval1 = maya.MayaInterval(
base.add(days=start_doy1), base.add(days=end_doy1) base.add(days=start_doy1),
base.add(days=end_doy1),
) )
interval2 = maya.MayaInterval( 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: if intersection_doys:
start_doy_intersection, end_doy_intersection = intersection_doys start_doy_intersection, end_doy_intersection = intersection_doys
assert interval1 & interval2 == maya.MayaInterval( assert interval1 & interval2 == maya.MayaInterval(
@@ -105,6 +110,7 @@ def test_interval_intersection(
) )
else: else:
assert (interval1 & interval2) is None assert (interval1 & interval2) is None
# check invalid argument # check invalid argument
with pytest.raises(TypeError): with pytest.raises(TypeError):
interval1 & 'invalid type' interval1 & 'invalid type'
@@ -113,10 +119,13 @@ def test_interval_intersection(
def test_interval_intersects(): def test_interval_intersects():
base = maya.MayaDT.from_datetime(datetime(2016, 1, 1)) base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
interval = maya.MayaInterval(base, base.add(days=1)) interval = maya.MayaInterval(base, base.add(days=1))
assert interval.intersects(interval) assert interval.intersects(interval)
assert not interval.intersects( assert not interval.intersects(maya.MayaInterval(
maya.MayaInterval(base.add(days=2), base.add(days=3)) base.add(days=2),
) base.add(days=3),
))
# check invalid argument # check invalid argument
with pytest.raises(TypeError): with pytest.raises(TypeError):
interval.intersects('invalid type') interval.intersects('invalid type')
@@ -126,11 +135,13 @@ def test_and_operator():
base = maya.MayaDT.from_datetime(datetime(2016, 1, 1)) base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
interval1 = maya.MayaInterval(base, base.add(days=2)) interval1 = maya.MayaInterval(base, base.add(days=2))
interval2 = maya.MayaInterval(base.add(days=1), base.add(days=3)) interval2 = maya.MayaInterval(base.add(days=1), base.add(days=3))
assert ( assert (
interval1 & interval2 == interval1 & interval2 ==
interval2 & interval1 == interval2 & interval1 ==
interval1.intersection(interval2) interval1.intersection(interval2)
) )
# check invalid argument # check invalid argument
with pytest.raises(TypeError): with pytest.raises(TypeError):
interval1.intersection('invalid type') interval1.intersection('invalid type')
@@ -140,11 +151,14 @@ def test_interval_eq_operator():
start = maya.now() start = maya.now()
end = start.add(hours=1) end = start.add(hours=1)
interval = maya.MayaInterval(start=start, end=end) interval = maya.MayaInterval(start=start, end=end)
assert 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)) assert interval != maya.MayaInterval(start=start, end=end.add(days=1))
# check invalid argument # check invalid argument
with pytest.raises(TypeError): with pytest.raises(TypeError):
interval == 'invalid type' interval == 'invalid type'
with pytest.raises(TypeError): with pytest.raises(TypeError):
interval != 'invalid type' interval != 'invalid type'
@@ -153,6 +167,7 @@ def test_interval_timedelta():
start = maya.now() start = maya.now()
delta = timedelta(hours=1) delta = timedelta(hours=1)
interval = maya.MayaInterval(start=start, duration=delta) interval = maya.MayaInterval(start=start, duration=delta)
assert interval.timedelta == delta assert interval.timedelta == delta
@@ -160,55 +175,69 @@ def test_interval_duration():
start = maya.now() start = maya.now()
delta = timedelta(hours=1) delta = timedelta(hours=1)
interval = maya.MayaInterval(start=start, duration=delta) interval = maya.MayaInterval(start=start, duration=delta)
assert interval.duration == delta.total_seconds() assert interval.duration == delta.total_seconds()
@pytest.mark.parametrize( @pytest.mark.parametrize('start_doy1,end_doy1,start_doy2,end_doy2,expected', (
'start_doy1,end_doy1,start_doy2,end_doy2,expected',
(
(0, 2, 1, 3, False), (0, 2, 1, 3, False),
(0, 2, 3, 4, False), (0, 2, 3, 4, False),
(0, 2, 2, 3, False), (0, 2, 2, 3, False),
(0, 1, 0, 1, True), (0, 1, 0, 1, True),
(0, 3, 1, 2, True), (0, 3, 1, 2, True),
), ), ids=(
ids=('overlapping', 'non-overlapping', 'adjacent', 'equal', 'subset'), 'overlapping',
) 'non-overlapping',
'adjacent',
'equal',
'subset',
))
def test_interval_contains( def test_interval_contains(
start_doy1, end_doy1, start_doy2, end_doy2, expected start_doy1, end_doy1, start_doy2, end_doy2, expected
): ):
base = maya.MayaDT.from_datetime(datetime(2016, 1, 1)) base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
interval1 = maya.MayaInterval( interval1 = maya.MayaInterval(
base.add(days=start_doy1), base.add(days=end_doy1) base.add(days=start_doy1),
base.add(days=end_doy1),
) )
interval2 = maya.MayaInterval( 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 interval1.contains(interval2) is expected
assert (interval2 in interval1) is expected assert (interval2 in interval1) is expected
# check invalid argument # check invalid argument
with pytest.raises(TypeError): with pytest.raises(TypeError):
interval1.contains('invalid type') interval1.contains('invalid type')
@pytest.mark.parametrize( @pytest.mark.parametrize('start_doy,end_doy,dt_doy,expected', (
'start_doy,end_doy,dt_doy,expected',
(
(2, 4, 1, False), (2, 4, 1, False),
(2, 4, 2, True), (2, 4, 2, True),
(2, 4, 3, True), (2, 4, 3, True),
(2, 4, 4, False), (2, 4, 4, False),
(2, 4, 5, False), (2, 4, 5, False),
), ), ids=(
ids=('before-start', 'on-start', 'during', 'on-end', 'after-end'), 'before-start',
) 'on-start',
def test_interval_in_operator_maya_dt(start_doy, end_doy, dt_doy, expected): '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)) base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
interval = maya.MayaInterval( 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) dt = base.add(days=dt_doy)
assert (dt in interval) is expected assert (dt in interval) is expected
# check invalid argument # check invalid argument
with pytest.raises(TypeError): with pytest.raises(TypeError):
'invalid type' in interval 'invalid type' in interval
@@ -218,84 +247,91 @@ def test_interval_hash():
start = maya.now() start = maya.now()
end = start.add(hours=1) end = start.add(hours=1)
interval = maya.MayaInterval(start=start, end=end) interval = maya.MayaInterval(start=start, end=end)
assert hash(interval) == hash(maya.MayaInterval(start=start, end=end)) assert hash(interval) == hash(maya.MayaInterval(start=start, end=end))
assert hash(interval) != hash( assert hash(interval) != hash(maya.MayaInterval(
maya.MayaInterval(start=start, end=end.add(days=1)) start=start, end=end.add(days=1)))
)
def test_interval_iter(): def test_interval_iter():
start = maya.now() start = maya.now()
end = start.add(days=1) end = start.add(days=1)
assert tuple(maya.MayaInterval(start=start, end=end)) == (start, end) assert tuple(maya.MayaInterval(start=start, end=end)) == (start, end)
@pytest.mark.parametrize( @pytest.mark.parametrize('start1,end1,start2,end2,expected', [
'start1,end1,start2,end2,expected', (1, 2, 1, 2, 0),
[(1, 2, 1, 2, 0), (1, 3, 2, 4, -1), (2, 4, 1, 3, 1), (1, 2, 1, 3, -1)], (1, 3, 2, 4, -1),
ids=( (2, 4, 1, 3, 1),
(1, 2, 1, 3, -1),
], ids=(
'equal', 'equal',
'less-than', 'less-than',
'greater-than', 'greater-than',
'use-end-time-if-start-time-identical', 'use-end-time-if-start-time-identical',
), ))
)
def test_interval_cmp(start1, end1, start2, end2, expected): def test_interval_cmp(start1, end1, start2, end2, expected):
base = maya.now() base = maya.now()
interval1 = maya.MayaInterval( interval1 = maya.MayaInterval(
start=base.add(days=start1), end=base.add(days=end1) start=base.add(days=start1),
end=base.add(days=end1),
) )
interval2 = maya.MayaInterval( 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 assert cmp(interval1, interval2) == expected
# check invalid argument # check invalid argument
with pytest.raises(TypeError): with pytest.raises(TypeError):
cmp(interval1, 'invalid type') cmp(interval1, 'invalid type')
@pytest.mark.parametrize( @pytest.mark.parametrize('start1,end1,start2,end2,expected', [
'start1,end1,start2,end2,expected', (1, 2, 2, 3, [(1, 3)]),
[ (1, 3, 2, 4, [(1, 4)]),
(1, 2, 2, 3, [(1, 3)]), (1, 2, 3, 4, [(1, 2), (3, 4)]),
(1, 3, 2, 4, [(1, 4)]), (1, 5, 2, 3, [(1, 5)]),
(1, 2, 3, 4, [(1, 2), (3, 4)]), ], ids=(
(1, 5, 2, 3, [(1, 5)]), 'adjacent',
], 'overlapping',
ids=('adjacent', 'overlapping', 'non-overlapping', 'contains'), 'non-overlapping',
) 'contains',
))
def test_interval_combine(start1, end1, start2, end2, expected): def test_interval_combine(start1, end1, start2, end2, expected):
base = maya.now() base = maya.now()
interval1 = maya.MayaInterval( interval1 = maya.MayaInterval(
start=base.add(days=start1), end=base.add(days=end1) start=base.add(days=start1),
end=base.add(days=end1),
) )
interval2 = maya.MayaInterval( 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 = [ expected_intervals = [maya.MayaInterval(
maya.MayaInterval(start=base.add(days=start), end=base.add(days=end)) start=base.add(days=start),
for start, end in expected end=base.add(days=end),
] ) for start, end in expected]
assert interval1.combine(interval2) == expected_intervals assert interval1.combine(interval2) == expected_intervals
assert interval2.combine(interval1) == expected_intervals assert interval2.combine(interval1) == expected_intervals
# check invalid argument # check invalid argument
with pytest.raises(TypeError): with pytest.raises(TypeError):
interval2.combine('invalid type') interval2.combine('invalid type')
@pytest.mark.parametrize( @pytest.mark.parametrize('start1,end1,start2,end2,expected', [
'start1,end1,start2,end2,expected', (1, 2, 3, 4, [(1, 2)]),
[ (1, 2, 2, 4, [(1, 2)]),
(1, 2, 3, 4, [(1, 2)]), (2, 3, 1, 4, []),
(1, 2, 2, 4, [(1, 2)]), (1, 4, 2, 3, [(1, 2), (3, 4)]),
(2, 3, 1, 4, []), (1, 4, 0, 2, [(2, 4)]),
(1, 4, 2, 3, [(1, 2), (3, 4)]), (1, 4, 3, 5, [(1, 3)]),
(1, 4, 0, 2, [(2, 4)]), (1, 4, 1, 2, [(2, 4)]),
(1, 4, 3, 5, [(1, 3)]), (1, 4, 3, 4, [(1, 3)]),
(1, 4, 1, 2, [(2, 4)]), ], ids=(
(1, 4, 3, 4, [(1, 3)]),
],
ids=(
'non-overlapping', 'non-overlapping',
'adjacent', 'adjacent',
'contains', 'contains',
@@ -304,109 +340,115 @@ def test_interval_combine(start1, end1, start2, end2, expected):
'overlaps-right', 'overlaps-right',
'overlaps-left-identical-start', 'overlaps-left-identical-start',
'overlaps-right-identical-end', 'overlaps-right-identical-end',
), ))
)
def test_interval_subtract(start1, end1, start2, end2, expected): def test_interval_subtract(start1, end1, start2, end2, expected):
base = maya.now() base = maya.now()
interval1 = maya.MayaInterval( interval1 = maya.MayaInterval(
start=base.add(days=start1), end=base.add(days=end1) start=base.add(days=start1),
end=base.add(days=end1),
) )
interval2 = maya.MayaInterval( 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 = [ expected_intervals = [maya.MayaInterval(
maya.MayaInterval(start=base.add(days=start), end=base.add(days=end)) start=base.add(days=start),
for start, end in expected end=base.add(days=end),
] ) for start, end in expected]
assert interval1.subtract(interval2) == expected_intervals assert interval1.subtract(interval2) == expected_intervals
# check invalid argument # check invalid argument
with pytest.raises(TypeError): with pytest.raises(TypeError):
interval1.subtract('invalid type') interval1.subtract('invalid type')
@pytest.mark.parametrize( @pytest.mark.parametrize('start1,end1,start2,end2,expected', [
'start1,end1,start2,end2,expected', (1, 2, 2, 3, True),
[ (2, 3, 1, 2, True),
(1, 2, 2, 3, True), (1, 3, 2, 3, False),
(2, 3, 1, 2, True), (2, 3, 4, 5, False),
(1, 3, 2, 3, False), ], ids=(
(2, 3, 4, 5, False), 'adjacent-right',
], 'adjacent-left',
ids=('adjacent-right', 'adjacent-left', 'overlapping', 'non-overlapping'), 'overlapping',
) 'non-overlapping',
))
def test_interval_is_adjacent(start1, end1, start2, end2, expected): def test_interval_is_adjacent(start1, end1, start2, end2, expected):
base = maya.now() base = maya.now()
interval1 = maya.MayaInterval( interval1 = maya.MayaInterval(
start=base.add(days=start1), end=base.add(days=end1) start=base.add(days=start1),
end=base.add(days=end1),
) )
interval2 = maya.MayaInterval( 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 assert interval1.is_adjacent(interval2) == expected
# check invalid argument # check invalid argument
with pytest.raises(TypeError): with pytest.raises(TypeError):
interval1.is_adjacent('invalid type') interval1.is_adjacent('invalid type')
@pytest.mark.parametrize( @pytest.mark.parametrize('start,end,delta,include_remainder,expected', [
'start,end,delta,include_remainder,expected', (0, 10, 5, False, [(0, 5), (5, 10)]),
[ (0, 10, 5, True, [(0, 5), (5, 10)]),
(0, 10, 5, False, [(0, 5), (5, 10)]), (0, 10, 3, False, [(0, 3), (3, 6), (6, 9)]),
(0, 10, 5, True, [(0, 5), (5, 10)]), (0, 10, 3, True, [(0, 3), (3, 6), (6, 9), (9, 10)]),
(0, 10, 3, False, [(0, 3), (3, 6), (6, 9)]), (0, 2, 5, False, []),
(0, 10, 3, True, [(0, 3), (3, 6), (6, 9), (9, 10)]), (0, 2, 5, True, [(0, 2)]),
(0, 2, 5, False, []), ], ids=(
(0, 2, 5, True, [(0, 2)]),
],
ids=(
'even-split', 'even-split',
'even-split-include-partial', 'even-split-include-partial',
'uneven-split-do-not-include-partial', 'uneven-split-do-not-include-partial',
'uneven-split-include-partial', 'uneven-split-include-partial',
'delta-larger-than-timepsan-do-not-include-partial', 'delta-larger-than-timepsan-do-not-include-partial',
'delta-larger-than-timepsan-include-partial', 'delta-larger-than-timepsan-include-partial',
), ))
)
def test_interval_split(start, end, delta, include_remainder, expected): def test_interval_split(start, end, delta, include_remainder, expected):
base = maya.now() base = maya.now()
interval = maya.MayaInterval( interval = maya.MayaInterval(
start=base.add(days=start), end=base.add(days=end) start=base.add(days=start),
end=base.add(days=end),
) )
delta = timedelta(days=delta) delta = timedelta(days=delta)
expected_intervals = [ expected_intervals = [
maya.MayaInterval(start=base.add(days=s), end=base.add(days=e)) maya.MayaInterval(
for s, e in expected 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(): def test_interval_split_non_positive_delta():
start = maya.now() start = maya.now()
end = start.add(days=1) end = start.add(days=1)
interval = maya.MayaInterval(start=start, end=end) interval = maya.MayaInterval(start=start, end=end)
with pytest.raises(ValueError): with pytest.raises(ValueError):
list(interval.split(timedelta(seconds=0))) list(interval.split(timedelta(seconds=0)))
with pytest.raises(ValueError): with pytest.raises(ValueError):
list(interval.split(timedelta(seconds=-10))) list(interval.split(timedelta(seconds=-10)))
@pytest.mark.parametrize( @pytest.mark.parametrize('start,end,minutes,timezone,snap_out,expected_start,expected_end', [
'start,end,minutes,timezone,snap_out,expected_start,expected_end', ((5, 12), (8, 48), 30, None, False, (5, 30), (8, 30)),
[ ((5, 12), (8, 48), 30, None, True, (5, 0), (9, 0)),
((5, 12), (8, 48), 30, None, False, (5, 30), (8, 30)), ((5, 15), (9, 0), 15, None, False, (5, 15), (9, 0)),
((5, 12), (8, 48), 30, None, True, (5, 0), (9, 0)), ((5, 15), (9, 0), 15, None, True, (5, 15), (9, 0)),
((5, 15), (9, 0), 15, None, False, (5, 15), (9, 0)), ((6, 50), (9, 15), 60, 'America/New_York', False, (7, 0), (9, 0)),
((5, 15), (9, 0), 15, None, True, (5, 15), (9, 0)), ((6, 50), (9, 15), 60, 'America/New_York', True, (6, 0), (10, 0)),
((6, 50), (9, 15), 60, 'America/New_York', False, (7, 0), (9, 0)), ((6, 20), (6, 50), 60, None, False, (6, 0), (6, 0)),
((6, 50), (9, 15), 60, 'America/New_York', True, (6, 0), (10, 0)), ((6, 20), (6, 50), 60, None, True, (6, 0), (7, 0)),
((6, 20), (6, 50), 60, None, False, (6, 0), (6, 0)), ((6, 20), (6, 50), 60, 'America/Chicago', False, (6, 0), (6, 0)),
((6, 20), (6, 50), 60, None, True, (6, 0), (7, 0)), ((6, 20), (6, 50), 60, 'America/Chicago', True, (6, 0), (7, 0)),
((6, 20), (6, 50), 60, 'America/Chicago', False, (6, 0), (6, 0)), ], ids=(
((6, 20), (6, 50), 60, 'America/Chicago', True, (6, 0), (7, 0)),
],
ids=(
'normal', 'normal',
'normal-snap_out', 'normal-snap_out',
'already-quantized', 'already-quantized',
@@ -417,20 +459,21 @@ def test_interval_split_non_positive_delta():
'too-small-snap_out', 'too-small-snap_out',
'too-small-with-timezone', 'too-small-with-timezone',
'too-small-with-timezone-snap_out', 'too-small-with-timezone-snap_out',
), ))
) def test_quantize(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)) base = maya.MayaDT.from_datetime(datetime(2017, 1, 1))
interval = maya.MayaInterval( interval = maya.MayaInterval(
start=base.add(hours=start[0], minutes=start[1]), start=base.add(hours=start[0], minutes=start[1]),
end=base.add(hours=end[0], minutes=end[1]), end=base.add(hours=end[0], minutes=end[1]),
) )
kwargs = {'timezone': timezone} if timezone is not None else {} kwargs = {'timezone': timezone} if timezone is not None else {}
quantized_interval = interval.quantize( quantized_interval = interval.quantize(
timedelta(minutes=minutes), snap_out=snap_out, **kwargs timedelta(minutes=minutes),
snap_out=snap_out,
**kwargs
) )
assert quantized_interval == maya.MayaInterval( assert quantized_interval == maya.MayaInterval(
start=base.add(hours=expected_start[0], minutes=expected_start[1]), start=base.add(hours=expected_start[0], minutes=expected_start[1]),
end=base.add(hours=expected_end[0], minutes=expected_end[1]), end=base.add(hours=expected_end[0], minutes=expected_end[1]),
@@ -441,6 +484,7 @@ def test_quantize_invalid_delta():
start = maya.now() start = maya.now()
end = start.add(days=1) end = start.add(days=1)
interval = maya.MayaInterval(start=start, end=end) interval = maya.MayaInterval(start=start, end=end)
with pytest.raises(ValueError): with pytest.raises(ValueError):
interval.quantize(timedelta(minutes=0)) interval.quantize(timedelta(minutes=0))
with pytest.raises(ValueError): with pytest.raises(ValueError):
@@ -451,13 +495,12 @@ def test_interval_flatten_non_overlapping():
step = 2 step = 2
max_hour = 20 max_hour = 20
base = maya.now() base = maya.now()
intervals = [ intervals = [maya.MayaInterval(
maya.MayaInterval( start=base.add(hours=hour),
start=base.add(hours=hour), duration=timedelta(hours=step - 1) duration=timedelta(hours=step - 1),
) ) for hour in range(0, max_hour, step)]
for hour in range(0, max_hour, step)
]
random.shuffle(intervals) random.shuffle(intervals)
assert maya.MayaInterval.flatten(intervals) == sorted(intervals) assert maya.MayaInterval.flatten(intervals) == sorted(intervals)
@@ -465,35 +508,35 @@ def test_interval_flatten_adjacent():
step = 2 step = 2
max_hour = 20 max_hour = 20
base = maya.when('jan/1/2011') base = maya.when('jan/1/2011')
intervals = [ intervals = [
maya.MayaInterval( maya.MayaInterval(
start=base.add(hours=hour), duration=timedelta(hours=step) start=base.add(hours=hour),
) duration=timedelta(hours=step),
for hour in range(0, max_hour, step) ) for hour in range(0, max_hour, step)
] ]
random.shuffle(intervals) random.shuffle(intervals)
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(): def test_interval_flatten_intersecting():
step = 2 step = 2
max_hour = 20 max_hour = 20
base = maya.now() base = maya.now()
intervals = [ intervals = [maya.MayaInterval(
maya.MayaInterval( start=base.add(hours=hour),
start=base.add(hours=hour), duration=timedelta(hours=step, minutes=30),
duration=timedelta(hours=step, minutes=30), ) for hour in range(0, max_hour, step)]
)
for hour in range(0, max_hour, step)
]
random.shuffle(intervals) random.shuffle(intervals)
assert maya.MayaInterval.flatten(intervals) == [
maya.MayaInterval( assert maya.MayaInterval.flatten(intervals) == [maya.MayaInterval(
start=base, duration=timedelta(hours=max_hour, minutes=30) start=base,
) duration=timedelta(hours=max_hour, minutes=30),
] )]
def test_interval_flatten_containing(): def test_interval_flatten_containing():
@@ -501,16 +544,16 @@ def test_interval_flatten_containing():
max_hour = 20 max_hour = 20
base = maya.now() base = maya.now()
containing_interval = maya.MayaInterval( containing_interval = maya.MayaInterval(
start=base, end=base.add(hours=max_hour + step) start=base,
end=base.add(hours=max_hour + step),
) )
intervals = [ intervals = [maya.MayaInterval(
maya.MayaInterval( start=base.add(hours=hour),
start=base.add(hours=hour), duration=timedelta(hours=step - 1) duration=timedelta(hours=step - 1),
) ) for hour in range(2, max_hour, step)]
for hour in range(2, max_hour, step)
]
intervals.append(containing_interval) intervals.append(containing_interval)
random.shuffle(intervals) random.shuffle(intervals)
assert maya.MayaInterval.flatten(intervals) == [containing_interval] assert maya.MayaInterval.flatten(intervals) == [containing_interval]
@@ -518,55 +561,24 @@ def test_interval_from_datetime():
start = maya.now() start = maya.now()
duration = timedelta(hours=1) duration = timedelta(hours=1)
end = start + duration end = start + duration
interval = maya.MayaInterval.from_datetime( 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.start == start
assert interval.end == end assert interval.end == end
interval2 = maya.MayaInterval.from_datetime( 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.start == start
assert interval2.end == end assert interval2.end == end
interval3 = maya.MayaInterval.from_datetime( 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.start == start
assert interval3.end == end assert interval3.end == end
def test_interval_iso8601():
start = maya.when('11-17-11 08:09:10')
interval = maya.MayaInterval(start=start, duration=1)
assert interval.iso8601() == '2011-11-17T08:09:10Z/2011-11-17T08:09:11Z'
def test_interval_from_iso8601():
interval = maya.MayaInterval.from_iso8601(
"2018-03-18T14:27:18Z/2018-04-01T04:15:27Z"
)
s = maya.when("2018-03-18T14:27:18Z")
e = maya.when("2018-04-01T04:15:27Z")
assert interval.start == s
assert interval.end == e
def test_interval_from_iso8601_duration():
interval = maya.MayaInterval.from_iso8601(
"2018-03-18T14:27:18Z/P13DT13H48M9S"
)
s = maya.when("2018-03-18T14:27:18Z")
e = maya.when("2018-04-01T04:15:27Z")
assert interval.start == s
assert interval.end == e
interval = maya.MayaInterval.from_iso8601(
"2018-03-05T14:27:18Z/P2W"
)
s = maya.when("2018-03-05T14:27:18Z")
e = maya.when("2018-03-19T14:27:18Z")
assert interval.start == s
assert interval.end == e