mirror of
https://github.com/kennethreitz/maya.git
synced 2026-06-05 23:00:18 +00:00
Compare commits
95 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 04a82f3078 | |||
| 5f6b5fc66d | |||
| 15a5c9eedb | |||
| 7c5d5871d3 | |||
| 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 | |||
| 79d017fdcf | |||
| 7331d0d855 | |||
| 9c90c0534a | |||
| b9c501b0e4 | |||
| 3a71ff0174 | |||
| 43cc1d5946 | |||
| aecb643beb | |||
| fa966900e1 | |||
| 892e589ef2 | |||
| 0f795738c0 | |||
| b333489081 | |||
| 17a450eb5d | |||
| 8cd2158567 | |||
| 883b1b9b92 | |||
| 414df5f3f5 | |||
| 1694ed7cf8 | |||
| cbe9f6bae7 | |||
| 8bdd5c65fa | |||
| 4d96d06d70 | |||
| bbdb9b8762 | |||
| 28ecad81bd | |||
| c56c552184 | |||
| d5e4853886 | |||
| 28b3a849a9 | |||
| d872f29cfc | |||
| d8cbdede28 | |||
| c0f1bd709f | |||
| 8469e60d3c | |||
| 953f857940 | |||
| 6375143eac | |||
| c0fd845d4c | |||
| 0f38b99157 | |||
| 66a016ea84 | |||
| 6f1df92b8f | |||
| 51c4298ece | |||
| 74092289dd | |||
| f3f2793b50 | |||
| 251f535d67 | |||
| 98e9a2190a | |||
| c8dd4b9264 | |||
| a1b27e80d6 | |||
| 92f3b83b8b | |||
| 0f39ec6323 | |||
| 4d88eede9d | |||
| 971ecec9cf | |||
| ef471f8041 | |||
| 0fda49bdb9 | |||
| a8a18462fe | |||
| 82dc88c878 | |||
| e9a14e32da | |||
| 7b7f990a13 | |||
| 720617f062 | |||
| 5bf45dbfb7 | |||
| 488a25bcd8 | |||
| b2ac4f08a5 | |||
| 57ccc67721 |
+7
-1
@@ -2,8 +2,14 @@ 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
|
script: pipenv run pytest tests/
|
||||||
|
|||||||
@@ -21,3 +21,6 @@ In chronological order:
|
|||||||
- jerry2yu (`@jerry2yu <https://github.com/jerry2yu>`_)
|
- jerry2yu (`@jerry2yu <https://github.com/jerry2yu>`_)
|
||||||
- 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>`_)
|
||||||
|
- Dima Spivak <dima@spivak.ch> (`@dimaspivak <https://github.com/dimaspivak>`_)
|
||||||
|
- Tom Barron <tusculum@gmail.com> (`@dtbarron <https://github.com/tbarron>`_)
|
||||||
|
|||||||
@@ -1,7 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2016 Kenneth Reitz
|
Copyright (c) 2016 Kenneth Reitz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|||||||
@@ -1,2 +1,6 @@
|
|||||||
|
.PHONY: tests docs
|
||||||
|
|
||||||
tests:
|
tests:
|
||||||
pytest test_maya.py test_maya_interval.py
|
pytest tests/
|
||||||
|
docs:
|
||||||
|
cd docs && make html
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
|
[dev-packages]
|
||||||
|
|
||||||
|
pytest = "*"
|
||||||
|
sphinx = "*"
|
||||||
|
|
||||||
|
|
||||||
[packages]
|
[packages]
|
||||||
|
|
||||||
humanize = "*"
|
humanize = "*"
|
||||||
pytz = "*"
|
pytz = "*"
|
||||||
dateparser = "*"
|
dateparser = "*"
|
||||||
"ruamel.yaml" = "*"
|
"ruamel.yaml" = "*"
|
||||||
tzlocal = "*"
|
tzlocal = "*"
|
||||||
pendulum = ">=1.0"
|
pendulum = ">=1.0"
|
||||||
|
snaptime = "*"
|
||||||
[dev-packages]
|
|
||||||
pytest = "*"
|
|
||||||
|
|||||||
Generated
+266
-21
@@ -1,11 +1,13 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "5617ff73ba51e60721267b24dc01e83f33d2a3692870b60e394b0f75ed2dc313"
|
"sha256": "3abd33d2f9beea083faca673fa3864252623b284fa24f9a8b939aed12428318c"
|
||||||
},
|
},
|
||||||
|
"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
|
||||||
}
|
}
|
||||||
@@ -13,60 +15,303 @@
|
|||||||
},
|
},
|
||||||
"default": {
|
"default": {
|
||||||
"dateparser": {
|
"dateparser": {
|
||||||
"version": "==0.6.0"
|
"hashes": [
|
||||||
|
"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": {
|
||||||
"version": "==1.2.0"
|
"hashes": [
|
||||||
|
"sha256:0c14388546db6605a860b8b7112cb69d0b11c9ce5e072210504544e0d4575799",
|
||||||
|
"sha256:39a255776528afe11ea0d57814f9bf3729c1e0b99063af2e5c6cfd750c3e1f7f",
|
||||||
|
"sha256:3c85e8cbc91f45e1cc916cc9180b34153cd6aaaaacfb51a48b3156318314fa82",
|
||||||
|
"sha256:8199206c479b13947dcac63c025575d035331bb3819d1783dc1d568a11962906",
|
||||||
|
"sha256:8798aeca58b3dd7ffdc5a4993c9eaafedc4048165429e8f499ddd62c73bf3964",
|
||||||
|
"sha256:881efe37328de0785c0731d462e1485a45712f2cd5cb55907d6c15458460ebeb",
|
||||||
|
"sha256:bcca072f82e84b419efec1320cd3ee5c230d263f3a601b146651ed4db77d89f0",
|
||||||
|
"sha256:ff0c5fa3af4a471a218408c448b804ac6bccb105127727474f4e83c0e4072e97"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==1.4.2"
|
||||||
},
|
},
|
||||||
"python-dateutil": {
|
"python-dateutil": {
|
||||||
"version": "==2.6.0"
|
"hashes": [
|
||||||
|
"sha256:07009062406cffd554a9b4135cd2ff167c9bf6b7aac61fe946c93e69fad1bbd8",
|
||||||
|
"sha256:8f95bb7e6edbb2456a51a1fb58c8dca942024b4f5844cae62c90aa88afe6e300"
|
||||||
|
],
|
||||||
|
"version": "==2.7.0"
|
||||||
},
|
},
|
||||||
"pytz": {
|
"pytz": {
|
||||||
"version": "==2017.2"
|
"hashes": [
|
||||||
|
"sha256:07edfc3d4d2705a20a6e99d97f0c4b61c800b8232dc1c04d87e8554f130148dd",
|
||||||
|
"sha256:3a47ff71597f821cd84a162e71593004286e5be07a340fd462f0d33a760782b5",
|
||||||
|
"sha256:410bcd1d6409026fbaa65d9ed33bf6dd8b1e94a499e32168acfc7b332e4095c0",
|
||||||
|
"sha256:5bd55c744e6feaa4d599a6cbd8228b4f8f9ba96de2c38d56f08e534b3c9edf0d",
|
||||||
|
"sha256:61242a9abc626379574a166dc0e96a66cd7c3b27fc10868003fa210be4bff1c9",
|
||||||
|
"sha256:887ab5e5b32e4d0c86efddd3d055c1f363cbaa583beb8da5e22d2fa2f64d51ef",
|
||||||
|
"sha256:ba18e6a243b3625513d85239b3e49055a2f0318466e0b8a92b8fb8ca7ccdf55f",
|
||||||
|
"sha256:ed6509d9af298b7995d69a440e2822288f2eca1681b8cce37673dbb10091e5fe",
|
||||||
|
"sha256:f93ddcdd6342f94cea379c73cddb5724e0d6d0a1c91c9bdef364dc0368ba4fda"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==2018.3"
|
||||||
},
|
},
|
||||||
"pytzdata": {
|
"pytzdata": {
|
||||||
"version": "==2017.2"
|
"hashes": [
|
||||||
|
"sha256:4e2cceb54335cd6c28caea46b15cd592e2aec5e8b05b0241cbccfb1b23c02ae7",
|
||||||
|
"sha256:7cd949123e2c2060fd12793de3a4a449e36b5dea5e169b810a3ac3f0b9877cfa"
|
||||||
|
],
|
||||||
|
"version": "==2018.3"
|
||||||
},
|
},
|
||||||
"regex": {
|
"regex": {
|
||||||
"version": "==2017.04.29"
|
"hashes": [
|
||||||
},
|
"sha256:1b428a296531ea1642a7da48562746309c5c06471a97bd0c02dd6a82e9cecee8",
|
||||||
"ruamel.ordereddict": {
|
"sha256:27d72bb42dffb32516c28d218bb054ce128afd3e18464f30837166346758af67",
|
||||||
"version": "==0.4.9"
|
"sha256:32cf4743debee9ea12d3626ee21eae83052763740e04086304e7a74778bf58c9",
|
||||||
|
"sha256:32f6408dbca35040bc65f9f4ae1444d5546411fde989cb71443a182dd643305e",
|
||||||
|
"sha256:333687d9a44738c486735955993f83bd22061a416c48f5a5f9e765e90cf1b0c9",
|
||||||
|
"sha256:35eeccf17af3b017a54d754e160af597036435c58eceae60f1dd1364ae1250c7",
|
||||||
|
"sha256:361a1fd703a35580a4714ec28d85e29780081a4c399a99bbfb2aee695d72aedb",
|
||||||
|
"sha256:494bed6396a20d3aa6376bdf2d3fbb1005b8f4339558d8ac7b53256755f80303",
|
||||||
|
"sha256:5b9c0ddd5b4afa08c9074170a2ea9b34ea296e32aeea522faaaaeeeb2fe0af2e",
|
||||||
|
"sha256:a50532f61b23d4ab9d216a6214f359dd05c911c1a1ad20986b6738a782926c1a",
|
||||||
|
"sha256:a9243d7b359b72c681a2c32eaa7ace8d346b7e8ce09d172a683acf6853161d9c",
|
||||||
|
"sha256:b44624a38d07d3c954c84ad302c29f7930f4bf01443beef5589e9157b14e2a29",
|
||||||
|
"sha256:be42a601aaaeb7a317f818490a39d153952a97c40c6e9beeb2a1103616405348",
|
||||||
|
"sha256:eee4d94b1a626490fc8170ffd788883f8c641b576e11ba9b4a29c9f6623371e0",
|
||||||
|
"sha256:f69d1201a4750f763971ea8364ed95ee888fc128968b39d38883a72a4d005895"
|
||||||
|
],
|
||||||
|
"version": "==2018.2.21"
|
||||||
},
|
},
|
||||||
"ruamel.yaml": {
|
"ruamel.yaml": {
|
||||||
"version": "==0.14.12"
|
"hashes": [
|
||||||
|
"sha256:01e30ecb1b1c0ebf9fce814dc20dace402571517277799291202b61b22096c24",
|
||||||
|
"sha256:02babffd019911841ba01b76e23dfec7c9e9b2725503fb2698c4982fa1a6e835",
|
||||||
|
"sha256:072f6364a89972e8dc0afdce3335a709d5464dfeaa4f736d092a54574338b874",
|
||||||
|
"sha256:14d161558e3bf89e87d77c218098be22fa9a0d6d0bea40250fce525b1d0cbee2",
|
||||||
|
"sha256:5504398fc755a2b14c9983b2101161a8591a4b30812590cc1c365e7fcc117dfa",
|
||||||
|
"sha256:68c8f2986bcb91b6db1aea8698941769840c7257e951a9377048f7eff35be773",
|
||||||
|
"sha256:6d05c5a5baf829c70916c226ef3200650846a7227de226bca8a59efaf88bb973",
|
||||||
|
"sha256:6d7929b24e329d662fa43b657fddfee5260e2d35d0a543065cd755d4e17a9b2f",
|
||||||
|
"sha256:8dc74821e4bb6b21fb1ab35964e159391d99ee44981d07d57bf96e2395f3ef75",
|
||||||
|
"sha256:9225c83952d28f302cfc23c3d9a6f8231bfd581476d7aff1e3c7de49eecb4ee9",
|
||||||
|
"sha256:b6c5d5f03ba78e3f27c7188a00c4e09b6a4507fe3154ba40a294e09cb30ee016",
|
||||||
|
"sha256:c0908896e34b617ead40552cab03c1769bdc43d1da02419160dc900c5dfddde2",
|
||||||
|
"sha256:c41e04b526d0153c9246cfab87d7ddefdc9f165cb8886a8ec48ba7a2b73069f6",
|
||||||
|
"sha256:e2d2715bf92156bec5fb42e92e95dac1c4d9904f8a3d4e2d0c438758fe9092d7",
|
||||||
|
"sha256:e3bbfe0d294e08fdbb0cb05485435a2ceb4e168e98b5dc611f051c1864986b4b",
|
||||||
|
"sha256:f2d02a4af5a13b09d0b823cdd0317b54f3e0115e50b5ac4d9840c3a1b566817f",
|
||||||
|
"sha256:fcfc24a21594c071cc4588e84b7657a1f47ebcf6037c6c43fa15c4bbd3989ec2"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.15.35"
|
||||||
},
|
},
|
||||||
"six": {
|
"six": {
|
||||||
"version": "==1.10.0"
|
"hashes": [
|
||||||
|
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
|
||||||
|
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
|
||||||
|
],
|
||||||
|
"version": "==1.11.0"
|
||||||
|
},
|
||||||
|
"snaptime": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:e3f1eb89043d58d30721ab98cb65023f1a4c2740e3b197704298b163c92d508b"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.2.4"
|
||||||
},
|
},
|
||||||
"tzlocal": {
|
"tzlocal": {
|
||||||
"version": "==1.4"
|
"hashes": [
|
||||||
|
"sha256:4ebeb848845ac898da6519b9b31879cf13b6626f7184c496037b818e238f2c4e"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==1.5.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"develop": {
|
"develop": {
|
||||||
"appdirs": {
|
"alabaster": {
|
||||||
"version": "==1.4.3"
|
"hashes": [
|
||||||
|
"sha256:2eef172f44e8d301d25aff8068fddd65f767a3f04b5f15b0f4922f113aa1c732",
|
||||||
|
"sha256:37cdcb9e9954ed60912ebc1ca12a9d12178c26637abdf124e3cde2341c257fe0"
|
||||||
|
],
|
||||||
|
"version": "==0.7.10"
|
||||||
|
},
|
||||||
|
"attrs": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9",
|
||||||
|
"sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450"
|
||||||
|
],
|
||||||
|
"version": "==17.4.0"
|
||||||
|
},
|
||||||
|
"babel": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:8ce4cb6fdd4393edd323227cba3a077bceb2a6ce5201c902c65e730046f41f14",
|
||||||
|
"sha256:ad209a68d7162c4cff4b29cdebe3dec4cef75492df501b0049a9433c96ce6f80"
|
||||||
|
],
|
||||||
|
"version": "==2.5.3"
|
||||||
|
},
|
||||||
|
"certifi": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:14131608ad2fd56836d33a71ee60fa1c82bc9d2c8d98b7bdbc631fe1b3cd1296",
|
||||||
|
"sha256:edbc3f203427eef571f79a7692bb160a2b0f7ccaa31953e99bd17e307cf63f7d"
|
||||||
|
],
|
||||||
|
"version": "==2018.1.18"
|
||||||
|
},
|
||||||
|
"chardet": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||||
|
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||||
|
],
|
||||||
|
"version": "==3.0.4"
|
||||||
|
},
|
||||||
|
"docutils": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6",
|
||||||
|
"sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274",
|
||||||
|
"sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6"
|
||||||
|
],
|
||||||
|
"version": "==0.14"
|
||||||
|
},
|
||||||
|
"idna": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f",
|
||||||
|
"sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4"
|
||||||
|
],
|
||||||
|
"version": "==2.6"
|
||||||
|
},
|
||||||
|
"imagesize": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:3620cc0cadba3f7475f9940d22431fc4d407269f1be59ec9b8edcca26440cf18",
|
||||||
|
"sha256:5b326e4678b6925158ccc66a9fa3122b6106d7c876ee32d7de6ce59385b96315"
|
||||||
|
],
|
||||||
|
"version": "==1.0.0"
|
||||||
|
},
|
||||||
|
"jinja2": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
|
||||||
|
"sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
|
||||||
|
],
|
||||||
|
"version": "==2.10"
|
||||||
|
},
|
||||||
|
"markupsafe": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"
|
||||||
|
],
|
||||||
|
"version": "==1.0"
|
||||||
},
|
},
|
||||||
"packaging": {
|
"packaging": {
|
||||||
"version": "==16.8"
|
"hashes": [
|
||||||
|
"sha256:e9215d2d2535d3ae866c3d6efc77d5b24a0192cce0ff20e42896cc0664f889c0",
|
||||||
|
"sha256:f019b770dd64e585a99714f1fd5e01c7a8f11b45635aa953fd41c689a657375b"
|
||||||
|
],
|
||||||
|
"version": "==17.1"
|
||||||
|
},
|
||||||
|
"pluggy": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff"
|
||||||
|
],
|
||||||
|
"version": "==0.6.0"
|
||||||
},
|
},
|
||||||
"py": {
|
"py": {
|
||||||
"version": "==1.4.33"
|
"hashes": [
|
||||||
|
"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": {
|
||||||
"version": "==3.0.7"
|
"hashes": [
|
||||||
|
"sha256:062027955bccbc04d2fcd5d79690947e018ba31abe4c90b2c6721abec734261b",
|
||||||
|
"sha256:117bad36c1a787e1a8a659df35de53ba05f9f3398fb9e4ac17e80ad5903eb8c5"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==3.4.2"
|
||||||
},
|
},
|
||||||
"setuptools": {
|
"pytz": {
|
||||||
"version": "==35.0.2"
|
"hashes": [
|
||||||
|
"sha256:07edfc3d4d2705a20a6e99d97f0c4b61c800b8232dc1c04d87e8554f130148dd",
|
||||||
|
"sha256:3a47ff71597f821cd84a162e71593004286e5be07a340fd462f0d33a760782b5",
|
||||||
|
"sha256:410bcd1d6409026fbaa65d9ed33bf6dd8b1e94a499e32168acfc7b332e4095c0",
|
||||||
|
"sha256:5bd55c744e6feaa4d599a6cbd8228b4f8f9ba96de2c38d56f08e534b3c9edf0d",
|
||||||
|
"sha256:61242a9abc626379574a166dc0e96a66cd7c3b27fc10868003fa210be4bff1c9",
|
||||||
|
"sha256:887ab5e5b32e4d0c86efddd3d055c1f363cbaa583beb8da5e22d2fa2f64d51ef",
|
||||||
|
"sha256:ba18e6a243b3625513d85239b3e49055a2f0318466e0b8a92b8fb8ca7ccdf55f",
|
||||||
|
"sha256:ed6509d9af298b7995d69a440e2822288f2eca1681b8cce37673dbb10091e5fe",
|
||||||
|
"sha256:f93ddcdd6342f94cea379c73cddb5724e0d6d0a1c91c9bdef364dc0368ba4fda"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==2018.3"
|
||||||
|
},
|
||||||
|
"requests": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b",
|
||||||
|
"sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e"
|
||||||
|
],
|
||||||
|
"version": "==2.18.4"
|
||||||
},
|
},
|
||||||
"six": {
|
"six": {
|
||||||
"version": "==1.10.0"
|
"hashes": [
|
||||||
|
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
|
||||||
|
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
|
||||||
|
],
|
||||||
|
"version": "==1.11.0"
|
||||||
|
},
|
||||||
|
"snowballstemmer": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:919f26a68b2c17a7634da993d91339e288964f93c274f1343e3bbbe2096e1128",
|
||||||
|
"sha256:9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89"
|
||||||
|
],
|
||||||
|
"version": "==1.2.1"
|
||||||
|
},
|
||||||
|
"sphinx": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:41ae26acc6130ccf6ed47e5cca73742b80d55a134f0ab897c479bba8d3640b8e",
|
||||||
|
"sha256:da987de5fcca21a4acc7f67a86a363039e67ac3e8827161e61b91deb131c0ee8"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==1.7.1"
|
||||||
|
},
|
||||||
|
"sphinxcontrib-websupport": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:7a85961326aa3a400cd4ad3c816d70ed6f7c740acd7ce5d78cd0a67825072eb9",
|
||||||
|
"sha256:f4932e95869599b89bf4f80fc3989132d83c9faa5bf633e7b5e0c25dffb75da2"
|
||||||
|
],
|
||||||
|
"version": "==1.0.1"
|
||||||
|
},
|
||||||
|
"urllib3": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b",
|
||||||
|
"sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f"
|
||||||
|
],
|
||||||
|
"version": "==1.22"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-7
@@ -20,6 +20,8 @@ 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
|
||||||
---------------------
|
---------------------
|
||||||
@@ -63,6 +65,15 @@ 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
|
||||||
|
|
||||||
@@ -74,13 +85,18 @@ Behold, datetimes for humans!
|
|||||||
UTC
|
UTC
|
||||||
|
|
||||||
# Range of hours in a day:
|
# Range of hours in a day:
|
||||||
>>> maya.interval(start=maya.now(), end=maya.now().add(days=1), interval=60*60)
|
>>> maya.intervals(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 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.
|
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:
|
For example:
|
||||||
|
|
||||||
@@ -94,8 +110,7 @@ For example:
|
|||||||
|
|
||||||
>>> event = MayaInterval(start=event_start, end=event_end)
|
>>> event = MayaInterval(start=event_start, end=event_end)
|
||||||
|
|
||||||
From here, there a a number of methods available to you, which you can use to compare this event to another event.
|
From here, there are a number of methods available to you, which you can use to compare this event to another event.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -114,7 +129,7 @@ From here, there a a number of methods available to you, which you can use to co
|
|||||||
☤ What about Delorean, Arrow, & Pendulum?
|
☤ What about Delorean, Arrow, & Pendulum?
|
||||||
-----------------------------------------
|
-----------------------------------------
|
||||||
|
|
||||||
All these project complement eachother, and are friends. Pendulum, for example, helps power Maya's parsing.
|
All these project complement each other, and are friends. Pendulum, for example, helps power Maya's parsing.
|
||||||
|
|
||||||
Arrow, for example, is a fantastic library, but isn't what I wanted in a datetime library. In many ways, it's better than Maya for certain things. In some ways, in my opinion, it's not.
|
Arrow, for example, is a fantastic library, but isn't what I wanted in a datetime library. In many ways, it's better than Maya for certain things. In some ways, in my opinion, it's not.
|
||||||
|
|
||||||
@@ -126,9 +141,9 @@ I think these projects complement each-other, personally. Maya is great for pars
|
|||||||
☤ Installing Maya
|
☤ Installing Maya
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
Installation is easy, with pip::
|
Installation is easy, with `pipenv <http://pipenv.org/>`_::
|
||||||
|
|
||||||
$ pip install maya
|
$ pipenv install maya
|
||||||
|
|
||||||
✨🍰✨
|
✨🍰✨
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Minimal makefile for Sphinx documentation
|
||||||
|
#
|
||||||
|
|
||||||
|
# You can set these variables from the command line.
|
||||||
|
SPHINXOPTS =
|
||||||
|
SPHINXBUILD = python -msphinx
|
||||||
|
SPHINXPROJ = maya
|
||||||
|
SOURCEDIR = source
|
||||||
|
BUILDDIR = _build
|
||||||
|
|
||||||
|
# Put it first so that "make" without argument is like "make help".
|
||||||
|
help:
|
||||||
|
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
|
|
||||||
|
.PHONY: help Makefile
|
||||||
|
|
||||||
|
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||||
|
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||||
|
%: Makefile
|
||||||
|
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
@ECHO OFF
|
||||||
|
|
||||||
|
pushd %~dp0
|
||||||
|
|
||||||
|
REM Command file for Sphinx documentation
|
||||||
|
|
||||||
|
if "%SPHINXBUILD%" == "" (
|
||||||
|
set SPHINXBUILD=python -msphinx
|
||||||
|
)
|
||||||
|
set SOURCEDIR=source
|
||||||
|
set BUILDDIR=build
|
||||||
|
set SPHINXPROJ=maya
|
||||||
|
|
||||||
|
if "%1" == "" goto help
|
||||||
|
|
||||||
|
%SPHINXBUILD% >NUL 2>NUL
|
||||||
|
if errorlevel 9009 (
|
||||||
|
echo.
|
||||||
|
echo.The Sphinx module was not found. Make sure you have Sphinx installed,
|
||||||
|
echo.then set the SPHINXBUILD environment variable to point to the full
|
||||||
|
echo.path of the 'sphinx-build' executable. Alternatively you may add the
|
||||||
|
echo.Sphinx directory to PATH.
|
||||||
|
echo.
|
||||||
|
echo.If you don't have Sphinx installed, grab it from
|
||||||
|
echo.http://sphinx-doc.org/
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||||
|
goto end
|
||||||
|
|
||||||
|
:help
|
||||||
|
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||||
|
|
||||||
|
:end
|
||||||
|
popd
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# maya documentation build configuration file, created by
|
||||||
|
# sphinx-quickstart on Sun May 28 15:46:10 2017.
|
||||||
|
#
|
||||||
|
# This file is execfile()d with the current directory set to its
|
||||||
|
# containing dir.
|
||||||
|
#
|
||||||
|
# Note that not all possible configuration values are present in this
|
||||||
|
# autogenerated file.
|
||||||
|
#
|
||||||
|
# All configuration values have a default; values that are commented out
|
||||||
|
# serve to show the default.
|
||||||
|
|
||||||
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
|
#
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
# sys.path.insert(0, os.path.abspath('.'))
|
||||||
|
sys.path.insert(0, os.path.abspath('..'))
|
||||||
|
sys.path.insert(0, os.path.abspath('_themes'))
|
||||||
|
|
||||||
|
import maya
|
||||||
|
|
||||||
|
# -- General configuration ------------------------------------------------
|
||||||
|
|
||||||
|
# If your documentation needs a minimal Sphinx version, state it here.
|
||||||
|
#
|
||||||
|
# needs_sphinx = '1.0'
|
||||||
|
|
||||||
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||||
|
# ones.
|
||||||
|
extensions = ['sphinx.ext.autodoc',
|
||||||
|
'sphinx.ext.todo',
|
||||||
|
'sphinx.ext.coverage',
|
||||||
|
'sphinx.ext.viewcode']
|
||||||
|
|
||||||
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
|
templates_path = ['_templates']
|
||||||
|
|
||||||
|
# The suffix(es) of source filenames.
|
||||||
|
# You can specify multiple suffix as a list of string:
|
||||||
|
#
|
||||||
|
# source_suffix = ['.rst', '.md']
|
||||||
|
source_suffix = '.rst'
|
||||||
|
|
||||||
|
# The master toctree document.
|
||||||
|
master_doc = 'index'
|
||||||
|
|
||||||
|
# General information about the project.
|
||||||
|
project = 'maya'
|
||||||
|
copyright = '2017, Kenneth Reitz'
|
||||||
|
author = 'Kenneth Reitz'
|
||||||
|
|
||||||
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
|
# |version| and |release|, also used in various other places throughout the
|
||||||
|
# built documents.
|
||||||
|
#
|
||||||
|
# The short X.Y version.
|
||||||
|
version = maya.__version__
|
||||||
|
# The full version, including alpha/beta/rc tags.
|
||||||
|
release = maya.__version__
|
||||||
|
|
||||||
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
|
# for a list of supported languages.
|
||||||
|
#
|
||||||
|
# This is also used if you do content translation via gettext catalogs.
|
||||||
|
# Usually you set "language" from the command line for these cases.
|
||||||
|
language = None
|
||||||
|
|
||||||
|
# List of patterns, relative to source directory, that match files and
|
||||||
|
# directories to ignore when looking for source files.
|
||||||
|
# This patterns also effect to html_static_path and html_extra_path
|
||||||
|
exclude_patterns = []
|
||||||
|
|
||||||
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
|
pygments_style = 'sphinx'
|
||||||
|
|
||||||
|
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||||
|
todo_include_todos = True
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for HTML output ----------------------------------------------
|
||||||
|
|
||||||
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
|
# a list of builtin themes.
|
||||||
|
#
|
||||||
|
html_theme = 'alabaster'
|
||||||
|
|
||||||
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
|
# further. For a list of options available for each theme, see the
|
||||||
|
# documentation.
|
||||||
|
#
|
||||||
|
# html_theme_options = {}
|
||||||
|
|
||||||
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
|
html_static_path = ['_static']
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for HTMLHelp output ------------------------------------------
|
||||||
|
|
||||||
|
# Output file base name for HTML help builder.
|
||||||
|
htmlhelp_basename = 'mayadoc'
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for LaTeX output ---------------------------------------------
|
||||||
|
|
||||||
|
latex_elements = {
|
||||||
|
# The paper size ('letterpaper' or 'a4paper').
|
||||||
|
#
|
||||||
|
# 'papersize': 'letterpaper',
|
||||||
|
|
||||||
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
|
#
|
||||||
|
# 'pointsize': '10pt',
|
||||||
|
|
||||||
|
# Additional stuff for the LaTeX preamble.
|
||||||
|
#
|
||||||
|
# 'preamble': '',
|
||||||
|
|
||||||
|
# Latex figure (float) alignment
|
||||||
|
#
|
||||||
|
# 'figure_align': 'htbp',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
|
# (source start file, target name, title,
|
||||||
|
# author, documentclass [howto, manual, or own class]).
|
||||||
|
latex_documents = [
|
||||||
|
(master_doc, 'maya.tex', 'maya Documentation',
|
||||||
|
'Kenneth Reitz', 'manual'),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for manual page output ---------------------------------------
|
||||||
|
|
||||||
|
# One entry per manual page. List of tuples
|
||||||
|
# (source start file, name, description, authors, manual section).
|
||||||
|
man_pages = [
|
||||||
|
(master_doc, 'maya', 'maya Documentation',
|
||||||
|
[author], 1)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for Texinfo output -------------------------------------------
|
||||||
|
|
||||||
|
# Grouping the document tree into Texinfo files. List of tuples
|
||||||
|
# (source start file, target name, title, author,
|
||||||
|
# dir menu entry, description, category)
|
||||||
|
texinfo_documents = [
|
||||||
|
(master_doc, 'maya', 'maya Documentation',
|
||||||
|
author, 'maya', 'One line description of project.',
|
||||||
|
'Miscellaneous'),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
.. maya documentation master file, created by
|
||||||
|
sphinx-quickstart on Sun May 28 15:46:10 2017.
|
||||||
|
You can adapt this file completely to your liking, but it should at least
|
||||||
|
contain the root `toctree` directive.
|
||||||
|
|
||||||
|
Maya: Datetime for Humans
|
||||||
|
================================
|
||||||
|
Release v\ |version|. (:ref:`Installation <install>`)
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/v/maya.svg
|
||||||
|
:target: https://pypi.python.org/pypi/maya
|
||||||
|
|
||||||
|
.. image:: https://travis-ci.org/kennethreitz/maya.svg?branch=master
|
||||||
|
:target: https://travis-ci.org/kennethreitz/maya
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/badge/SayThanks-!-1EAEDB.svg
|
||||||
|
:target: https://saythanks.io/to/kennethreitz
|
||||||
|
|
||||||
|
☤ Behold, datetimes for humans!
|
||||||
|
-------------------------------
|
||||||
|
.. code-block:: pycon
|
||||||
|
|
||||||
|
>>> now = maya.now()
|
||||||
|
<MayaDT epoch=1481850660.9>
|
||||||
|
|
||||||
|
>>> tomorrow = maya.when('tomorrow')
|
||||||
|
<MayaDT epoch=1481919067.23>
|
||||||
|
|
||||||
|
>>> tomorrow.slang_date()
|
||||||
|
'tomorrow'
|
||||||
|
|
||||||
|
>>> tomorrow.slang_time()
|
||||||
|
'23 hours from now'
|
||||||
|
|
||||||
|
# Also: MayaDT.from_iso8601(...)
|
||||||
|
>>> tomorrow.iso8601()
|
||||||
|
'2017-02-10T22:17:01.445418Z'
|
||||||
|
|
||||||
|
# Also: MayaDT.from_rfc2822(...)
|
||||||
|
>>> tomorrow.rfc2822()
|
||||||
|
'Fri, 10 Feb 2017 22:17:01 GMT'
|
||||||
|
|
||||||
|
# Also: MayaDT.from_rfc3339(...)
|
||||||
|
>>> tomorrow.rfc3339()
|
||||||
|
'2017-02-10T22:17:01.44Z'
|
||||||
|
|
||||||
|
>>> tomorrow.datetime()
|
||||||
|
datetime.datetime(2016, 12, 16, 15, 11, 30, 263350, tzinfo=<UTC>)
|
||||||
|
|
||||||
|
# Automatically parse datetime strings and generate naive datetimes.
|
||||||
|
>>> scraped = '2016-12-16 18:23:45.423992+00:00'
|
||||||
|
>>> maya.parse(scraped).datetime(to_timezone='US/Eastern', naive=True)
|
||||||
|
datetime.datetime(2016, 12, 16, 13, 23, 45, 423992)
|
||||||
|
|
||||||
|
>>> rand_day = maya.when('2011-02-07', timezone='US/Eastern')
|
||||||
|
<MayaDT epoch=1297036800.0>
|
||||||
|
|
||||||
|
>>> rand_day.day
|
||||||
|
7
|
||||||
|
|
||||||
|
>>> rand_day.add(days=10).day
|
||||||
|
17
|
||||||
|
|
||||||
|
# Always.
|
||||||
|
>>> rand_day.timezone
|
||||||
|
UTC
|
||||||
|
|
||||||
|
# Range of hours in a day:
|
||||||
|
>>> maya.interval(
|
||||||
|
... start=maya.now(),
|
||||||
|
... end=maya.now().add(days=1),
|
||||||
|
... interval=60*60)
|
||||||
|
<generator object intervals at 0x105ba5820>
|
||||||
|
|
||||||
|
|
||||||
|
Table of Contents
|
||||||
|
-----------------
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
user/install
|
||||||
|
user/quickstart
|
||||||
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
.. _install:
|
||||||
|
Installation
|
||||||
|
=============
|
||||||
|
|
||||||
|
Pip Install Maya
|
||||||
|
----------------
|
||||||
|
|
||||||
|
To install maya, simply use::
|
||||||
|
$ pip install maya
|
||||||
|
|
||||||
|
Source Code
|
||||||
|
-----------
|
||||||
|
Maya is actively developed on `Github
|
||||||
|
<https://github.com/kennethreitz/maya.git>`_
|
||||||
|
|
||||||
|
You can either clone the public repository::
|
||||||
|
|
||||||
|
$ git clone git://github.com/kennethreitz/maya.git
|
||||||
|
|
||||||
|
Or, download the `tarball <https://github.com/kennethreitz/maya/tarball/master>`_::
|
||||||
|
|
||||||
|
$ curl -OL https://github.com/kennethreitz/maya/tarball/master
|
||||||
|
# optionally, zipball is also available (for Windows users).
|
||||||
|
|
||||||
|
Once you have a copy of the source, you can embed it in your own Python
|
||||||
|
package, or install it into your site-packages easily::
|
||||||
|
|
||||||
|
$ python setup.py install
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
.. _quickstart:
|
||||||
|
Quickstart
|
||||||
|
==========
|
||||||
|
|
||||||
|
.. module::maya
|
||||||
|
|
||||||
|
Ready for a simple datetime tool? This doc provides some tools to use in your
|
||||||
|
busy workflow.
|
||||||
|
|
||||||
|
First, make sure that Maya is:
|
||||||
|
|
||||||
|
- :ref:`Installed <install>`
|
||||||
|
- :ref:`Up to date <update>`
|
||||||
|
|
||||||
|
|
||||||
|
Parse a Date
|
||||||
|
------------
|
||||||
|
Parsing a date from a string with Maya is 🍰!
|
||||||
|
|
||||||
|
First, you'll need to import maya::
|
||||||
|
|
||||||
|
>>> import maya
|
||||||
|
|
||||||
|
There are currently two ways to make sense of datetime:
|
||||||
|
|
||||||
|
- ``maya.parse``
|
||||||
|
- ``maya.when``
|
||||||
|
|
||||||
|
A simple answer is that you should use parse on machine output, and when on human input.
|
||||||
|
|
||||||
|
Use as follows::
|
||||||
|
|
||||||
|
>>> recent_win = maya.parse('2016-11-02T20:00PM')
|
||||||
|
>>> old_win = maya.when('October 14, 1908')
|
||||||
|
>>> grandpas_date = maya.when('108 years ago')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
.. _update:
|
||||||
|
Community Updates
|
||||||
|
=================
|
||||||
|
|
||||||
|
If you'd like to stay up to date on the community and development of Maya,
|
||||||
|
there are several options:
|
||||||
|
|
||||||
|
|
||||||
|
GitHub
|
||||||
|
------
|
||||||
|
|
||||||
|
The best way to track the development of Maya is through
|
||||||
|
`the GitHub repo <https://github.com/kennethreitz/Maya>`_.
|
||||||
|
|
||||||
|
Twitter
|
||||||
|
-------
|
||||||
|
|
||||||
|
The author, Kenneth Reitz, often tweets about new features and releases of Maya.
|
||||||
|
|
||||||
|
Follow `@kennethreitz <https://twitter.com/kennethreitz>`_ for updates.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Release and Version History
|
||||||
|
===========================
|
||||||
|
|
||||||
|
.. include:: ../../HISTORY.rst
|
||||||
@@ -1 +1,2 @@
|
|||||||
from .core import *
|
from .core import *
|
||||||
|
from .__version__ import __version__
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
__version__ = '0.3.4'
|
||||||
+11
-9
@@ -1,5 +1,4 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""
|
"""
|
||||||
maya.compat
|
maya.compat
|
||||||
~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~
|
||||||
@@ -12,24 +11,19 @@ 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.
|
||||||
@@ -38,8 +32,10 @@ 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
|
||||||
|
|
||||||
@@ -59,36 +55,42 @@ 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__
|
||||||
|
|||||||
+337
-150
@@ -1,7 +1,6 @@
|
|||||||
# ___ __ ___ _ _ ___
|
# ___ __ ___ _ _ ___
|
||||||
# || \/ | ||=|| \\// ||=||
|
# || \/ | ||=|| \\// ||=||
|
||||||
# || | || || // || ||
|
# || | || || // || ||
|
||||||
|
|
||||||
# Ignore warnings for yaml usage.
|
# Ignore warnings for yaml usage.
|
||||||
import warnings
|
import warnings
|
||||||
import ruamel.yaml
|
import ruamel.yaml
|
||||||
@@ -10,32 +9,72 @@ 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 functools
|
import re
|
||||||
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):
|
||||||
"""
|
"""
|
||||||
Decorator to validate all the arguments to function
|
Decorator to validate all the arguments to function
|
||||||
are of the type of calling class
|
are of the type of calling class for passed operator
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def inner(function):
|
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('unorderable types: {}() {} {}()'.format(
|
raise TypeError(
|
||||||
type(self).__name__, operator, type(arg).__name__))
|
'unorderable types: {}() {} {}()'.format(
|
||||||
|
type(self).__name__, operator, type(arg).__name__
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return function(self, *args, **kwargs)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
return inner
|
||||||
|
|
||||||
|
|
||||||
|
def validate_arguments_type_of_function(param_type=None):
|
||||||
|
"""
|
||||||
|
Decorator to validate the <type> of arguments in
|
||||||
|
the calling function are of the `param_type` class.
|
||||||
|
|
||||||
|
if `param_type` is None, uses `param_type` as the class where it is used.
|
||||||
|
|
||||||
|
Note: Use this decorator on the functions of the class.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def inner(function):
|
||||||
|
|
||||||
|
def wrapper(self, *args, **kwargs):
|
||||||
|
type_ = param_type or type(self)
|
||||||
|
for arg in args + tuple(kwargs.values()):
|
||||||
|
if not isinstance(arg, type_):
|
||||||
|
raise TypeError(
|
||||||
|
(
|
||||||
|
'Invalid Type: {}.{}() accepts only the '
|
||||||
|
'arguments of type "<{}>"'
|
||||||
|
).format(
|
||||||
|
type(self).__name__,
|
||||||
|
function.__name__,
|
||||||
|
type_.__name__,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return function(self, *args, **kwargs)
|
return function(self, *args, **kwargs)
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
@@ -45,6 +84,7 @@ def validate_class_type_arguments(operator):
|
|||||||
|
|
||||||
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__()
|
||||||
@@ -87,27 +127,50 @@ class MayaDT(object):
|
|||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return hash(int(self.epoch))
|
return hash(int(self.epoch))
|
||||||
|
|
||||||
def __add__(self, item):
|
def __add__(self, duration):
|
||||||
return self.add(seconds=seconds_or_timedelta(item).total_seconds())
|
return self.add(
|
||||||
|
seconds=_seconds_or_timedelta(duration).total_seconds()
|
||||||
|
)
|
||||||
|
|
||||||
def __radd__(self, item):
|
def __radd__(self, duration):
|
||||||
return self + item
|
return self + duration
|
||||||
|
|
||||||
def __sub__(self, item):
|
def __sub__(self, duration_or_date):
|
||||||
return self.subtract(
|
if isinstance(duration_or_date, MayaDT):
|
||||||
seconds=seconds_or_timedelta(item).total_seconds())
|
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):
|
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(pendulum.instance(self.datetime()).add(**kwargs))
|
return self.from_datetime(
|
||||||
|
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(pendulum.instance(self.datetime()).subtract(**kwargs))
|
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(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."""
|
||||||
@@ -120,8 +183,11 @@ class MayaDT(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def local_timezone(self):
|
def local_timezone(self):
|
||||||
"""Returns the name of the local timezone, for informational purposes."""
|
"""Returns the name of the local timezone."""
|
||||||
return self._local_tz.zone
|
if self._local_tz.zone in pytz.all_timezones:
|
||||||
|
return self._local_tz.zone
|
||||||
|
|
||||||
|
return self.timezone
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _local_tz(self):
|
def _local_tz(self):
|
||||||
@@ -129,63 +195,72 @@ class MayaDT(object):
|
|||||||
return get_localzone()
|
return get_localzone()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@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)
|
||||||
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
|
@classmethod
|
||||||
def from_iso8601(klass, string):
|
@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
|
||||||
|
def from_iso8601(klass, iso8601_string):
|
||||||
"""Returns MayaDT instance from iso8601 string."""
|
"""Returns MayaDT instance from iso8601 string."""
|
||||||
return parse(string)
|
return parse(iso8601_string)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_rfc2822(string):
|
def from_rfc2822(rfc2822_string):
|
||||||
"""Returns MayaDT instance from rfc2822 string."""
|
"""Returns MayaDT instance from rfc2822 string."""
|
||||||
return parse(string)
|
return parse(rfc2822_string)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_rfc3339(string):
|
def from_rfc3339(rfc3339_string):
|
||||||
"""Returns MayaDT instance from rfc3339 string."""
|
"""Returns MayaDT instance from rfc3339 string."""
|
||||||
return parse(string)
|
return parse(rfc3339_string)
|
||||||
|
|
||||||
# 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 {string} -- timezone to convert to (default: None/UTC)
|
to_timezone {str} -- timezone to convert to (default: None/UTC)
|
||||||
naive {boolean} -- if True, the tzinfo is simply dropped (default: False)
|
naive {bool} -- if True,
|
||||||
|
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):
|
||||||
@@ -204,7 +279,6 @@ class MayaDT(object):
|
|||||||
|
|
||||||
# Properties
|
# Properties
|
||||||
# ----------
|
# ----------
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def year(self):
|
def year(self):
|
||||||
return self.datetime().year
|
return self.datetime().year
|
||||||
@@ -217,13 +291,20 @@ 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. 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()
|
return self.datetime().isoweekday()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -248,7 +329,6 @@ 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)
|
||||||
@@ -260,15 +340,38 @@ 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)
|
||||||
|
|
||||||
|
|
||||||
@@ -277,39 +380,46 @@ def to_iso8601(dt):
|
|||||||
|
|
||||||
|
|
||||||
def end_of_day_midnight(dt):
|
def end_of_day_midnight(dt):
|
||||||
return dt if dt.time() == time.min else \
|
if dt.time() == time.min:
|
||||||
(dt.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=1))
|
return dt
|
||||||
|
|
||||||
|
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, inclusive of the start
|
A MayaInterval represents a range between two datetimes,
|
||||||
and exclusive of the end.
|
inclusive of the start 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 duration is not None),
|
(start and end),
|
||||||
(end and duration is not None),
|
(start 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.
|
||||||
duration = seconds_or_timedelta(duration)
|
if 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')
|
||||||
|
|
||||||
@@ -317,39 +427,69 @@ class MayaInterval(object):
|
|||||||
self.end = end
|
self.end = end
|
||||||
|
|
||||||
def __repr__(self):
|
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):
|
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.iso6801, self.end.iso8601)
|
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
|
@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)
|
# start = self._parse_iso8601_duration(start, end=end)
|
||||||
# try:
|
raise NotImplementedError()
|
||||||
# end = parse(start)
|
|
||||||
# except pendulum.parsing.exceptions.ParserError as e:
|
try:
|
||||||
# end = self._parse_iso8601_duration(start)
|
end = parse(end)
|
||||||
|
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()
|
|
||||||
|
|
||||||
def __and__(self, i):
|
@validate_arguments_type_of_function()
|
||||||
return self.intersection(i)
|
def __and__(self, maya_interval):
|
||||||
|
return self.intersection(maya_interval)
|
||||||
|
|
||||||
def __or__(self, i):
|
@validate_arguments_type_of_function()
|
||||||
return self.combine(i)
|
def __or__(self, maya_interval):
|
||||||
|
return self.combine(maya_interval)
|
||||||
|
|
||||||
def __eq__(self, i):
|
@validate_arguments_type_of_function()
|
||||||
|
def __eq__(self, maya_interval):
|
||||||
return (
|
return (
|
||||||
self.start == i.start and
|
self.start == maya_interval.start and self.end == maya_interval.end
|
||||||
self.end == i.end
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
@@ -359,10 +499,11 @@ class MayaInterval(object):
|
|||||||
yield self.start
|
yield self.start
|
||||||
yield self.end
|
yield self.end
|
||||||
|
|
||||||
def __cmp__(self, i):
|
@validate_arguments_type_of_function()
|
||||||
|
def __cmp__(self, maya_interval):
|
||||||
return (
|
return (
|
||||||
cmp(self.start, i.start) or
|
cmp(self.start, maya_interval.start)
|
||||||
cmp(self.end, i.end)
|
or cmp(self.end, maya_interval.end)
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -377,116 +518,117 @@ class MayaInterval(object):
|
|||||||
def is_instant(self):
|
def is_instant(self):
|
||||||
return self.timedelta == timedelta(seconds=0)
|
return self.timedelta == timedelta(seconds=0)
|
||||||
|
|
||||||
def intersects(self, i):
|
def intersects(self, maya_interval):
|
||||||
return self & i is not None
|
return self & maya_interval is not None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def midpoint(self):
|
def midpoint(self):
|
||||||
return self.start.add(seconds=(self.duration / 2))
|
return self.start.add(seconds=(self.duration / 2))
|
||||||
|
|
||||||
def combine(self, i):
|
@validate_arguments_type_of_function()
|
||||||
|
def combine(self, maya_interval):
|
||||||
"""Returns a combined list of timespans, merged together."""
|
"""Returns a combined list of timespans, merged together."""
|
||||||
ii = sorted([self, i])
|
interval_list = sorted([self, maya_interval])
|
||||||
if self & i or self.is_adjacent(i):
|
if self & maya_interval or self.is_adjacent(maya_interval):
|
||||||
return [
|
return [
|
||||||
MayaInterval(
|
MayaInterval(
|
||||||
ii[0].start,
|
interval_list[0].start,
|
||||||
max(ii[0].end, ii[1].end),
|
max(interval_list[0].end, interval_list[1].end),
|
||||||
),
|
)
|
||||||
]
|
]
|
||||||
return ii
|
|
||||||
|
|
||||||
def subtract(self, i):
|
return interval_list
|
||||||
""""Removes the given inerval."""
|
|
||||||
if not self & i:
|
@validate_arguments_type_of_function()
|
||||||
|
def subtract(self, maya_interval):
|
||||||
|
""""Removes the given interval."""
|
||||||
|
if not self & maya_interval:
|
||||||
return [self]
|
return [self]
|
||||||
elif i.contains(self):
|
|
||||||
|
elif maya_interval.contains(self):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
ii = []
|
interval_list = []
|
||||||
if self.start < i.start:
|
if self.start < maya_interval.start:
|
||||||
ii.append(MayaInterval(self.start, i.start))
|
interval_list.append(MayaInterval(self.start, maya_interval.start))
|
||||||
if self.end > i.end:
|
if self.end > maya_interval.end:
|
||||||
ii.append(MayaInterval(i.end, self.end))
|
interval_list.append(MayaInterval(maya_interval.end, self.end))
|
||||||
return ii
|
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):
|
||||||
|
raise ValueError('cannot call split with a non-positive timedelta')
|
||||||
|
|
||||||
assert duration > timedelta(seconds=0), 'cannot call split with a non-positive timedelta'
|
|
||||||
start = self.start
|
start = self.start
|
||||||
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):
|
||||||
|
raise ValueError('cannot quantize by non-positive timedelta')
|
||||||
|
|
||||||
assert duration > timedelta(seconds=0), 'cannot quantize by non-positive timedelta'
|
|
||||||
epoch = timezone.localize(Datetime(1970, 1, 1))
|
epoch = timezone.localize(Datetime(1970, 1, 1))
|
||||||
seconds = int(duration.total_seconds())
|
seconds = int(duration.total_seconds())
|
||||||
|
start_seconds = int(
|
||||||
start_seconds = int((self.start.datetime(naive=False) - epoch).total_seconds())
|
(self.start.datetime(naive=False) - epoch).total_seconds()
|
||||||
end_seconds = int((self.end.datetime(naive=False) - epoch).total_seconds())
|
)
|
||||||
|
end_seconds = int(
|
||||||
|
(self.end.datetime(naive=False) - epoch).total_seconds()
|
||||||
|
)
|
||||||
if start_seconds % seconds and not snap_out:
|
if start_seconds % seconds and not snap_out:
|
||||||
start_seconds += seconds
|
start_seconds += seconds
|
||||||
if end_seconds % seconds and snap_out:
|
if end_seconds % seconds and snap_out:
|
||||||
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),
|
||||||
)
|
)
|
||||||
|
|
||||||
def intersection(self, i):
|
@validate_arguments_type_of_function()
|
||||||
|
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, i.start)
|
end = min(self.end, maya_interval.end)
|
||||||
end = min(self.end, i.end)
|
either_instant = self.is_instant or maya_interval.is_instant
|
||||||
|
instant_overlap = (self.start == maya_interval.start or start <= end)
|
||||||
either_instant = self.is_instant or i.is_instant
|
|
||||||
instant_overlap = (
|
|
||||||
self.start == i.start or
|
|
||||||
start <= end
|
|
||||||
)
|
|
||||||
if (either_instant and instant_overlap) or (start < end):
|
if (either_instant and instant_overlap) or (start < end):
|
||||||
return MayaInterval(start, end)
|
return MayaInterval(start, end)
|
||||||
|
|
||||||
def contains(self, i):
|
@validate_arguments_type_of_function()
|
||||||
|
def contains(self, maya_interval):
|
||||||
return (
|
return (
|
||||||
self.start <= i.start and
|
self.start <= maya_interval.start and self.end >= maya_interval.end
|
||||||
self.end >= i.end
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def __contains__(self, item):
|
def __contains__(self, maya_dt):
|
||||||
if isinstance(item, MayaDT):
|
if isinstance(maya_dt, MayaDT):
|
||||||
return self.contains_dt(item)
|
return self.contains_dt(maya_dt)
|
||||||
|
|
||||||
return self.contains(item)
|
return self.contains(maya_dt)
|
||||||
|
|
||||||
def contains_dt(self, dt):
|
def contains_dt(self, dt):
|
||||||
return self.start <= dt < self.end
|
return self.start <= dt < self.end
|
||||||
|
|
||||||
def is_adjacent(self, i):
|
@validate_arguments_type_of_function()
|
||||||
|
def is_adjacent(self, maya_interval):
|
||||||
return (
|
return (
|
||||||
self.start == i.end or
|
self.start == maya_interval.end or self.end == maya_interval.start
|
||||||
self.end == i.start
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -503,14 +645,28 @@ 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(' ', '').strip('\r\n').replace('\n', '\r\n')
|
).replace(
|
||||||
|
' ', ''
|
||||||
|
).strip(
|
||||||
|
'\r\n'
|
||||||
|
).replace(
|
||||||
|
'\n', '\r\n'
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def flatten(ii):
|
def flatten(interval_list):
|
||||||
return functools.reduce(lambda reduced, i: (
|
return functools.reduce(
|
||||||
(reduced[:-1] + i.combine(reduced[-1]))
|
lambda reduced,
|
||||||
if reduced else [i]
|
maya_interval: (
|
||||||
), sorted(ii), [])
|
(
|
||||||
|
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):
|
||||||
@@ -525,7 +681,7 @@ def now():
|
|||||||
return MayaDT(epoch=epoch)
|
return MayaDT(epoch=epoch)
|
||||||
|
|
||||||
|
|
||||||
def when(string, timezone='UTC'):
|
def when(string, timezone='UTC', prefer_past=False):
|
||||||
""""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.
|
||||||
@@ -536,44 +692,75 @@ def when(string, timezone='UTC'):
|
|||||||
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
|
||||||
|
|
||||||
"""
|
"""
|
||||||
dt = dateparser.parse(string,
|
settings = {
|
||||||
settings={'TIMEZONE': timezone, 'RETURN_AS_TIMEZONE_AWARE': True, 'TO_TIMEZONE': 'UTC'})
|
'TIMEZONE': timezone,
|
||||||
|
'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, day_first=False):
|
def parse(string, timezone='UTC', day_first=False, year_first=True):
|
||||||
""""Returns a MayaDT instance for the machine-produced moment specified.
|
""""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:
|
Keyword Arguments:
|
||||||
string -- string to be parsed
|
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)
|
||||||
"""
|
"""
|
||||||
dt = pendulum.parse(string, day_first=day_first)
|
options = {}
|
||||||
|
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(s):
|
def _seconds_or_timedelta(duration):
|
||||||
# Convert seconds into timedelta.
|
"""Returns `datetime.timedelta` object for the passed duration.
|
||||||
if isinstance(s, int):
|
|
||||||
s = timedelta(seconds=s)
|
|
||||||
|
|
||||||
return s
|
Keyword Arguments:
|
||||||
|
duration -- `datetime.timedelta` object or seconds in `int` format.
|
||||||
|
"""
|
||||||
|
if isinstance(duration, int):
|
||||||
|
dt_timedelta = timedelta(seconds=duration)
|
||||||
|
elif isinstance(duration, timedelta):
|
||||||
|
dt_timedelta = duration
|
||||||
|
else:
|
||||||
|
raise TypeError(
|
||||||
|
'Expects argument as `datetime.timedelta` object '
|
||||||
|
'or seconds in `int` format'
|
||||||
|
)
|
||||||
|
|
||||||
|
return dt_timedelta
|
||||||
|
|
||||||
|
|
||||||
def intervals(start, end, interval):
|
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,
|
||||||
interval = seconds_or_timedelta(interval)
|
at a given interval (seconds or timedelta).
|
||||||
|
"""
|
||||||
|
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)
|
||||||
|
|||||||
@@ -1,20 +1,16 @@
|
|||||||
#!/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 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:
|
with codecs.open(os.path.join(here, 'README.rst'), encoding='utf-8') as f:
|
||||||
long_description = '\n' + f.read()
|
long_description = '\n' + f.read()
|
||||||
@@ -30,21 +26,82 @@ required = [
|
|||||||
'dateparser',
|
'dateparser',
|
||||||
'ruamel.yaml',
|
'ruamel.yaml',
|
||||||
'tzlocal',
|
'tzlocal',
|
||||||
'pendulum'
|
'pendulum',
|
||||||
|
'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 codecs.open(os.path.join(here, 'maya', '__version__.py'), 'r', encoding='utf-8') as f:
|
||||||
|
exec(f.read(), about)
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='maya',
|
name='maya',
|
||||||
version='0.3.1',
|
version=about['__version__'],
|
||||||
description='Datetimes for Humans.',
|
description='Datetimes for Humans.',
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
author='Kenneth Reitz',
|
author='Kenneth Reitz',
|
||||||
author_email='me@kennethreitz.org',
|
author_email='me@kennethreitz.org',
|
||||||
url='https://github.com/kennethreitz/maya',
|
url='https://github.com/kennethreitz/maya',
|
||||||
packages=['maya'],
|
packages=packages,
|
||||||
install_requires=required,
|
install_requires=required,
|
||||||
license='MIT',
|
license='MIT',
|
||||||
classifiers=(
|
classifiers=(
|
||||||
|
'Development Status :: 5 - Production/Stable',
|
||||||
|
'Intended Audience :: Developers',
|
||||||
|
'License :: OSI Approved :: MIT License',
|
||||||
|
'Natural Language :: English',
|
||||||
|
'Operating System :: OS Independent',
|
||||||
|
'Programming Language :: Python :: 2',
|
||||||
|
'Programming Language :: Python :: 2.7',
|
||||||
|
'Programming Language :: Python :: 3',
|
||||||
|
'Programming Language :: Python :: 3.6',
|
||||||
|
'Programming Language :: Python :: Implementation',
|
||||||
|
'Programming Language :: Python :: Implementation :: CPython',
|
||||||
|
'Topic :: Software Development :: Libraries :: Python Modules'
|
||||||
),
|
),
|
||||||
|
cmdclass={
|
||||||
|
'upload': UploadCommand,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import pytest
|
|
||||||
import copy
|
import copy
|
||||||
from datetime import timedelta
|
import time
|
||||||
|
from datetime import timedelta, datetime as Datetime
|
||||||
|
|
||||||
|
import pytz
|
||||||
|
import pytest
|
||||||
|
|
||||||
import maya
|
import maya
|
||||||
|
from maya.core import _seconds_or_timedelta # import private function
|
||||||
|
|
||||||
|
|
||||||
def test_rfc2822():
|
def test_rfc2822():
|
||||||
@@ -23,57 +27,68 @@ 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
|
||||||
|
|
||||||
@@ -87,14 +102,12 @@ 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
|
||||||
@@ -106,7 +119,6 @@ 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
|
||||||
@@ -122,10 +134,8 @@ 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>'
|
||||||
|
|
||||||
@@ -141,27 +151,47 @@ def test_slang_date():
|
|||||||
|
|
||||||
|
|
||||||
def test_slang_time():
|
def test_slang_time():
|
||||||
d = maya.when('one hour ago')
|
d = maya.when('1 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
|
||||||
@@ -171,25 +201,18 @@ 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
|
||||||
@@ -204,11 +227,21 @@ def test_comparison_operations():
|
|||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
now >= 1
|
now >= 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_seconds_or_timedelta():
|
||||||
|
# test for value in seconds
|
||||||
|
assert _seconds_or_timedelta(1234) == timedelta(0, 1234)
|
||||||
|
# test for value as `datetime.timedelta`
|
||||||
|
assert _seconds_or_timedelta(timedelta(0, 1234)) == timedelta(0, 1234)
|
||||||
|
# test for invalid value
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
_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():
|
||||||
@@ -227,3 +260,37 @@ 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')
|
||||||
@@ -15,26 +15,20 @@ 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)
|
||||||
@@ -64,7 +58,9 @@ def test_interval_init_end_duration():
|
|||||||
assert interval.start == end.subtract(seconds=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, 1, 3, (1, 2)),
|
||||||
(0, 2, 3, 4, None),
|
(0, 2, 3, 4, None),
|
||||||
(0, 2, 2, 3, None),
|
(0, 2, 2, 3, None),
|
||||||
@@ -76,7 +72,8 @@ 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',
|
||||||
@@ -87,21 +84,19 @@ 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=start_doy1), base.add(days=end_doy1)
|
||||||
base.add(days=end_doy1),
|
|
||||||
)
|
)
|
||||||
interval2 = maya.MayaInterval(
|
interval2 = maya.MayaInterval(
|
||||||
base.add(days=start_doy2),
|
base.add(days=start_doy2), base.add(days=end_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(
|
||||||
@@ -110,45 +105,54 @@ def test_interval_intersection(
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
assert (interval1 & interval2) is None
|
assert (interval1 & interval2) is None
|
||||||
|
# check invalid argument
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
interval1 & 'invalid type'
|
||||||
|
|
||||||
|
|
||||||
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(maya.MayaInterval(
|
assert not interval.intersects(
|
||||||
base.add(days=2),
|
maya.MayaInterval(base.add(days=2), base.add(days=3))
|
||||||
base.add(days=3),
|
)
|
||||||
))
|
# check invalid argument
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
interval.intersects('invalid type')
|
||||||
|
|
||||||
|
|
||||||
def test_and_operator():
|
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
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
interval1.intersection('invalid type')
|
||||||
|
|
||||||
|
|
||||||
def test_interval_eq_operator():
|
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
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
interval == 'invalid type'
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
interval != 'invalid type'
|
||||||
|
|
||||||
|
|
||||||
def test_interval_timedelta():
|
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
|
||||||
|
|
||||||
|
|
||||||
@@ -156,147 +160,142 @@ 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('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, 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=(
|
),
|
||||||
'overlapping',
|
ids=('overlapping', 'non-overlapping', 'adjacent', 'equal', 'subset'),
|
||||||
'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=start_doy1), base.add(days=end_doy1)
|
||||||
base.add(days=end_doy1),
|
|
||||||
)
|
)
|
||||||
interval2 = maya.MayaInterval(
|
interval2 = maya.MayaInterval(
|
||||||
base.add(days=start_doy2),
|
base.add(days=start_doy2), base.add(days=end_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
|
||||||
|
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, 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=(
|
),
|
||||||
'before-start',
|
ids=('before-start', 'on-start', 'during', 'on-end', 'after-end'),
|
||||||
'on-start',
|
)
|
||||||
'during',
|
def test_interval_in_operator_maya_dt(start_doy, end_doy, dt_doy, expected):
|
||||||
'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),
|
start=base.add(days=start_doy), end=base.add(days=end_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
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
'invalid type' in interval
|
||||||
|
|
||||||
|
|
||||||
def test_interval_hash():
|
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(maya.MayaInterval(
|
assert hash(interval) != hash(
|
||||||
start=start, end=end.add(days=1)))
|
maya.MayaInterval(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('start1,end1,start2,end2,expected', [
|
@pytest.mark.parametrize(
|
||||||
(1, 2, 1, 2, 0),
|
'start1,end1,start2,end2,expected',
|
||||||
(1, 3, 2, 4, -1),
|
[(1, 2, 1, 2, 0), (1, 3, 2, 4, -1), (2, 4, 1, 3, 1), (1, 2, 1, 3, -1)],
|
||||||
(2, 4, 1, 3, 1),
|
ids=(
|
||||||
(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),
|
start=base.add(days=start1), end=base.add(days=end1)
|
||||||
end=base.add(days=end1),
|
|
||||||
)
|
)
|
||||||
interval2 = maya.MayaInterval(
|
interval2 = maya.MayaInterval(
|
||||||
start=base.add(days=start2),
|
start=base.add(days=start2), end=base.add(days=end2)
|
||||||
end=base.add(days=end2),
|
|
||||||
)
|
)
|
||||||
assert cmp(interval1, interval2) == expected
|
assert cmp(interval1, interval2) == expected
|
||||||
|
# check invalid argument
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
cmp(interval1, 'invalid type')
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('start1,end1,start2,end2,expected', [
|
@pytest.mark.parametrize(
|
||||||
(1, 2, 2, 3, [(1, 3)]),
|
'start1,end1,start2,end2,expected',
|
||||||
(1, 3, 2, 4, [(1, 4)]),
|
[
|
||||||
(1, 2, 3, 4, [(1, 2), (3, 4)]),
|
(1, 2, 2, 3, [(1, 3)]),
|
||||||
(1, 5, 2, 3, [(1, 5)]),
|
(1, 3, 2, 4, [(1, 4)]),
|
||||||
], ids=(
|
(1, 2, 3, 4, [(1, 2), (3, 4)]),
|
||||||
'adjacent',
|
(1, 5, 2, 3, [(1, 5)]),
|
||||||
'overlapping',
|
],
|
||||||
'non-overlapping',
|
ids=('adjacent', 'overlapping', 'non-overlapping', 'contains'),
|
||||||
'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),
|
start=base.add(days=start1), end=base.add(days=end1)
|
||||||
end=base.add(days=end1),
|
|
||||||
)
|
)
|
||||||
interval2 = maya.MayaInterval(
|
interval2 = maya.MayaInterval(
|
||||||
start=base.add(days=start2),
|
start=base.add(days=start2), end=base.add(days=end2)
|
||||||
end=base.add(days=end2),
|
|
||||||
)
|
)
|
||||||
expected_intervals = [maya.MayaInterval(
|
expected_intervals = [
|
||||||
start=base.add(days=start),
|
maya.MayaInterval(start=base.add(days=start), end=base.add(days=end))
|
||||||
end=base.add(days=end),
|
for start, end in expected
|
||||||
) 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
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
interval2.combine('invalid type')
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('start1,end1,start2,end2,expected', [
|
@pytest.mark.parametrize(
|
||||||
(1, 2, 3, 4, [(1, 2)]),
|
'start1,end1,start2,end2,expected',
|
||||||
(1, 2, 2, 4, [(1, 2)]),
|
[
|
||||||
(2, 3, 1, 4, []),
|
(1, 2, 3, 4, [(1, 2)]),
|
||||||
(1, 4, 2, 3, [(1, 2), (3, 4)]),
|
(1, 2, 2, 4, [(1, 2)]),
|
||||||
(1, 4, 0, 2, [(2, 4)]),
|
(2, 3, 1, 4, []),
|
||||||
(1, 4, 3, 5, [(1, 3)]),
|
(1, 4, 2, 3, [(1, 2), (3, 4)]),
|
||||||
(1, 4, 1, 2, [(2, 4)]),
|
(1, 4, 0, 2, [(2, 4)]),
|
||||||
(1, 4, 3, 4, [(1, 3)]),
|
(1, 4, 3, 5, [(1, 3)]),
|
||||||
], ids=(
|
(1, 4, 1, 2, [(2, 4)]),
|
||||||
|
(1, 4, 3, 4, [(1, 3)]),
|
||||||
|
],
|
||||||
|
ids=(
|
||||||
'non-overlapping',
|
'non-overlapping',
|
||||||
'adjacent',
|
'adjacent',
|
||||||
'contains',
|
'contains',
|
||||||
@@ -305,107 +304,109 @@ 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),
|
start=base.add(days=start1), end=base.add(days=end1)
|
||||||
end=base.add(days=end1),
|
|
||||||
)
|
)
|
||||||
interval2 = maya.MayaInterval(
|
interval2 = maya.MayaInterval(
|
||||||
start=base.add(days=start2),
|
start=base.add(days=start2), end=base.add(days=end2)
|
||||||
end=base.add(days=end2),
|
|
||||||
)
|
)
|
||||||
expected_intervals = [maya.MayaInterval(
|
expected_intervals = [
|
||||||
start=base.add(days=start),
|
maya.MayaInterval(start=base.add(days=start), end=base.add(days=end))
|
||||||
end=base.add(days=end),
|
for start, end in expected
|
||||||
) for start, end in expected]
|
]
|
||||||
|
|
||||||
assert interval1.subtract(interval2) == expected_intervals
|
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', [
|
@pytest.mark.parametrize(
|
||||||
(1, 2, 2, 3, True),
|
'start1,end1,start2,end2,expected',
|
||||||
(2, 3, 1, 2, True),
|
[
|
||||||
(1, 3, 2, 3, False),
|
(1, 2, 2, 3, True),
|
||||||
(2, 3, 4, 5, False),
|
(2, 3, 1, 2, True),
|
||||||
], ids=(
|
(1, 3, 2, 3, False),
|
||||||
'adjacent-right',
|
(2, 3, 4, 5, False),
|
||||||
'adjacent-left',
|
],
|
||||||
'overlapping',
|
ids=('adjacent-right', 'adjacent-left', 'overlapping', 'non-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),
|
start=base.add(days=start1), end=base.add(days=end1)
|
||||||
end=base.add(days=end1),
|
|
||||||
)
|
)
|
||||||
interval2 = maya.MayaInterval(
|
interval2 = maya.MayaInterval(
|
||||||
start=base.add(days=start2),
|
start=base.add(days=start2), end=base.add(days=end2)
|
||||||
end=base.add(days=end2),
|
|
||||||
)
|
)
|
||||||
assert interval1.is_adjacent(interval2) == expected
|
assert interval1.is_adjacent(interval2) == expected
|
||||||
|
# check invalid argument
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
interval1.is_adjacent('invalid type')
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('start,end,delta,include_remainder,expected', [
|
@pytest.mark.parametrize(
|
||||||
(0, 10, 5, False, [(0, 5), (5, 10)]),
|
'start,end,delta,include_remainder,expected',
|
||||||
(0, 10, 5, True, [(0, 5), (5, 10)]),
|
[
|
||||||
(0, 10, 3, False, [(0, 3), (3, 6), (6, 9)]),
|
(0, 10, 5, False, [(0, 5), (5, 10)]),
|
||||||
(0, 10, 3, True, [(0, 3), (3, 6), (6, 9), (9, 10)]),
|
(0, 10, 5, True, [(0, 5), (5, 10)]),
|
||||||
(0, 2, 5, False, []),
|
(0, 10, 3, False, [(0, 3), (3, 6), (6, 9)]),
|
||||||
(0, 2, 5, True, [(0, 2)]),
|
(0, 10, 3, True, [(0, 3), (3, 6), (6, 9), (9, 10)]),
|
||||||
], ids=(
|
(0, 2, 5, False, []),
|
||||||
|
(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),
|
start=base.add(days=start), end=base.add(days=end)
|
||||||
end=base.add(days=end),
|
|
||||||
)
|
)
|
||||||
delta = timedelta(days=delta)
|
delta = timedelta(days=delta)
|
||||||
|
|
||||||
expected_intervals = [
|
expected_intervals = [
|
||||||
maya.MayaInterval(
|
maya.MayaInterval(start=base.add(days=s), end=base.add(days=e))
|
||||||
start=base.add(days=s),
|
for s, e in expected
|
||||||
end=base.add(days=e),
|
|
||||||
) for s, e in expected
|
|
||||||
]
|
]
|
||||||
|
assert expected_intervals == list(
|
||||||
assert expected_intervals == list(interval.split(
|
interval.split(delta, include_remainder=include_remainder)
|
||||||
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(AssertionError):
|
|
||||||
list(interval.split(timedelta(seconds=0)))
|
list(interval.split(timedelta(seconds=0)))
|
||||||
|
with pytest.raises(ValueError):
|
||||||
with pytest.raises(AssertionError):
|
|
||||||
list(interval.split(timedelta(seconds=-10)))
|
list(interval.split(timedelta(seconds=-10)))
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('start,end,minutes,timezone,snap_out,expected_start,expected_end', [
|
@pytest.mark.parametrize(
|
||||||
((5, 12), (8, 48), 30, None, False, (5, 30), (8, 30)),
|
'start,end,minutes,timezone,snap_out,expected_start,expected_end',
|
||||||
((5, 12), (8, 48), 30, None, True, (5, 0), (9, 0)),
|
[
|
||||||
((5, 15), (9, 0), 15, None, False, (5, 15), (9, 0)),
|
((5, 12), (8, 48), 30, None, False, (5, 30), (8, 30)),
|
||||||
((5, 15), (9, 0), 15, None, True, (5, 15), (9, 0)),
|
((5, 12), (8, 48), 30, None, True, (5, 0), (9, 0)),
|
||||||
((6, 50), (9, 15), 60, 'America/New_York', False, (7, 0), (9, 0)),
|
((5, 15), (9, 0), 15, None, False, (5, 15), (9, 0)),
|
||||||
((6, 50), (9, 15), 60, 'America/New_York', True, (6, 0), (10, 0)),
|
((5, 15), (9, 0), 15, None, True, (5, 15), (9, 0)),
|
||||||
((6, 20), (6, 50), 60, None, False, (6, 0), (6, 0)),
|
((6, 50), (9, 15), 60, 'America/New_York', False, (7, 0), (9, 0)),
|
||||||
((6, 20), (6, 50), 60, None, True, (6, 0), (7, 0)),
|
((6, 50), (9, 15), 60, 'America/New_York', True, (6, 0), (10, 0)),
|
||||||
((6, 20), (6, 50), 60, 'America/Chicago', False, (6, 0), (6, 0)),
|
((6, 20), (6, 50), 60, None, False, (6, 0), (6, 0)),
|
||||||
((6, 20), (6, 50), 60, 'America/Chicago', True, (6, 0), (7, 0)),
|
((6, 20), (6, 50), 60, None, True, (6, 0), (7, 0)),
|
||||||
], ids=(
|
((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',
|
||||||
'normal-snap_out',
|
'normal-snap_out',
|
||||||
'already-quantized',
|
'already-quantized',
|
||||||
@@ -416,21 +417,20 @@ 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),
|
timedelta(minutes=minutes), snap_out=snap_out, **kwargs
|
||||||
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,10 +441,9 @@ 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(AssertionError):
|
|
||||||
interval.quantize(timedelta(minutes=0))
|
interval.quantize(timedelta(minutes=0))
|
||||||
with pytest.raises(AssertionError):
|
with pytest.raises(ValueError):
|
||||||
interval.quantize(timedelta(minutes=-1))
|
interval.quantize(timedelta(minutes=-1))
|
||||||
|
|
||||||
|
|
||||||
@@ -452,12 +451,13 @@ def test_interval_flatten_non_overlapping():
|
|||||||
step = 2
|
step = 2
|
||||||
max_hour = 20
|
max_hour = 20
|
||||||
base = maya.now()
|
base = maya.now()
|
||||||
intervals = [maya.MayaInterval(
|
intervals = [
|
||||||
start=base.add(hours=hour),
|
maya.MayaInterval(
|
||||||
duration=timedelta(hours=step - 1),
|
start=base.add(hours=hour), 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 +465,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),
|
start=base.add(hours=hour), duration=timedelta(hours=step)
|
||||||
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) == [
|
||||||
assert maya.MayaInterval.flatten(intervals) == [maya.MayaInterval(
|
maya.MayaInterval(start=base, duration=timedelta(hours=max_hour))
|
||||||
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 = [maya.MayaInterval(
|
intervals = [
|
||||||
start=base.add(hours=hour),
|
maya.MayaInterval(
|
||||||
duration=timedelta(hours=step, minutes=30),
|
start=base.add(hours=hour),
|
||||||
) for hour in range(0, max_hour, step)]
|
duration=timedelta(hours=step, minutes=30),
|
||||||
|
)
|
||||||
|
for hour in range(0, max_hour, step)
|
||||||
|
]
|
||||||
random.shuffle(intervals)
|
random.shuffle(intervals)
|
||||||
|
assert maya.MayaInterval.flatten(intervals) == [
|
||||||
assert maya.MayaInterval.flatten(intervals) == [maya.MayaInterval(
|
maya.MayaInterval(
|
||||||
start=base,
|
start=base, duration=timedelta(hours=max_hour, minutes=30)
|
||||||
duration=timedelta(hours=max_hour, minutes=30),
|
)
|
||||||
)]
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_interval_flatten_containing():
|
def test_interval_flatten_containing():
|
||||||
@@ -501,16 +501,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,
|
start=base, end=base.add(hours=max_hour + step)
|
||||||
end=base.add(hours=max_hour + step),
|
|
||||||
)
|
)
|
||||||
intervals = [maya.MayaInterval(
|
intervals = [
|
||||||
start=base.add(hours=hour),
|
maya.MayaInterval(
|
||||||
duration=timedelta(hours=step - 1),
|
start=base.add(hours=hour), 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,24 +518,55 @@ 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),
|
start_dt=start.datetime(naive=False), end_dt=end.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),
|
start_dt=start.datetime(naive=False), duration=duration
|
||||||
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),
|
end_dt=end.datetime(naive=False), duration=duration
|
||||||
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
|
||||||
Reference in New Issue
Block a user