Compare commits

...

63 Commits

Author SHA1 Message Date
Timo Furrer 04a82f3078 Add Python 3.7 to test matrix 2018-03-31 11:46:05 +02:00
Timo Furrer 5f6b5fc66d Use UTC offset from given time struct when converting from time struct. Fixes #104 2018-03-31 11:42:07 +02:00
Timo Furrer 15a5c9eedb Merge pull request #130 from bsdtux/master
PR for issue #129. Attempt to secure _EPOCH_START
2018-03-30 11:19:14 +02:00
Josh Stephens 7c5d5871d3 * moved _EPOCH_START to be a class variable to protect from a user
makeing an arbitrary change which could render inaccurate results from MayaDT class
2018-03-25 16:06:38 -05:00
kennethreitz b411d5e6d1 Merge pull request #127 from matmunn/duration
Parsing an ISO8601 duration for an interval
2018-03-21 06:30:04 -04:00
Mat Munn 5458b0bf15 Parsing an ISO8601 duration for an interval 2018-03-21 11:23:22 +11:00
Timo Furrer 02f91de523 Merge pull request #124 from matmunn/master
Basic implementation of ISO 8601 parsing for MayaInterval
2018-03-18 12:30:41 +01:00
Mat Munn 00d174c9e2 Merge branch 'master' into master 2018-03-18 08:04:51 +11:00
Mat Munn f87f705a53 Added test 2018-03-18 08:03:34 +11:00
Timo Furrer 9a850b4212 PEP8 formatting by white
Used `white maya/ tests/`.
2018-03-17 12:24:18 +01:00
Timo Furrer 84a5f700c3 Merge pull request #126 from timofurrer/feature/snap
Implement snap modifiers with maya.snap(). Closes #94
2018-03-17 11:59:45 +01:00
Timo Furrer 13e9f2d896 Implement snap modifiers with maya.snap(). Closes #94 2018-03-17 11:55:18 +01:00
Timo Furrer 0c206ad0db Fix tests 2018-03-17 11:52:47 +01:00
Timo Furrer aa87723a0c Make tests and docs a phony makefile target 2018-03-14 20:17:35 +01:00
Mat Munn d51a6a57ec Basic implementation of ISO 8601 parsing for MayaInterval 2018-03-14 10:13:50 +11:00
kennethreitz 3cb65f227c setup.py update
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-02-26 17:57:57 -05:00
kennethreitz 0108ebe07e 0.3.4
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-02-26 17:31:33 -05:00
kennethreitz a7d89be3c7 Merge pull request #118 from tbarron/mayadt_subtraction
Mayadt subtraction
2018-02-26 17:30:34 -05:00
kennethreitz 99ffb773bb Merge branch 'master' into mayadt_subtraction 2018-02-26 17:29:34 -05:00
kennethreitz b3293f898e Merge pull request #119 from tbarron/issue_104
Issue 104
2018-02-26 17:29:07 -05:00
kennethreitz 2d8015a13b Merge pull request #120 from radek-sprta/patch-1
Add date property to MayaDT
2018-02-26 17:28:08 -05:00
radek-sprta 9066ee862d Add date property to MayaDT 2018-02-03 12:37:25 +01:00
Tom Barron 5f362a8968 Adding myself to the AUTHORS file 2018-01-29 18:20:41 -05:00
Tom Barron 565fdd6a9e Add myself to AUTHORS file 2018-01-29 18:18:52 -05:00
Tom Barron 24063b5e1b New test and code to address issue 104 2018-01-29 18:10:36 -05:00
Tom Barron b57f4a6775 Proposed update to support MayaDT - MayaDT -> datetime.timedelta 2018-01-29 13:37:31 -05:00
kennethreitz 91e7f499e2 Merge pull request #111 from zed/patch-1
there is no os.dirname only os.path.dirname
2017-12-18 17:28:29 -05:00
kennethreitz be3a349b16 Merge pull request #113 from timofurrer/bugfix/open-encoding
Use codecs.open when relying on encoding
2017-12-18 17:28:20 -05:00
Timo Furrer f69a93b110 Use codecs.open when relying on encoding. Fixes #112 2017-12-18 18:59:58 +01:00
zed 86586e733c there is no os.dirname only os.path.dirname 2017-12-08 15:35:30 +03:00
kennethreitz cd7b5b4aae Merge pull request #109 from alysivji/add_pendulum_tz
Add timezone option for parse()
2017-12-03 13:47:31 -06:00
kennethreitz b70884a1ec Merge pull request #110 from zolrath/fix-prefer-past-test
Fix prefer_past test in December
2017-12-03 06:34:50 -06:00
Matt Furden 773cdecab5 Fix prefer_past test at end of year 2017-12-03 00:47:12 -08:00
Aly Sivji ca865cd840 Add timezone parameter to maya.parse() 2017-12-03 00:06:41 -06:00
kennethreitz 5cf40b2d2e Merge pull request #108 from timofurrer/feature/parse-year-first
Support year_first in maya.parse. Closes #102
2017-11-21 11:32:41 -05:00
Timo Furrer 93152fa7f4 Support year_first in maya.parse. Closes #102
This change adds support for a `year_first` keyword argument to the
`maya.parse` function. `maya` will forward this argument to pendulum
which then lets `dateutil.parse` do the work.
2017-11-21 13:19:55 +01:00
kennethreitz baa0660a9b Merge pull request #103 from zolrath/prefer-past
Add prefer_past option for when
2017-11-20 10:20:39 -05:00
Matt Furden fd62815ce5 Fix test variable name change 2017-10-21 09:42:23 -07:00
Matt Furden c0092e74ae Add prefer_past option for when
When parsing dates from websites when encountering ambiguous dates like "December 12th" this is interpreted in the future (when the current date is in October).

When parsing dates that we know must be in the past this is not the correct behavior.

Add a `prefer_past` keyword argument to `when` to allow the user to ensure the date is parsed as being in the past.
2017-10-15 21:49:03 -07:00
kennethreitz 79d017fdcf Merge pull request #100 from amalmurali47/patch-1
Fix typos and correct spelling.
2017-09-21 10:15:42 -04:00
Amal Murali 7331d0d855 Fix typos and correct spelling. 2017-09-21 18:02:15 +05:30
kennethreitz 9c90c0534a Merge pull request #99 from Miserlou/master
Fixes #98 - add support for time struct with test and docs
2017-09-20 13:45:03 -04:00
Rich Jones b9c501b0e4 Fixes #98 - add support for time struct with test and docs 2017-09-20 19:32:37 +02:00
kennethreitz 3a71ff0174 Update README.rst 2017-09-05 13:01:06 -04:00
kennethreitz 43cc1d5946 Merge pull request #97 from dimaspivak/master
Fix MayaInterval.iso8601()
2017-09-02 15:14:51 -04:00
Dima Spivak aecb643beb Add Dima Spivak to AUTHORS.rst 2017-09-02 11:50:27 -07:00
Dima Spivak fa966900e1 Fix MayaInterval.iso8601() 2017-09-02 11:47:32 -07:00
kennethreitz 892e589ef2 Update README.rst 2017-08-31 03:13:35 -04:00
kennethreitz 0f795738c0 Update README.rst 2017-08-31 03:13:16 -04:00
kennethreitz b333489081 Merge pull request #96 from stsievert/patch-1
Add XKCD #1883 to README
2017-08-30 13:12:11 -04:00
Scott Sievert 17a450eb5d Add XKCD #1883 to README 2017-08-30 12:06:08 -05:00
kennethreitz 8cd2158567 Merge pull request #87 from endast/patch-1
Update README.rst
2017-08-26 15:32:11 -04:00
kennethreitz 883b1b9b92 Merge pull request #90 from ibigpapa/master
Fix for issue #89
2017-08-26 15:31:15 -04:00
kennethreitz 414df5f3f5 Merge pull request #93 from timofurrer/patch-1
Fix typo in README. Closes #92
2017-07-28 13:46:33 -04:00
Timo Furrer 1694ed7cf8 Fix typo in README. Closes #92 2017-07-28 10:00:06 +02:00
Troy Harrison cbe9f6bae7 Adds test for issue #89 2017-06-28 14:27:57 -05:00
Troy Harrison 8bdd5c65fa Fixes issue #89 2017-06-28 14:26:54 -05:00
Magnus Wahlberg 4d96d06d70 Update README.rst
The example for intervals is missing an "s"

>>> import maya
>>> maya.interval(start=maya.now(), end=maya.now().add(days=1), interval=60*60)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'maya' has no attribute 'interval'
>>> maya.intervals(start=maya.now(), end=maya.now().add(days=1), interval=60*60)
<generator object intervals at 0x106acc0a0>
2017-06-23 08:44:20 +02:00
kennethreitz bbdb9b8762 Merge pull request #79 from robcarrington/fix-setup
Fixed setup.py bug by turning encoding into a keyword argument. Encod…
2017-05-30 22:12:00 -04:00
Robert Carrington 28ecad81bd Fixed setup.py bug by turning encoding into a keyword argument. Encoding parameter was being passed in the position belonging to the buffering parameter 2017-05-30 21:03:35 -05:00
kennethreitz c56c552184 Merge pull request #78 from moin18/seconds_or_timedelta_private
"seconds_or_timedelta" as private
2017-05-30 10:51:31 -04:00
Moin d5e4853886 updated test to use private _seconds_or_timedelta 2017-05-30 20:03:20 +05:30
Moin 28b3a849a9 declare seconds_or_timedelta as private function 2017-05-30 19:58:33 +05:30
12 changed files with 909 additions and 415 deletions
+6
View File
@@ -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 tests/ script: pipenv run pytest tests/
+2
View File
@@ -22,3 +22,5 @@ In chronological order:
- Joshua Li <joshua.r.li.98@gmail.com> (`@JoshuaRLi <https://github.com/JoshuaRLi>`_) - Joshua Li <joshua.r.li.98@gmail.com> (`@JoshuaRLi <https://github.com/JoshuaRLi>`_)
- Sébastien Eustace <sebastien@eustace.io> (`@sdispater <https://github.com/sdispater>`_) - Sébastien Eustace <sebastien@eustace.io> (`@sdispater <https://github.com/sdispater>`_)
- Evan Mattiza <emattiza@gmail.com> (`@emattiza <https://github.com/emattiza>`_) - Evan Mattiza <emattiza@gmail.com> (`@emattiza <https://github.com/emattiza>`_)
- Dima Spivak <dima@spivak.ch> (`@dimaspivak <https://github.com/dimaspivak>`_)
- Tom Barron <tusculum@gmail.com> (`@dtbarron <https://github.com/tbarron>`_)
+2
View File
@@ -1,3 +1,5 @@
.PHONY: tests docs
tests: tests:
pytest tests/ pytest tests/
docs: docs:
+5 -1
View File
@@ -1,11 +1,15 @@
[dev-packages] [dev-packages]
pytest = "*" pytest = "*"
Sphinx = "*" sphinx = "*"
[packages] [packages]
humanize = "*" humanize = "*"
pytz = "*" pytz = "*"
dateparser = "*" dateparser = "*"
"ruamel.yaml" = "*" "ruamel.yaml" = "*"
tzlocal = "*" tzlocal = "*"
pendulum = ">=1.0" pendulum = ">=1.0"
snaptime = "*"
Generated
+266 -21
View File
@@ -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 -6
View File
@@ -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,7 +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.
@@ -113,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.
@@ -125,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
✨🍰✨ ✨🍰✨
+1 -1
View File
@@ -1 +1 @@
__version__ = '0.3.2' __version__ = '0.3.4'
+11 -9
View File
@@ -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__
+247 -111
View File
@@ -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,19 +9,20 @@ 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):
""" """
@@ -31,14 +31,20 @@ def validate_class_type_arguments(operator):
""" """
def inner(function): def inner(function):
def wrapper(self, *args, **kwargs): def wrapper(self, *args, **kwargs):
for arg in args + tuple(kwargs.values()): for arg in args + tuple(kwargs.values()):
if not isinstance(arg, self.__class__): if not isinstance(arg, self.__class__):
raise TypeError('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 function(self, *args, **kwargs)
return wrapper return wrapper
return inner return inner
@@ -51,26 +57,34 @@ def validate_arguments_type_of_function(param_type=None):
Note: Use this decorator on the functions of the class. Note: Use this decorator on the functions of the class.
""" """
def inner(function): def inner(function):
def wrapper(self, *args, **kwargs): def wrapper(self, *args, **kwargs):
type_ = param_type or type(self) type_ = param_type or type(self)
for arg in args + tuple(kwargs.values()): for arg in args + tuple(kwargs.values()):
if not isinstance(arg, type_): if not isinstance(arg, type_):
raise TypeError(('Invalid Type: {}.{}() accepts only the ' raise TypeError(
'arguments of type "<{}>"').format( (
type(self).__name__, 'Invalid Type: {}.{}() accepts only the '
function.__name__, 'arguments of type "<{}>"'
type_.__name__, ).format(
) type(self).__name__,
) function.__name__,
type_.__name__,
)
)
return function(self, *args, **kwargs) return function(self, *args, **kwargs)
return wrapper return wrapper
return inner return inner
class MayaDT(object): class MayaDT(object):
"""The Maya Datetime object.""" """The Maya Datetime object."""
__EPOCH_START = (1970, 1, 1)
def __init__(self, epoch): def __init__(self, epoch):
super(MayaDT, self).__init__() super(MayaDT, self).__init__()
@@ -114,26 +128,49 @@ class MayaDT(object):
return hash(int(self.epoch)) return hash(int(self.epoch))
def __add__(self, duration): def __add__(self, duration):
return self.add(seconds=seconds_or_timedelta(duration).total_seconds()) return self.add(
seconds=_seconds_or_timedelta(duration).total_seconds()
)
def __radd__(self, duration): def __radd__(self, duration):
return self + duration return self + duration
def __sub__(self, duration): def __sub__(self, duration_or_date):
return self.subtract( if isinstance(duration_or_date, MayaDT):
seconds=seconds_or_timedelta(duration).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."""
@@ -146,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):
@@ -158,23 +198,31 @@ class MayaDT(object):
@validate_arguments_type_of_function(Datetime) @validate_arguments_type_of_function(Datetime)
def __dt_to_epoch(dt): def __dt_to_epoch(dt):
"""Converts a datetime into an epoch.""" """Converts a datetime into an epoch."""
# Assume UTC if no datetime is provided. # Assume UTC if no datetime is provided.
if dt.tzinfo is None: if dt.tzinfo is None:
dt = dt.replace(tzinfo=pytz.utc) dt = dt.replace(tzinfo=pytz.utc)
epoch_start = Datetime(*MayaDT.__EPOCH_START, tzinfo=pytz.timezone('UTC'))
epoch_start = Datetime(*_EPOCH_START, tzinfo=pytz.timezone('UTC'))
return (dt - epoch_start).total_seconds() return (dt - epoch_start).total_seconds()
# Importers # Importers
# --------- # ---------
@classmethod @classmethod
@validate_arguments_type_of_function(Datetime) @validate_arguments_type_of_function(Datetime)
def from_datetime(klass, dt): def from_datetime(klass, dt):
"""Returns MayaDT instance from datetime.""" """Returns MayaDT instance from datetime."""
return klass(klass.__dt_to_epoch(dt)) return klass(klass.__dt_to_epoch(dt))
@classmethod
@validate_arguments_type_of_function(time.struct_time)
def from_struct(klass, struct, timezone=pytz.UTC):
"""Returns MayaDT instance from a 9-tuple struct
It's assumed to be from gmtime().
"""
struct_time = time.mktime(struct) - utc_offset(struct)
dt = Datetime.fromtimestamp(struct_time, timezone)
return klass(klass.__dt_to_epoch(dt))
@classmethod @classmethod
def from_iso8601(klass, iso8601_string): def from_iso8601(klass, iso8601_string):
"""Returns MayaDT instance from iso8601 string.""" """Returns MayaDT instance from iso8601 string."""
@@ -192,28 +240,27 @@ class MayaDT(object):
# Exporters # Exporters
# --------- # ---------
def datetime(self, to_timezone=None, naive=False): def datetime(self, to_timezone=None, naive=False):
"""Returns a timezone-aware datetime... """Returns a timezone-aware datetime...
Defaulting to UTC (as it should). Defaulting to UTC (as it should).
Keyword Arguments: Keyword Arguments:
to_timezone {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):
@@ -232,7 +279,6 @@ class MayaDT(object):
# Properties # Properties
# ---------- # ----------
@property @property
def year(self): def year(self):
return self.datetime().year return self.datetime().year
@@ -245,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
@@ -276,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)
@@ -288,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)
@@ -305,40 +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.
if duration: if duration:
duration = seconds_or_timedelta(duration) duration = _seconds_or_timedelta(duration)
if not start: if not start:
start = end - duration start = end - duration
if not end: if not end:
end = start + duration end = start + duration
if start > end: if start > end:
raise ValueError('MayaInterval cannot end before it starts') raise ValueError('MayaInterval cannot end before it starts')
@@ -346,28 +427,56 @@ 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()
@validate_arguments_type_of_function() @validate_arguments_type_of_function()
def __and__(self, maya_interval): def __and__(self, maya_interval):
@@ -380,8 +489,7 @@ class MayaInterval(object):
@validate_arguments_type_of_function() @validate_arguments_type_of_function()
def __eq__(self, maya_interval): def __eq__(self, maya_interval):
return ( return (
self.start == maya_interval.start and self.start == maya_interval.start and self.end == maya_interval.end
self.end == maya_interval.end
) )
def __hash__(self): def __hash__(self):
@@ -394,8 +502,8 @@ class MayaInterval(object):
@validate_arguments_type_of_function() @validate_arguments_type_of_function()
def __cmp__(self, maya_interval): def __cmp__(self, maya_interval):
return ( return (
cmp(self.start, maya_interval.start) or cmp(self.start, maya_interval.start)
cmp(self.end, maya_interval.end) or cmp(self.end, maya_interval.end)
) )
@property @property
@@ -426,8 +534,9 @@ class MayaInterval(object):
MayaInterval( MayaInterval(
interval_list[0].start, interval_list[0].start,
max(interval_list[0].end, interval_list[1].end), max(interval_list[0].end, interval_list[1].end),
), )
] ]
return interval_list return interval_list
@validate_arguments_type_of_function() @validate_arguments_type_of_function()
@@ -435,6 +544,7 @@ class MayaInterval(object):
""""Removes the given interval.""" """"Removes the given interval."""
if not self & maya_interval: if not self & maya_interval:
return [self] return [self]
elif maya_interval.contains(self): elif maya_interval.contains(self):
return [] return []
@@ -446,10 +556,8 @@ class MayaInterval(object):
return interval_list return interval_list
def split(self, duration, include_remainder=True): def split(self, duration, include_remainder=True):
# Convert seconds to timedelta, if appropriate. # Convert seconds to timedelta, if appropriate.
duration = seconds_or_timedelta(duration) duration = _seconds_or_timedelta(duration)
if duration <= timedelta(seconds=0): if duration <= timedelta(seconds=0):
raise ValueError('cannot call split with a non-positive timedelta') raise ValueError('cannot call split with a non-positive timedelta')
@@ -457,38 +565,36 @@ class MayaInterval(object):
while start < self.end: while start < self.end:
if start + duration <= self.end: if start + duration <= self.end:
yield MayaInterval(start, start + duration) yield MayaInterval(start, start + duration)
elif include_remainder: elif include_remainder:
yield MayaInterval(start, self.end) yield MayaInterval(start, self.end)
start += duration start += duration
def quantize(self, duration, snap_out=False, timezone='UTC'): def quantize(self, duration, snap_out=False, timezone='UTC'):
"""Returns a quanitzed interval.""" """Returns a quanitzed interval."""
# Convert seconds to timedelta, if appropriate. # Convert seconds to timedelta, if appropriate.
duration = seconds_or_timedelta(duration) duration = _seconds_or_timedelta(duration)
timezone = pytz.timezone(timezone) timezone = pytz.timezone(timezone)
if duration <= timedelta(seconds=0): if duration <= timedelta(seconds=0):
raise ValueError('cannot quantize by non-positive timedelta') raise ValueError('cannot quantize by non-positive timedelta')
epoch = timezone.localize(Datetime(1970, 1, 1)) epoch = timezone.localize(Datetime(1970, 1, 1))
seconds = int(duration.total_seconds()) seconds = int(duration.total_seconds())
start_seconds = int(
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),
@@ -497,23 +603,17 @@ class MayaInterval(object):
@validate_arguments_type_of_function() @validate_arguments_type_of_function()
def intersection(self, maya_interval): def intersection(self, maya_interval):
"""Returns the intersection between two intervals.""" """Returns the intersection between two intervals."""
start = max(self.start, maya_interval.start) start = max(self.start, maya_interval.start)
end = min(self.end, maya_interval.end) end = min(self.end, maya_interval.end)
either_instant = self.is_instant or maya_interval.is_instant either_instant = self.is_instant or maya_interval.is_instant
instant_overlap = ( instant_overlap = (self.start == maya_interval.start or start <= end)
self.start == maya_interval.start or
start <= end
)
if (either_instant and instant_overlap) or (start < end): if (either_instant and instant_overlap) or (start < end):
return MayaInterval(start, end) return MayaInterval(start, end)
@validate_arguments_type_of_function() @validate_arguments_type_of_function()
def contains(self, maya_interval): def contains(self, maya_interval):
return ( return (
self.start <= maya_interval.start and self.start <= maya_interval.start and self.end >= maya_interval.end
self.end >= maya_interval.end
) )
def __contains__(self, maya_dt): def __contains__(self, maya_dt):
@@ -528,8 +628,7 @@ class MayaInterval(object):
@validate_arguments_type_of_function() @validate_arguments_type_of_function()
def is_adjacent(self, maya_interval): def is_adjacent(self, maya_interval):
return ( return (
self.start == maya_interval.end or self.start == maya_interval.end or self.end == maya_interval.start
self.end == maya_interval.start
) )
@property @property
@@ -546,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(interval_list): def flatten(interval_list):
return functools.reduce(lambda reduced, maya_interval: ( return functools.reduce(
(reduced[:-1] + maya_interval.combine(reduced[-1])) lambda reduced,
if reduced else [maya_interval] maya_interval: (
), sorted(interval_list), []) (
reduced[:-1] + maya_interval.combine(reduced[-1])
) if reduced else [
maya_interval
]
),
sorted(interval_list),
[],
)
@classmethod @classmethod
def from_datetime(cls, start_dt=None, end_dt=None, duration=None): def from_datetime(cls, start_dt=None, end_dt=None, duration=None):
@@ -568,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.
@@ -579,31 +692,49 @@ 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(duration): def _seconds_or_timedelta(duration):
"""Returns `datetime.timedelta` object for the passed duration. """Returns `datetime.timedelta` object for the passed duration.
Keyword Arguments: Keyword Arguments:
@@ -614,17 +745,22 @@ def seconds_or_timedelta(duration):
elif isinstance(duration, timedelta): elif isinstance(duration, timedelta):
dt_timedelta = duration dt_timedelta = duration
else: else:
raise TypeError('Expects argument as `datetime.timedelta` object ' raise TypeError(
'or seconds in `int` format') 'Expects argument as `datetime.timedelta` object '
'or seconds in `int` format'
)
return dt_timedelta return dt_timedelta
def intervals(start, end, interval): def intervals(start, end, interval):
"""Yields MayaDT objects between the start and end MayaDTs given, at a given interval (seconds or timedelta).""" """
Yields MayaDT objects between the start and end MayaDTs given,
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)
+47 -10
View File
@@ -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,16 +26,54 @@ required = [
'dateparser', 'dateparser',
'ruamel.yaml', 'ruamel.yaml',
'tzlocal', 'tzlocal',
'pendulum' 'pendulum',
'snaptime'
] ]
packages = [ packages = [
'maya', 'maya',
] ]
class UploadCommand(Command):
"""Support setup.py upload."""
description = 'Build and publish the package.'
user_options = []
@staticmethod
def status(s):
"""Prints things in bold."""
print('\033[1m{0}\033[0m'.format(s))
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
try:
self.status('Removing previous builds…')
rmtree(os.path.join(here, 'dist'))
except OSError:
pass
self.status('Building Source and Wheel (universal) distribution…')
os.system('{0} setup.py sdist bdist_wheel --universal'.format(sys.executable))
self.status('Uploading the package to PyPi via Twine…')
os.system('twine upload dist/*')
self.status('Pushing git tags…')
os.system('git tag v{0}'.format(about['__version__']))
os.system('git push --tags')
sys.exit()
# About dict to store version and package info # About dict to store version and package info
about = dict() about = dict()
with open(os.path.join(here, 'maya', '__version__.py'), 'r', 'utf-8') as f: with codecs.open(os.path.join(here, 'maya', '__version__.py'), 'r', encoding='utf-8') as f:
exec(f.read(), about) exec(f.read(), about)
setup( setup(
@@ -67,4 +101,7 @@ setup(
'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: CPython',
'Topic :: Software Development :: Libraries :: Python Modules' 'Topic :: Software Development :: Libraries :: Python Modules'
), ),
cmdclass={
'upload': UploadCommand,
},
) )
+94 -38
View File
@@ -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
@@ -207,19 +230,18 @@ def test_comparison_operations():
def test_seconds_or_timedelta(): def test_seconds_or_timedelta():
# test for value in seconds # test for value in seconds
assert maya.seconds_or_timedelta(1234) == timedelta(0, 1234) assert _seconds_or_timedelta(1234) == timedelta(0, 1234)
# test for value as `datetime.timedelta` # test for value as `datetime.timedelta`
assert maya.seconds_or_timedelta(timedelta(0, 1234)) == timedelta(0, 1234) assert _seconds_or_timedelta(timedelta(0, 1234)) == timedelta(0, 1234)
# test for invalid value # test for invalid value
with pytest.raises(TypeError): with pytest.raises(TypeError):
maya.seconds_or_timedelta('invalid interval') _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():
@@ -238,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')
+202 -214
View File
@@ -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,7 +105,6 @@ def test_interval_intersection(
) )
else: else:
assert (interval1 & interval2) is None assert (interval1 & interval2) is None
# check invalid argument # check invalid argument
with pytest.raises(TypeError): with pytest.raises(TypeError):
interval1 & 'invalid type' interval1 & 'invalid type'
@@ -119,13 +113,10 @@ def test_interval_intersection(
def test_interval_intersects(): def test_interval_intersects():
base = maya.MayaDT.from_datetime(datetime(2016, 1, 1)) base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
interval = maya.MayaInterval(base, base.add(days=1)) interval = maya.MayaInterval(base, base.add(days=1))
assert interval.intersects(interval) assert interval.intersects(interval)
assert not interval.intersects(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 # check invalid argument
with pytest.raises(TypeError): with pytest.raises(TypeError):
interval.intersects('invalid type') interval.intersects('invalid type')
@@ -135,13 +126,11 @@ def test_and_operator():
base = maya.MayaDT.from_datetime(datetime(2016, 1, 1)) base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
interval1 = maya.MayaInterval(base, base.add(days=2)) interval1 = maya.MayaInterval(base, base.add(days=2))
interval2 = maya.MayaInterval(base.add(days=1), base.add(days=3)) interval2 = maya.MayaInterval(base.add(days=1), base.add(days=3))
assert ( assert (
interval1 & interval2 == interval1 & interval2 ==
interval2 & interval1 == interval2 & interval1 ==
interval1.intersection(interval2) interval1.intersection(interval2)
) )
# check invalid argument # check invalid argument
with pytest.raises(TypeError): with pytest.raises(TypeError):
interval1.intersection('invalid type') interval1.intersection('invalid type')
@@ -151,14 +140,11 @@ def test_interval_eq_operator():
start = maya.now() start = maya.now()
end = start.add(hours=1) end = start.add(hours=1)
interval = maya.MayaInterval(start=start, end=end) interval = maya.MayaInterval(start=start, end=end)
assert interval == maya.MayaInterval(start=start, end=end) assert interval == maya.MayaInterval(start=start, end=end)
assert interval != maya.MayaInterval(start=start, end=end.add(days=1)) assert interval != maya.MayaInterval(start=start, end=end.add(days=1))
# check invalid argument # check invalid argument
with pytest.raises(TypeError): with pytest.raises(TypeError):
interval == 'invalid type' interval == 'invalid type'
with pytest.raises(TypeError): with pytest.raises(TypeError):
interval != 'invalid type' interval != 'invalid type'
@@ -167,7 +153,6 @@ 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
@@ -175,69 +160,55 @@ 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 # check invalid argument
with pytest.raises(TypeError): with pytest.raises(TypeError):
interval1.contains('invalid type') 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 # check invalid argument
with pytest.raises(TypeError): with pytest.raises(TypeError):
'invalid type' in interval 'invalid type' in interval
@@ -247,91 +218,84 @@ 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 # check invalid argument
with pytest.raises(TypeError): with pytest.raises(TypeError):
cmp(interval1, 'invalid type') 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 # check invalid argument
with pytest.raises(TypeError): with pytest.raises(TypeError):
interval2.combine('invalid type') 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',
@@ -340,115 +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 # check invalid argument
with pytest.raises(TypeError): with pytest.raises(TypeError):
interval1.subtract('invalid type') 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 # check invalid argument
with pytest.raises(TypeError): with pytest.raises(TypeError):
interval1.is_adjacent('invalid type') 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(ValueError):
list(interval.split(timedelta(seconds=0))) list(interval.split(timedelta(seconds=0)))
with pytest.raises(ValueError): with pytest.raises(ValueError):
list(interval.split(timedelta(seconds=-10))) list(interval.split(timedelta(seconds=-10)))
@pytest.mark.parametrize('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',
@@ -459,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]),
@@ -484,7 +441,6 @@ def test_quantize_invalid_delta():
start = maya.now() start = maya.now()
end = start.add(days=1) end = start.add(days=1)
interval = maya.MayaInterval(start=start, end=end) interval = maya.MayaInterval(start=start, end=end)
with pytest.raises(ValueError): with pytest.raises(ValueError):
interval.quantize(timedelta(minutes=0)) interval.quantize(timedelta(minutes=0))
with pytest.raises(ValueError): with pytest.raises(ValueError):
@@ -495,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)
@@ -508,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():
@@ -544,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]
@@ -561,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