Compare commits

...

271 Commits

Author SHA1 Message Date
Timo Furrer 7a750a1cff release: 0.4.1 2018-05-10 11:32:54 +02:00
Timo Furrer bc06315abd Limit pendulum version to >=1.0 and <= 1.5.1
See https://github.com/sdispater/pendulum/issues/205
2018-05-10 11:32:32 +02:00
Timo Furrer 7cc767781a release: 0.4.0 2018-05-10 11:13:55 +02:00
Timo Furrer 8468dd2ead Fix subtracting MayaDT instances. Fixes #151 2018-05-10 11:04:16 +02:00
kennethreitz 046f005ca7 Update README.rst 2018-05-08 08:05:07 -04:00
kennethreitz cd0b2300d7 Merge pull request #128 from azban/patch-1
readme: add artist attribution
2018-05-08 08:04:05 -04:00
Timo Furrer d3ddb39d9d Merge pull request #144 from kennethreitz/feature/cleanup-pipenv-use
Refactor pipenv usage according to semi-official best practices.
2018-05-08 08:53:31 +02:00
Timo Furrer 3c8fe4478c Merge pull request #147 from alxwrd/fix-when-weekday-names
.when with weekdays and prefer_past
2018-04-17 17:58:27 +02:00
Alex Ward a0983132bb update .when docstring 2018-04-16 12:20:14 +01:00
Alex Ward 9612d70707 update .when tests to use new prefer_dates_from param 2018-04-16 12:06:12 +01:00
Alex Ward 363fae1aaf update .when to use prefer_dates_from to align with dateparser 2018-04-16 11:39:16 +01:00
Alex Ward be2cd96132 add more options to .when pefer_past param 2018-04-15 19:22:56 +01:00
Alex Ward 771a2b6ce2 add tests around weekday names 2018-04-15 17:31:06 +01:00
Timo Furrer 8e644f655a Merge pull request #145 from vlcinsky/pendulum_ver
fix `pendulum>=1.0` in setup.py (was missing version)
2018-04-05 10:17:55 +02:00
Jan Vlcinsky 6903f6eb63 fix pendulum>=1.0 in setup.py (was missing version)
Assuming, that when Pipfile declares such rule, it shall be also in
setup.py.
2018-04-05 00:50:53 +02:00
Timo Furrer ca076ff625 Refactor pipenv usage according to semi-official best practices. Closes #142 2018-04-04 20:42:13 +02:00
Jan Vlčinský e0e33cc29f Remove probably obsolete requirement for ruamel.yaml (#136)
* Remove probably obsolete requirement for ruamel.yaml

Closes issue #134

* require dateparser>=0.7.0 (to ensure ruamel.yaml is not really needed)
2018-04-04 13:37:31 +02:00
Timo Furrer bd34915f96 Merge pull request #135 from vlcinsky/frozen_time
test with "now" frozen to different times/zones
2018-04-03 20:08:57 +02:00
Jan Vlcinsky 16690cfceb Removed Pipfile.lock 2018-04-03 19:57:58 +02:00
Jan Vlcinsky 97af369f05 Pipfile.lock updated with --dev (freezegun) 2018-04-03 19:54:18 +02:00
Jan Vlcinsky 367dac4e62 number of frozen_now variants narrowed to speed up tests 2018-04-03 19:54:18 +02:00
Jan Vlcinsky 59ec275ef6 test with "now" frozen to different times/zones
Using freezegun to freeze current times.
2018-04-03 19:54:18 +02:00
Timo Furrer 76f99bc781 Merge pull request #141 from vlcinsky/simple_pytest
Simplified call to pytest in tox.ini (issue #139)
2018-04-03 16:40:26 +02:00
Timo Furrer bf56321545 Merge branch 'master' into simple_pytest 2018-04-03 16:35:23 +02:00
Timo Furrer ee602ea56b Merge pull request #140 from vlcinsky/tox_ini
Get rid of Pipfile.lock (issue #138)
2018-04-03 16:34:35 +02:00
Jan Vlcinsky df3bdf231d Simplified call to pytest in tox.ini (issue #139) 2018-04-02 22:50:14 +02:00
Jan Vlcinsky 1a58d4f710 Get rid of Pipfile.lock (issue #138) 2018-04-02 22:43:28 +02:00
Timo Furrer d0d5a8f75d Explicitly specifiy time struct when getting UTC offset for current time 2018-04-02 12:09:31 +02:00
Timo Furrer 00a0ceb3c8 Add tox.ini for local testing. Refs #137 2018-04-01 14:09:02 +02:00
Timo Furrer ee0fe3b810 Merge pull request #131 from vlcinsky/master
test_maya.py refactored: using parametrization where feasible
2018-03-31 11:50:32 +02:00
Timo Furrer 0e0815c45a Merge pull request #132 from kennethreitz/feature/python-3.7
Add Python 3.7 to test matrix
2018-03-31 11:49:07 +02:00
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
Jan Vlcinsky e9592a3146 test_maya.py refactored: using parametrization where feasible
repeated code replaced by parameters provided by pytest.

source string and rexpected intentionally indented to match by column
to make checking year, month and date simpler.

Note, that the test_issue_104 was (and still is) failing.
2018-03-31 01:31:14 +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
azban b05ca8707c readme: add artist attribution 2018-03-21 10:50:24 -07: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
kennethreitz d872f29cfc Merge pull request #76 from emattiza/dev-docs
Docs addition for Maya
2017-05-29 02:12:10 -04:00
Evan.Mattiza d8cbdede28 added update, install, and some quickstart
Signed-off-by: Evan.Mattiza <emattiza@gmail.com>
2017-05-29 01:03:35 -05:00
Evan.Mattiza c0f1bd709f Versioning in docs off Maya Package
Signed-off-by: Evan.Mattiza <emattiza@gmail.com>
2017-05-28 23:16:20 -05:00
Evan.Mattiza 8469e60d3c Tree of API and Use-Case definitions
added files for api descriptions and usage by users

Signed-off-by: Evan.Mattiza <emattiza@gmail.com>
2017-05-28 23:16:20 -05:00
Evan.Mattiza 953f857940 Index Basics added
Signed-off-by: Evan.Mattiza <emattiza@gmail.com>
2017-05-28 23:16:20 -05:00
Evan.Mattiza 6375143eac Authors add for Evan
Added myself to authors

Signed-off-by: Evan.Mattiza <emattiza@gmail.com>
2017-05-28 23:16:20 -05:00
Evan.Mattiza c0fd845d4c Sphinx-Quickstart Additions
Basics added from sphinx-quickstart, makefile at project root
updated.

Signed-off-by: Evan.Mattiza <emattiza@gmail.com>
2017-05-28 23:16:20 -05:00
Evan.Mattiza 0f38b99157 Added Sphinx to Pipfile
Yeah. What it sounds like.

Signed-off-by: Evan.Mattiza <emattiza@gmail.com>
2017-05-28 23:16:20 -05:00
Evan.Mattiza 66a016ea84 Version File Add and setup.py exec read for version
Single place to change version and keep up to date at package level

Signed-off-by: Evan.Mattiza <emattiza@gmail.com>
2017-05-28 17:04:05 -05:00
kennethreitz 6f1df92b8f Merge pull request #72 from moin18/mayainterval_validation
Validation of arguments pass to the function of MayaInterval class
2017-05-28 11:15:11 -07:00
Moin 51c4298ece fixed review comment related to argument type check validator and fixed indentation issues 2017-05-28 23:20:40 +05:30
Moin 74092289dd modified test case to validate invalid argument scenario 2017-05-28 23:08:09 +05:30
Moin f3f2793b50 added decorator to vaildate arguments of MayaInterval functions 2017-05-28 23:07:37 +05:30
kennethreitz 251f535d67 Merge pull request #68 from moin18/seconds_or_timedelta_fix
fix 'seconds_or_timedelta' to return ONLY 'datetime.timedelta' object
2017-05-28 08:55:15 -07:00
kennethreitz 98e9a2190a Merge pull request #71 from timofurrer/use-valuerror
Raise ValueError instead of AssertionError
2017-05-28 08:54:27 -07:00
kennethreitz c8dd4b9264 Merge pull request #70 from timofurrer/classifiers
Add some reasonable trove classifiers
2017-05-28 08:53:52 -07:00
kennethreitz a1b27e80d6 Merge pull request #67 from moin18/cleanup
code cleanup - descriptive variable names
2017-05-28 08:53:40 -07:00
kennethreitz 92f3b83b8b Merge pull request #69 from timofurrer/fix-tests
Fix failing tests
2017-05-28 08:52:42 -07:00
Timo Furrer 0f39ec6323 Add setup.cfg with wheels config 2017-05-28 12:25:40 +02:00
Timo Furrer 4d88eede9d Fix failing tests 2017-05-28 12:23:26 +02:00
Timo Furrer 971ecec9cf Raise ValueError instead of AssertionError 2017-05-28 12:18:35 +02:00
Timo Furrer ef471f8041 Add some reasonable trove classifiers 2017-05-28 12:04:52 +02:00
Moin 0fda49bdb9 variable changes for the operator functions of the MayaDT 2017-05-28 13:06:17 +05:30
Moin a8a18462fe fix 'seconds_or_timedelta' to return ONLY 'datetime.timedelta' object 2017-05-28 06:49:18 +05:30
Moin 82dc88c878 added __init__ to tests directory to allow maya import 2017-05-28 05:28:59 +05:30
Moin e9a14e32da packages conf moved to separate list in setup.py 2017-05-28 05:22:03 +05:30
Moin 7b7f990a13 code cleanup - descriptive variable names 2017-05-28 05:06:25 +05:30
kennethreitz 720617f062 Merge pull request #65 from timofurrer/license-line-wrap
Wrap lines in license text
2017-05-27 15:37:33 -07:00
Timo Furrer 5bf45dbfb7 Wrap lines in license text 2017-05-28 00:36:51 +02:00
kennethreitz 488a25bcd8 Merge pull request #64 from timofurrer/tests-dir
Move tests into tests directory
2017-05-27 15:36:16 -07:00
Timo Furrer b2ac4f08a5 Move tests into tests directory 2017-05-28 00:33:41 +02:00
kennethreitz 57ccc67721 Update README.rst 2017-05-27 14:59:27 -04:00
kennethreitz 7814ec2864 fix broken tests 2017-05-27 13:04:22 -04:00
kennethreitz f39c932039 cleanups 2017-05-27 12:12:58 -04:00
kennethreitz 96ff770071 move things around 2017-05-27 12:11:51 -04:00
kennethreitz cd16ee94f0 move things around 2017-05-27 12:11:45 -04:00
kennethreitz 7c68489682 tests 2017-05-27 12:10:48 -04:00
kennethreitz 93e163722b Merge branch 'Tafkas-master' 2017-05-27 12:08:32 -04:00
kennethreitz b3ac13fcbf Merge pull request #59 from Tafkas/master
add compatibility for Python3
2017-05-27 09:08:18 -07:00
kennethreitz d50dc8701c simply test suite 2017-05-27 12:07:57 -04:00
Christian Stade-Schuldt 6c1cc24ad5 minor 2017-05-26 01:14:59 -07:00
Christian Stade-Schuldt 46b7e369b3 adapt compat.py style to the one from requests 2017-05-25 12:14:58 -07:00
Christian Stade-Schuldt 2b60e817ea add compatibility for Python3 2017-05-25 12:01:01 -07:00
kennethreitz 0fea886c00 Merge pull request #57 from c17r/patch-1
Regenerate lock file for 3.6
2017-05-25 08:57:55 -07:00
Christian Sauer ef52cb4b7d Regenerate lock file for 3.6
Maya uses `ruamel.yaml` which needs `ruamel.ordereddict` under 2.7 but does not under 3.x.  `ruamel.ordereddict` doesn't build under 3.x.  Maya's Pipfile.lock file was created under 2.7 so `ruamel.ordereddict` is listed as a dependency.
2017-05-25 11:56:25 -04:00
kennethreitz f1be1585d3 Update README.rst 2017-05-25 11:21:11 -04:00
kennethreitz 89ee548ec1 Update README.rst 2017-05-25 11:21:01 -04:00
kennethreitz 09351afc68 Update README.rst 2017-05-25 10:41:23 -04:00
kennethreitz 5d4a217fd6 Update README.rst 2017-05-25 10:41:03 -04:00
kennethreitz 27950e6d35 fixes 2017-05-23 11:45:33 -07:00
David Gouldin f2c52af853 Merge branch 'fix-interval-contains' 2017-05-23 11:44:57 -07:00
David Gouldin 01cc151b5c Correcting order of 'in' operator in code and tests 2017-05-23 11:43:48 -07:00
kennethreitz eda4a92da9 Merge branch 'master' of github.com:kennethreitz/maya 2017-05-23 11:38:24 -07:00
kennethreitz 6fbf8f2d74 fix the bug 2017-05-23 11:38:09 -07:00
kennethreitz 343abed679 fix the bug 2017-05-23 11:36:39 -07:00
kennethreitz 704af5a3a0 Update README.rst 2017-05-23 11:27:47 -07:00
kennethreitz f9f5b97f62 Update README.rst 2017-05-23 11:27:03 -07:00
kennethreitz 36fc82211f remove bunk 2017-05-23 10:51:03 -07:00
kennethreitz 6eba043582 amazing MayaInterval class, compliments of @dgouldin :) 2017-05-23 10:49:36 -07:00
kennethreitz 23652fe16a new lock file 2017-05-15 20:14:13 -04:00
kennethreitz 36379fc2f2 .org 2017-05-15 20:12:43 -04:00
kennethreitz c7b804985e Update README.rst 2017-05-15 20:10:27 -04:00
kennethreitz e4a928e8f4 new version 2017-05-15 20:09:11 -04:00
kennethreitz 080831519d range 2017-05-15 20:06:35 -04:00
kennethreitz 43705d84d6 test intervals 2017-05-15 20:05:00 -04:00
kennethreitz 6521a7dbc5 maya.intervals 2017-05-15 20:00:56 -04:00
kennethreitz 1796fc1d40 fix failing tests 2017-05-15 15:22:26 -04:00
kennethreitz 450dff000b v0.1.8 2017-02-09 21:29:03 -05:00
kennethreitz 90ba4ded70 fix for rand-day 2017-02-09 21:28:40 -05:00
kennethreitz 2e33ede5ac document add 2017-02-09 21:28:27 -05:00
kennethreitz 6a9661759f fix add/subtract 2017-02-09 21:28:22 -05:00
kennethreitz f46c3792e8 pipfile.lock 2017-02-09 21:23:37 -05:00
kennethreitz c07e51bf4d Update README.rst 2017-02-09 20:54:51 -05:00
kennethreitz 63d5e0ce90 Subtract and add! 2017-02-09 20:43:55 -05:00
kennethreitz 361f7a45ad Merge pull request #51 from sdispater/improve-iso8601-parsing
Improve iso8601 parsing
2017-02-09 19:15:19 -06:00
Sébastien Eustace 09db746027 Updates AUTHORS.rst 2017-02-09 19:40:09 -05:00
Sébastien Eustace 15a447491a Improves ISO8601 parsing 2017-02-09 19:39:18 -05:00
kennethreitz f8d83c0370 Update README.rst 2017-02-09 17:19:19 -05:00
kennethreitz 7c47796ac6 Update README.rst 2017-02-09 17:18:44 -05:00
kennethreitz 9c88519e63 only test 3.6 and 2.7 2017-02-09 17:15:22 -05:00
kennethreitz 50d4558ffa travis 2017-02-09 17:07:03 -05:00
kennethreitz 77bfce0bae rfc 3339, v0.1.7 2017-02-09 17:03:55 -05:00
kennethreitz 8aaa741793 v0.1.6 2017-01-24 12:46:15 -05:00
kennethreitz 5a442b3876 fix failing test 2017-01-01 17:36:42 -05:00
kennethreitz 4822487efe Merge pull request #46 from JoshuaRLi/update-authors
Update AUTHORS
2017-01-01 17:32:55 -05:00
Joshua Li e0ee61fd43 update AUTHORS 2017-01-01 16:42:18 -05:00
kennethreitz 08a654d523 v0.1.5 2016-12-30 18:59:32 -05:00
kennethreitz 0e260d761e Merge pull request #37 from moin18/decorator_remove
Type checks in comparison operators
2016-12-30 18:58:20 -05:00
Moin c9888a48e5 validate_class_type_arguments decorator 2016-12-29 16:23:53 +05:30
Moin 21743de790 test for AttributeError in MayaDT comparison operators 2016-12-29 14:42:59 +05:30
Moin 09c3e352e4 Merge branch 'master' of https://github.com/kennethreitz/maya into decorator_remove 2016-12-29 14:38:47 +05:30
Moin ba9e35525c modified maya_dt obj check decorator 2016-12-29 14:36:05 +05:30
kennethreitz 562403da0f Merge pull request #38 from moin18/slang_date
use local timezone to get the slang date
2016-12-28 15:07:07 -05:00
kennethreitz e96db15c0e Merge pull request #43 from timofurrer/python3
Support Python 3.3, 3.4, 3.5, 3.6
2016-12-28 15:06:11 -05:00
kennethreitz a3b86b6025 Merge pull request #44 from timofurrer/strproto
Implement str protocol for human readable datetime printing
2016-12-28 15:05:36 -05:00
Timo Furrer 2f47366cbf Implement str protocol for human readable datetime printing 2016-12-28 09:12:48 +01:00
Timo Furrer c17a358c66 Support Python 3.3, 3.4, 3.5, 3.6
ruamel.ordereddict is only needed for python 2.7 anyway.
So we should not depend on it in requirements.txt
2016-12-28 08:59:30 +01:00
kennethreitz b8300192c9 Merge pull request #39 from moin18/test_change
properties test at single place
2016-12-28 02:24:48 -05:00
kennethreitz d8f01a7789 Merge pull request #41 from JoshuaRLi/pip-win-fix
Fix file encoding issues preventing successful pip installation on Windows
2016-12-28 02:24:21 -05:00
kennethreitz 199a290b1d Merge pull request #42 from JoshuaRLi/test-dt-tz-fix
Fix erroneous test logic in test_dt_tz_translation, test_dt_tz_naive
2016-12-28 02:23:53 -05:00
Joshua Li 82d20ad1dc fix erroneous test logic with dt_tz_naive and dt_tz_translation 2016-12-27 22:32:39 -05:00
Joshua Li d6b8ac54ad README.rst is now read correctly with utf-8 encoding into the setup long_description, fixes python3 windows cmd installs via pip 2016-12-27 21:24:02 -05:00
Moin edfb9baae4 use local timezone to get the slang date 2016-12-27 04:56:40 +05:30
Moin 4fb95666ba properties test at single place 2016-12-27 04:51:43 +05:30
Moin 03243e2194 Type checks in comparison operators
Related to issue #33
2016-12-27 01:24:49 +05:30
kennethreitz 6420cc91d9 Merge pull request #30 from timofurrer/cleanup/fix-authors
Fix rst headings in AUTHORS file
2016-12-26 11:47:38 -05:00
kennethreitz 40eeea252d Merge pull request #31 from timofurrer/cleanup/remove-exec-flag
Remove executable flag from python files
2016-12-26 11:47:27 -05:00
kennethreitz 2452b30d96 Merge pull request #32 from timofurrer/cleanup/remove-substraction
Remove sub protocol
2016-12-26 11:47:08 -05:00
kennethreitz 4e009dc51a Merge pull request #34 from timofurrer/coverage
Add unit tests for weekday and week
2016-12-26 11:46:02 -05:00
kennethreitz d535739413 Merge pull request #35 from timofurrer/cleanup/tests
Make unit tests more valuable
2016-12-26 11:45:32 -05:00
Timo Furrer 299951bd0b Make unit tests more valuable 2016-12-26 12:01:54 +01:00
Timo Furrer 84555b1f5c Remove sub protocol 2016-12-26 11:17:46 +01:00
Timo Furrer 915337d2b4 Add unit tests for weekday and week 2016-12-26 11:10:02 +01:00
Timo Furrer e31f018a23 Remove executable flag from python files 2016-12-26 10:59:35 +01:00
Timo Furrer 0b64766b62 Fix rst headings in AUTHORS file 2016-12-26 10:56:13 +01:00
kennethreitz 5c255d2682 Merge pull request #29 from moin18/maya_operations
comparision operation support and subtraction of MayaDT object
2016-12-24 14:14:33 -05:00
kennethreitz 702eaab906 Merge pull request #28 from moin18/contributors
contributor firendly repository
2016-12-24 13:26:40 -05:00
Moin 5bbe383061 comparision operation support and subtraction of MayaDT object 2016-12-24 22:39:47 +05:30
Moin d40e698fb9 contributor firendly repository 2016-12-24 21:22:47 +05:30
kennethreitz 327f057e63 Merge pull request #27 from moin18/master
'week' and 'weekday' as properties to 'MayaDT' object
2016-12-24 00:45:38 -05:00
Moin 470516146a 'week' and 'weekday' as properties to 'MayaDT' object 2016-12-24 03:33:37 +05:30
kennethreitz dbed0555db _EPOCH_START 2016-12-23 13:11:25 -05:00
kennethreitz 2fce84195e v0.1.4 2016-12-21 13:24:53 -05:00
kennethreitz 7ab9e48d45 Merge pull request #25 from Factr/master
Added .gitignore
2016-12-20 22:19:15 -05:00
Adam Nelson ee1a8e2438 Added .gitignore
From https://github.com/github/gitignore/blob/master/Python.gitignore with Pycharm exclusion for .idea/
2016-12-20 16:08:29 -05:00
kennethreitz b0e91d6c0e Merge pull request #24 from Factr/master
Added tzlocal to setup.py
2016-12-20 14:30:11 -05:00
Adam Nelson bce5d13401 Merge branch 'master' of github.com:Factr/maya into HEAD 2016-12-20 13:36:19 -05:00
Adam Nelson b4a23d668d Added tzlocal as setup requirement 2016-12-20 13:34:49 -05:00
kennethreitz 90051da96d fix maya link to travis 2016-12-20 13:09:48 -05:00
kennethreitz 8f0ff0d68c skip 3 builds 2016-12-20 13:08:55 -05:00
kennethreitz fd1262a8e8 shields 2016-12-20 12:57:11 -05:00
kennethreitz 7558261dc8 basic travis file 2016-12-20 12:49:56 -05:00
kennethreitz 1407e688fd improve test name 2016-12-20 12:29:31 -05:00
kennethreitz 0c2b936111 cleanup tests 2016-12-20 12:27:16 -05:00
kennethreitz 9e72ef3d2f test for #19 2016-12-20 12:25:19 -05:00
kennethreitz eabba0b79e v0.1.3 2016-12-20 12:25:01 -05:00
kennethreitz 8af92bc33c fixes #19 2016-12-20 12:20:50 -05:00
kennethreitz 917420071f v0.1.2 2016-12-20 12:15:01 -05:00
kennethreitz 2bb430b91d make setup.py python 2/3 compatible 2016-12-20 12:14:48 -05:00
kennethreitz 967638228a -e . 2016-12-20 12:12:59 -05:00
kennethreitz 1ba2dac284 Merge pull request #23 from aaronjeline/master
setup.py fix
2016-12-20 12:12:04 -05:00
Aaron Eline 89a54abf1f Added fix from @joaoleveiga 2016-12-20 09:56:00 -05:00
Aaron Eline f119fe927b Fixed missing comma in setup.py 2016-12-20 09:53:51 -05:00
Adam Nelson 0efc489ba1 Added tzlocal as a requirement as well 2016-12-19 17:15:35 -05:00
kennethreitz 8b6ca639cb Merge pull request #21 from Factr/master
Python files executable, ruamel.yaml required
2016-12-19 13:29:32 -08:00
Adam Nelson 48c245a8ad Python files executable, ruamel.yaml required 2016-12-19 16:22:22 -05:00
kennethreitz 29a5b990fd day_first in tests 2016-12-19 13:31:03 -05:00
kennethreitz 69c88552d7 day_first 2016-12-19 13:02:47 -05:00
kennethreitz 2126c8f33e Merge pull request #20 from jerry2yu/master
Add datafirst parameter to parse method. Set datafirst true to parse …
2016-12-19 10:01:31 -08:00
kennethreitz dbc20b3026 Merge pull request #18 from harshit98/master
Update README.rst
2016-12-19 09:49:19 -08:00
Jerry Yu c521672f0e Add datafirst parameter to parse method. Set datafirst true to parse the first value in date (e.g. 01/05/2016) as the day 2016-12-19 13:00:18 -04:00
Harshit Prasad f756b13d4d Update README.rst 2016-12-19 21:01:07 +05:30
kennethreitz 2ce5a23e38 Merge pull request #16 from andrewthehan/typoReadme
Fix typo in README.rst
2016-12-18 21:37:03 -08:00
Andrew Han 605e15ae27 Fix typo in README.rst 2016-12-18 20:10:00 -08:00
kennethreitz 07a3e814a2 Update README.rst 2016-12-18 16:49:37 -05:00
kennethreitz 2e811d8689 Update README.rst 2016-12-18 16:47:28 -05:00
kennethreitz cbbd7d92c1 Update README.rst 2016-12-18 16:45:59 -05:00
kennethreitz d9e08034dd Update README.rst 2016-12-18 16:29:14 -05:00
kennethreitz e3b1eacb77 fix datetime formatting 2016-12-18 13:09:21 -05:00
kennethreitz be29d01160 Merge branch 'master' of github.com:kennethreitz/maya 2016-12-18 12:43:02 -05:00
kennethreitz 5e4853936b Merge pull request #8 from timofurrer/bugfix/format
Fix formatting MayaDT object
2016-12-18 09:41:58 -08:00
kennethreitz 7dbe450c28 Merge pull request #12 from djrobstep/bugfix/py-modules
my_modules -> py_modules
2016-12-18 09:41:29 -08:00
kennethreitz 3ce6734a5e Merge pull request #13 from grigouze/add_tests_for_coverage
Just add some tests to have full coverage
2016-12-18 09:41:12 -08:00
kennethreitz 6e2f530207 Merge pull request #7 from Kraymer/patch-1
Update README.rst (typo)
2016-12-18 09:40:04 -08:00
Grigouze c46d9874d4 Just add some tests to have full coverage
Now it's 💯 % coverage, i don't if it's good or bad but it was easy :)
2016-12-18 15:12:02 +01:00
Robert Lechte 87013d71e0 my_modules -> py_modules 2016-12-18 22:38:40 +11:00
Timo Furrer a562f48e24 Fix formatting MayaDT object 2016-12-18 10:59:22 +01:00
Fabrice Laporte 8fc07b3ab3 Update README.rst
Typo tomorrrow => tomorrow
2016-12-18 10:39:43 +01:00
kennethreitz 59064a5c50 Merge pull request #5 from uranusjr/master
Fix typos in README and add pip install instruction
2016-12-18 00:58:43 -08:00
Tzu-ping Chung 7ae84f3266 Add pip install instruction 2016-12-18 16:04:23 +08:00
Tzu-ping Chung cc668974b6 Fix typos 2016-12-18 16:03:35 +08:00
kennethreitz 6a4ddff215 Update README.rst 2016-12-18 02:48:45 -05:00
kennethreitz a0ab4fc3fb Update README.rst 2016-12-18 02:48:29 -05:00
kennethreitz c80ecad67b Update README.rst 2016-12-18 02:48:12 -05:00
kennethreitz 97842c01b3 Update README.rst 2016-12-18 02:47:57 -05:00
33 changed files with 2582 additions and 308 deletions
+95
View File
@@ -0,0 +1,95 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
Pipfile.lock
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# dotenv
.env
# virtualenv
.venv/
venv/
ENV/
# Spyder project settings
.spyderproject
# Rope project settings
.ropeproject
# PyCharm
.idea/
+18
View File
@@ -0,0 +1,18 @@
language: python
python:
- "2.7"
- "3.6"
- "3.7-dev"
matrix:
allow_failures:
- python: "3.7-dev"
# command to install dependencies
install:
- pip install pipenv
- pipenv install '-e .' --skip-lock --ignore-pipfile
- pipenv install --dev --skip-lock
# command to run tests
script: pipenv run pytest tests/
+26
View File
@@ -0,0 +1,26 @@
Contributions to the maya project
=================================
Creator & Maintainer
--------------------
- Kenneth Reitz <me@kennethreitz.org> `@kennethreitz <https://github.com/kennethreitz>`_
Contributors
------------
In chronological order:
- Adam Nelson <adam@varud.com> (`@adamn <https://github.com/adamn>`_)
- Timo Furrer <tuxtimo@gmail.com> (`@timofurrer <https://github.com/timofurrer>`_)
- Moinuddin Quadri <moin18@gmail.com> (`@moin18 <https://github.com/moin18>`_)
- Grigouze <grigouze@yahoo.fr> (`@grigouze <https://github.com/grigouze>`_)
- Tzu-ping Chung <uranusjr@gmail.com> (`@uranusjr <https://github.com/uranusjr>`_)
- aaronjeline (`@aaronjeline <https://github.com/aaronjeline>`_)
- jerry2yu (`@jerry2yu <https://github.com/jerry2yu>`_)
- Joshua Li <joshua.r.li.98@gmail.com> (`@JoshuaRLi <https://github.com/JoshuaRLi>`_)
- Sébastien Eustace <sebastien@eustace.io> (`@sdispater <https://github.com/sdispater>`_)
- 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>`_)
+17 -3
View File
@@ -1,7 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Kenneth Reitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+5 -1
View File
@@ -1,2 +1,6 @@
.PHONY: tests docs
tests:
pytest test_maya.py
pytest tests/
docs:
cd docs && make html
+4
View File
@@ -0,0 +1,4 @@
[dev-packages]
freezegun = "*"
pytest = "*"
sphinx = "*"
+105 -11
View File
@@ -1,5 +1,15 @@
Maya: Datetime for Humans™
==========================
Maya: Datetimes for Humans™
===========================
.. image:: https://img.shields.io/pypi/v/maya.svg
:target: https://pypi.python.org/pypi/maya
.. image:: https://travis-ci.org/kennethreitz/maya.svg?branch=master
:target: https://travis-ci.org/kennethreitz/maya
.. image:: https://img.shields.io/badge/SayThanks-!-1EAEDB.svg
:target: https://saythanks.io/to/kennethreitz
Datetimes are very frustrating to work with in Python, especially when dealing
with different locales on different systems. This library exists to make the
@@ -8,6 +18,15 @@ simple things **much** easier, while admitting that time is an illusion
Datetimes should be interacted with via an API written for humans.
Maya is mostly built around the headaches and use-cases around parsing datetime data from websites.
.. image:: https://farm4.staticflickr.com/3702/33288285996_5b69d2b8f7_k_d.jpg
Art by `Sam Flores
<https://www.instagram.com/samagram12/>`_ (Photo by `Kenneth Reitz
<https://www.instagram.com/kennethreitz/>`_).
☤ Basic Usage of Maya
---------------------
@@ -27,11 +46,17 @@ Behold, datetimes for humans!
>>> tomorrow.slang_time()
'23 hours from now'
# Also: MayaDT.from_iso8601(...)
>>> tomorrow.iso8601()
'2016-12-16T15:11:30.263350Z'
'2017-02-10T22:17:01.445418Z'
>>> tomorrrow.rfc2822()
'Fri, 16 Dec 2016 20:11:30 -0000'
# Also: MayaDT.from_rfc2822(...)
>>> tomorrow.rfc2822()
'Fri, 10 Feb 2017 22:17:01 GMT'
# Also: MayaDT.from_rfc3339(...)
>>> tomorrow.rfc3339()
'2017-02-10T22:17:01.44Z'
>>> tomorrow.datetime()
datetime.datetime(2016, 12, 16, 15, 11, 30, 263350, tzinfo=<UTC>)
@@ -44,14 +69,53 @@ Behold, datetimes for humans!
>>> rand_day = maya.when('2011-02-07', timezone='US/Eastern')
<MayaDT epoch=1297036800.0>
# Note how this is the 6th, not the 7th.
# 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
6
7
>>> rand_day.add(days=10).day
17
# Always.
>>> rand_day.timezone
UTC
# Range of hours in a day:
>>> maya.intervals(start=maya.now(), end=maya.now().add(days=1), interval=60*60)
<generator object intervals at 0x105ba5820>
# snap modifiers
>>> dt = maya.when('Mon, 21 Feb 1994 21:21:42 GMT')
>>> dt.snap('@d+3h').rfc2822()
'Mon, 21 Feb 1994 03:00:00 GMT'
☤ Advanced Usage of Maya
------------------------
In addition to timestamps, Maya also includes a 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:
.. code-block:: pycon
>>> from maya import MayaInterval
# Create an event that is one hour long, starting now.
>>> event_start = maya.now()
>>> event_end = event_start.add(hours=1)
>>> event = MayaInterval(start=event_start, end=event_end)
From here, there are a number of methods available to you, which you can use to compare this event to another event.
☤ Why is this useful?
@@ -61,15 +125,45 @@ Behold, datetimes for humans!
- Complete symmetric import and export of both ISO 8601 and RFC 2822 datetime stamps.
- Fantastic parsing of both dates written for/by humans and machines (``maya.when()`` vs ``maya.parse()``).
- Support for human slang, both import and export (e.g. `an hour ago`).
- Datetimes can very easily be generated, with our without tzinfo attached.
- Datetimes can very easily be generated, with or without tzinfo attached.
- This library is based around epoch time, but dates before Jan 1 1970 are indeed supported, via negative integers.
- Maya never panics, and always carrys a towel.
- Maya never panics, and always carries a towel.
☤ What about Delorean, Arrow, & Pendulum?
-----------------------------------------
All these project complement 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.
I simply desire a sane API for datetimes that made sense to me for all the things I'd ever want to do—especially when dealing with timezone algebra. Arrow doesn't do all of the things I need (but it does a lot more!). Maya does do exactly what I need.
I think these projects complement each-other, personally. Maya is great for parsing websites, and dealing with calendar events!
☤ Installing Maya
-----------------
Installation is easy, with pip::
Installation is easy, with `pipenv <http://pipenv.org/>`_::
$ pip install maya
$ pipenv install maya
✨🍰✨
☤ Like it?
----------
`Say Thanks <https://saythanks.io/to/kennethreitz>`_!
How to Contribute
-----------------
#. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug.
#. Fork `the repository`_ on GitHub to start making your changes to the **master** branch (or branch off of it).
#. Write a test which shows that the bug was fixed or that the feature works as expected.
#. Send a pull request and bug the maintainer until it gets merged and published. :) Make sure to add yourself to AUTHORS_.
.. _`the repository`: http://github.com/kennethreitz/maya
.. _AUTHORS: https://github.com/kennethreitz/maya/blob/master/AUTHORS.rst
+20
View File
@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = python -msphinx
SPHINXPROJ = maya
SOURCEDIR = source
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+36
View File
@@ -0,0 +1,36 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=python -msphinx
)
set SOURCEDIR=source
set BUILDDIR=build
set SPHINXPROJ=maya
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The Sphinx module was not found. Make sure you have Sphinx installed,
echo.then set the SPHINXBUILD environment variable to point to the full
echo.path of the 'sphinx-build' executable. Alternatively you may add the
echo.Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
:end
popd
View File
View File
+163
View File
@@ -0,0 +1,163 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# maya documentation build configuration file, created by
# sphinx-quickstart on Sun May 28 15:46:10 2017.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import os
import sys
# sys.path.insert(0, os.path.abspath('.'))
sys.path.insert(0, os.path.abspath('..'))
sys.path.insert(0, os.path.abspath('_themes'))
import maya
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = ['sphinx.ext.autodoc',
'sphinx.ext.todo',
'sphinx.ext.coverage',
'sphinx.ext.viewcode']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = 'maya'
copyright = '2017, Kenneth Reitz'
author = 'Kenneth Reitz'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = maya.__version__
# The full version, including alpha/beta/rc tags.
release = maya.__version__
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = []
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'alabaster'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'mayadoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'maya.tex', 'maya Documentation',
'Kenneth Reitz', 'manual'),
]
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'maya', 'maya Documentation',
[author], 1)
]
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'maya', 'maya Documentation',
author, 'maya', 'One line description of project.',
'Miscellaneous'),
]
+83
View File
@@ -0,0 +1,83 @@
.. maya documentation master file, created by
sphinx-quickstart on Sun May 28 15:46:10 2017.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Maya: Datetime for Humans
================================
Release v\ |version|. (:ref:`Installation <install>`)
.. image:: https://img.shields.io/pypi/v/maya.svg
:target: https://pypi.python.org/pypi/maya
.. image:: https://travis-ci.org/kennethreitz/maya.svg?branch=master
:target: https://travis-ci.org/kennethreitz/maya
.. image:: https://img.shields.io/badge/SayThanks-!-1EAEDB.svg
:target: https://saythanks.io/to/kennethreitz
☤ Behold, datetimes for humans!
-------------------------------
.. code-block:: pycon
>>> now = maya.now()
<MayaDT epoch=1481850660.9>
>>> tomorrow = maya.when('tomorrow')
<MayaDT epoch=1481919067.23>
>>> tomorrow.slang_date()
'tomorrow'
>>> tomorrow.slang_time()
'23 hours from now'
# Also: MayaDT.from_iso8601(...)
>>> tomorrow.iso8601()
'2017-02-10T22:17:01.445418Z'
# Also: MayaDT.from_rfc2822(...)
>>> tomorrow.rfc2822()
'Fri, 10 Feb 2017 22:17:01 GMT'
# Also: MayaDT.from_rfc3339(...)
>>> tomorrow.rfc3339()
'2017-02-10T22:17:01.44Z'
>>> tomorrow.datetime()
datetime.datetime(2016, 12, 16, 15, 11, 30, 263350, tzinfo=<UTC>)
# Automatically parse datetime strings and generate naive datetimes.
>>> scraped = '2016-12-16 18:23:45.423992+00:00'
>>> maya.parse(scraped).datetime(to_timezone='US/Eastern', naive=True)
datetime.datetime(2016, 12, 16, 13, 23, 45, 423992)
>>> rand_day = maya.when('2011-02-07', timezone='US/Eastern')
<MayaDT epoch=1297036800.0>
>>> rand_day.day
7
>>> rand_day.add(days=10).day
17
# Always.
>>> rand_day.timezone
UTC
# Range of hours in a day:
>>> maya.interval(
... start=maya.now(),
... end=maya.now().add(days=1),
... interval=60*60)
<generator object intervals at 0x105ba5820>
Table of Contents
-----------------
.. toctree::
:maxdepth: 2
user/install
user/quickstart
View File
+30
View File
@@ -0,0 +1,30 @@
.. _install:
Installation
=============
Pip Install Maya
----------------
To install maya, simply use::
$ pip install maya
Source Code
-----------
Maya is actively developed on `Github
<https://github.com/kennethreitz/maya.git>`_
You can either clone the public repository::
$ git clone git://github.com/kennethreitz/maya.git
Or, download the `tarball <https://github.com/kennethreitz/maya/tarball/master>`_::
$ curl -OL https://github.com/kennethreitz/maya/tarball/master
# optionally, zipball is also available (for Windows users).
Once you have a copy of the source, you can embed it in your own Python
package, or install it into your site-packages easily::
$ python setup.py install
View File
+38
View File
@@ -0,0 +1,38 @@
.. _quickstart:
Quickstart
==========
.. module::maya
Ready for a simple datetime tool? This doc provides some tools to use in your
busy workflow.
First, make sure that Maya is:
- :ref:`Installed <install>`
- :ref:`Up to date <update>`
Parse a Date
------------
Parsing a date from a string with Maya is 🍰!
First, you'll need to import maya::
>>> import maya
There are currently two ways to make sense of datetime:
- ``maya.parse``
- ``maya.when``
A simple answer is that you should use parse on machine output, and when on human input.
Use as follows::
>>> recent_win = maya.parse('2016-11-02T20:00PM')
>>> old_win = maya.when('October 14, 1908')
>>> grandpas_date = maya.when('108 years ago')
+27
View File
@@ -0,0 +1,27 @@
.. _update:
Community Updates
=================
If you'd like to stay up to date on the community and development of Maya,
there are several options:
GitHub
------
The best way to track the development of Maya is through
`the GitHub repo <https://github.com/kennethreitz/Maya>`_.
Twitter
-------
The author, Kenneth Reitz, often tweets about new features and releases of Maya.
Follow `@kennethreitz <https://twitter.com/kennethreitz>`_ for updates.
Release and Version History
===========================
.. include:: ../../HISTORY.rst
-208
View File
@@ -1,208 +0,0 @@
# ___ __ ___ _ _ ___
# || \/ | ||=|| \\// ||=||
# || | || || // || ||
# Ignore warnings for yaml usage.
import warnings
import ruamel.yaml
warnings.simplefilter('ignore', ruamel.yaml.error.UnsafeLoaderWarning)
import email.utils
import time
from datetime import datetime as Datetime
import pytz
import humanize
import dateparser
import iso8601
import dateutil.parser
from tzlocal import get_localzone
EPOCH_START = (1970, 1, 1)
class MayaDT(object):
"""The Maya Datetime object."""
def __init__(self, epoch):
super(MayaDT, self).__init__()
self._epoch = epoch
def __repr__(self):
return '<MayaDT epoch={}>'.format(self._epoch)
def __format__(self, *args, **kwargs):
"""Return's the datetime's format"""
return self.datetime(*args, **kwargs)
# Timezone Crap
# -------------
@property
def timezone(self):
"""Returns the UTC tzinfo name. It's always UTC. Always."""
return 'UTC'
@property
def _tz(self):
"""Returns the UTC tzinfo object."""
return pytz.timezone(self.timezone)
@property
def local_timezone(self):
"""Returns the name of the local timezone, for informational purposes."""
return self._local_tz.zone
@property
def _local_tz(self):
"""Returns the local timezone."""
return get_localzone()
@staticmethod
def __dt_to_epoch(dt):
"""Converts a datetime into an epoch."""
# Assume UTC if no datetime is provided.
if dt.tzinfo is None:
dt = dt.replace(tzinfo=pytz.utc)
epoch_start = Datetime(*EPOCH_START, tzinfo=pytz.timezone('UTC'))
return (dt - epoch_start).total_seconds()
# Importers
# ---------
@classmethod
def from_datetime(klass, dt):
"""Returns MayaDT instance from datetime."""
return klass(klass.__dt_to_epoch(dt))
@classmethod
def from_iso8601(klass, string):
"""Returns MayaDT instance from iso8601 string."""
dt = iso8601.parse_date(string)
return klass.from_datetime(dt)
@staticmethod
def from_rfc2822(string):
"""Returns MayaDT instance from rfc2822 string."""
return parse(string)
# Exporters
# ---------
def datetime(self, to_timezone=None, naive=False):
"""Returns a timezone-aware datetime...
Defaulting to UTC (as it should).
Keyword Arguments:
to_timezone {string} -- timezone to convert to (default: None/UTC)
naive {boolean} -- if True, the tzinfo is simply dropped (default: False)
"""
if to_timezone:
dt = self.datetime().astimezone(pytz.timezone(to_timezone))
else:
dt = Datetime.utcfromtimestamp(self._epoch)
# Strip the timezone info if requested to do so.
if naive:
return dt.replace(tzinfo=None)
return dt.replace(tzinfo=self._tz)
def iso8601(self):
"""Returns an ISO 8601 representation of the MayaDT."""
# Get a timezone-naive datetime.
dt = self.datetime(naive=True)
return '{}Z'.format(dt.isoformat())
def rfc2822(self):
"""Returns an RFC 2822 representation of the MayaDT."""
return email.utils.formatdate(self.epoch, usegmt=True)
# Properties
# ----------
@property
def year(self):
return self.datetime().year
@property
def month(self):
return self.datetime().month
@property
def day(self):
return self.datetime().day
@property
def hour(self):
return self.datetime().hour
@property
def minute(self):
return self.datetime().minute
@property
def second(self):
return self.datetime().second
@property
def microsecond(self):
return self.datetime().microsecond
@property
def epoch(self):
return self._epoch
# Human Slang Extras
# ------------------
def slang_date(self):
""""Returns human slang representation of date."""
return humanize.naturaldate(self.datetime())
def slang_time(self):
""""Returns human slang representation of time."""
dt = self.datetime(naive=True, to_timezone=self.local_timezone)
return humanize.naturaltime(dt)
def now():
"""Returns a MayaDT instance for this exact moment."""
epoch = time.time()
return MayaDT(epoch=epoch)
def when(string, timezone='UTC'):
""""Returns a MayaDT instance for the human moment specified.
Powered by dateparser. Useful for scraping websites.
Examples:
'next week', 'now', 'tomorrow', '300 years ago', 'August 14, 2015'
Keyword Arguments:
string -- string to be parsed
timezone -- timezone referenced from (default: 'UTC')
"""
dt = dateparser.parse(string, settings={'TIMEZONE': timezone, 'RETURN_AS_TIMEZONE_AWARE': True, 'TO_TIMEZONE': 'UTC'})
if dt is None:
raise ValueError('invalid datetime input specified.')
return MayaDT.from_datetime(dt)
def parse(string):
""""Returns a MayaDT instance for the machine-produced moment specified.
Powered by dateutil. Accepts most known formats. Useful for working with data.
Keyword Arguments:
string -- string to be parsed
"""
dt = dateutil.parser.parse(string)
return MayaDT.from_datetime(dt)
+2
View File
@@ -0,0 +1,2 @@
from .core import *
from .__version__ import __version__
+1
View File
@@ -0,0 +1 @@
__version__ = '0.4.1'
+102
View File
@@ -0,0 +1,102 @@
# -*- coding: utf-8 -*-
"""
maya.compat
~~~~~~~~~~~~~~~
This module handles import compatibility issues between Python 2 and
Python 3.
"""
import sys
# -------
# Pythons
# -------
# Syntax sugar.
_ver = sys.version_info
# : Python 2.x?
is_py2 = (_ver[0] == 2)
# : Python 3.x?
is_py3 = (_ver[0] == 3)
# ---------
# Specifics
# ---------
if is_py2:
cmp = cmp
elif is_py3:
def cmp(a, b):
"""
Compare two objects.
Returns a negative number if C{a < b}, zero if they are equal, and a
positive number if C{a > b}.
"""
if a < b:
return -1
elif a == b:
return 0
else:
return 1
def comparable(klass):
"""
Class decorator that ensures support for the special C{__cmp__} method.
On Python 2 this does nothing.
On Python 3, C{__eq__}, C{__lt__}, etc. methods are added to the class,
relying on C{__cmp__} to implement their comparisons.
"""
# On Python 2, __cmp__ will just work, so no need to add extra methods:
if not is_py3:
return klass
def __eq__(self, other):
c = self.__cmp__(other)
if c is NotImplemented:
return c
return c == 0
def __ne__(self, other):
c = self.__cmp__(other)
if c is NotImplemented:
return c
return c != 0
def __lt__(self, other):
c = self.__cmp__(other)
if c is NotImplemented:
return c
return c < 0
def __le__(self, other):
c = self.__cmp__(other)
if c is NotImplemented:
return c
return c <= 0
def __gt__(self, other):
c = self.__cmp__(other)
if c is NotImplemented:
return c
return c > 0
def __ge__(self, other):
c = self.__cmp__(other)
if c is NotImplemented:
return c
return c >= 0
klass.__lt__ = __lt__
klass.__gt__ = __gt__
klass.__le__ = __le__
klass.__ge__ = __ge__
klass.__eq__ = __eq__
klass.__ne__ = __ne__
return klass
+764
View File
@@ -0,0 +1,764 @@
# ___ __ ___ _ _ ___
# || \/ | ||=|| \\// ||=||
# || | || || // || ||
import email.utils
import time
import functools
from datetime import timedelta, datetime as Datetime
import re
import pytz
import humanize
import dateparser
import pendulum
import snaptime
from tzlocal import get_localzone
from dateutil.relativedelta import relativedelta
from .compat import cmp, comparable
def validate_class_type_arguments(operator):
"""
Decorator to validate all the arguments to function
are of the type of calling class for passed operator
"""
def inner(function):
def wrapper(self, *args, **kwargs):
for arg in args + tuple(kwargs.values()):
if not isinstance(arg, self.__class__):
raise TypeError(
'unorderable types: {}() {} {}()'.format(
type(self).__name__, operator, type(arg).__name__
)
)
return function(self, *args, **kwargs)
return wrapper
return inner
def validate_arguments_type_of_function(param_type=None):
"""
Decorator to validate the <type> of arguments in
the calling function are of the `param_type` class.
if `param_type` is None, uses `param_type` as the class where it is used.
Note: Use this decorator on the functions of the class.
"""
def inner(function):
def wrapper(self, *args, **kwargs):
type_ = param_type or type(self)
for arg in args + tuple(kwargs.values()):
if not isinstance(arg, type_):
raise TypeError(
(
'Invalid Type: {}.{}() accepts only the '
'arguments of type "<{}>"'
).format(
type(self).__name__,
function.__name__,
type_.__name__,
)
)
return function(self, *args, **kwargs)
return wrapper
return inner
class MayaDT(object):
"""The Maya Datetime object."""
__EPOCH_START = (1970, 1, 1)
def __init__(self, epoch):
super(MayaDT, self).__init__()
self._epoch = epoch
def __repr__(self):
return '<MayaDT epoch={}>'.format(self._epoch)
def __str__(self):
return self.rfc2822()
def __format__(self, *args, **kwargs):
"""Return's the datetime's format"""
return format(self.datetime(), *args, **kwargs)
@validate_class_type_arguments('==')
def __eq__(self, maya_dt):
return int(self._epoch) == int(maya_dt._epoch)
@validate_class_type_arguments('!=')
def __ne__(self, maya_dt):
return int(self._epoch) != int(maya_dt._epoch)
@validate_class_type_arguments('<')
def __lt__(self, maya_dt):
return int(self._epoch) < int(maya_dt._epoch)
@validate_class_type_arguments('<=')
def __le__(self, maya_dt):
return int(self._epoch) <= int(maya_dt._epoch)
@validate_class_type_arguments('>')
def __gt__(self, maya_dt):
return int(self._epoch) > int(maya_dt._epoch)
@validate_class_type_arguments('>=')
def __ge__(self, maya_dt):
return int(self._epoch) >= int(maya_dt._epoch)
def __hash__(self):
return hash(int(self.epoch))
def __add__(self, duration):
return self.add(
seconds=_seconds_or_timedelta(duration).total_seconds()
)
def __radd__(self, duration):
return self + duration
def __sub__(self, duration_or_date):
if isinstance(duration_or_date, MayaDT):
return self.subtract_date(dt=duration_or_date)
else:
return self.subtract(
seconds=_seconds_or_timedelta(duration_or_date).total_seconds()
)
def add(self, **kwargs):
"""Returns a new MayaDT object with the given offsets."""
return self.from_datetime(
pendulum.instance(self.datetime()).add(**kwargs)
)
def subtract(self, **kwargs):
"""Returns a new MayaDT object with the given offsets."""
return self.from_datetime(
pendulum.instance(self.datetime()).subtract(**kwargs)
)
def subtract_date(self, **kwargs):
"""Returns a timedelta object with the duration between the dates"""
return timedelta(seconds=self.epoch - kwargs['dt'].epoch)
def snap(self, instruction):
"""
Returns a new MayaDT object modified by the given instruction.
Powered by snaptime. See https://github.com/zartstrom/snaptime
for a complete documentation about the snaptime instructions.
"""
return self.from_datetime(snaptime.snap(self.datetime(), instruction))
# Timezone Crap
# -------------
@property
def timezone(self):
"""Returns the UTC tzinfo name. It's always UTC. Always."""
return 'UTC'
@property
def _tz(self):
"""Returns the UTC tzinfo object."""
return pytz.timezone(self.timezone)
@property
def local_timezone(self):
"""Returns the name of the local timezone."""
if self._local_tz.zone in pytz.all_timezones:
return self._local_tz.zone
return self.timezone
@property
def _local_tz(self):
"""Returns the local timezone."""
return get_localzone()
@staticmethod
@validate_arguments_type_of_function(Datetime)
def __dt_to_epoch(dt):
"""Converts a datetime into an epoch."""
# Assume UTC if no datetime is provided.
if dt.tzinfo is None:
dt = dt.replace(tzinfo=pytz.utc)
epoch_start = Datetime(*MayaDT.__EPOCH_START, tzinfo=pytz.timezone('UTC'))
return (dt - epoch_start).total_seconds()
# Importers
# ---------
@classmethod
@validate_arguments_type_of_function(Datetime)
def from_datetime(klass, dt):
"""Returns MayaDT instance from datetime."""
return klass(klass.__dt_to_epoch(dt))
@classmethod
@validate_arguments_type_of_function(time.struct_time)
def from_struct(klass, struct, timezone=pytz.UTC):
"""Returns MayaDT instance from a 9-tuple struct
It's assumed to be from gmtime().
"""
struct_time = time.mktime(struct) - utc_offset(struct)
dt = Datetime.fromtimestamp(struct_time, timezone)
return klass(klass.__dt_to_epoch(dt))
@classmethod
def from_iso8601(klass, iso8601_string):
"""Returns MayaDT instance from iso8601 string."""
return parse(iso8601_string)
@staticmethod
def from_rfc2822(rfc2822_string):
"""Returns MayaDT instance from rfc2822 string."""
return parse(rfc2822_string)
@staticmethod
def from_rfc3339(rfc3339_string):
"""Returns MayaDT instance from rfc3339 string."""
return parse(rfc3339_string)
# Exporters
# ---------
def datetime(self, to_timezone=None, naive=False):
"""Returns a timezone-aware datetime...
Defaulting to UTC (as it should).
Keyword Arguments:
to_timezone {str} -- timezone to convert to (default: None/UTC)
naive {bool} -- if True,
the tzinfo is simply dropped (default: False)
"""
if to_timezone:
dt = self.datetime().astimezone(pytz.timezone(to_timezone))
else:
dt = Datetime.utcfromtimestamp(self._epoch)
dt.replace(tzinfo=self._tz)
# Strip the timezone info if requested to do so.
if naive:
return dt.replace(tzinfo=None)
else:
if dt.tzinfo is None:
dt = dt.replace(tzinfo=self._tz)
return dt
def iso8601(self):
"""Returns an ISO 8601 representation of the MayaDT."""
# Get a timezone-naive datetime.
dt = self.datetime(naive=True)
return '{}Z'.format(dt.isoformat())
def rfc2822(self):
"""Returns an RFC 2822 representation of the MayaDT."""
return email.utils.formatdate(self.epoch, usegmt=True)
def rfc3339(self):
"""Returns an RFC 3339 representation of the MayaDT."""
return self.datetime().strftime("%Y-%m-%dT%H:%M:%S.%f")[:-4] + "Z"
# Properties
# ----------
@property
def year(self):
return self.datetime().year
@property
def month(self):
return self.datetime().month
@property
def day(self):
return self.datetime().day
@property
def date(self):
return self.datetime().date()
@property
def week(self):
return self.datetime().isocalendar()[1]
@property
def weekday(self):
"""Return the day of the week as an integer.
Monday is 1 and Sunday is 7.
"""
return self.datetime().isoweekday()
@property
def hour(self):
return self.datetime().hour
@property
def minute(self):
return self.datetime().minute
@property
def second(self):
return self.datetime().second
@property
def microsecond(self):
return self.datetime().microsecond
@property
def epoch(self):
return int(self._epoch)
# Human Slang Extras
# ------------------
def slang_date(self):
""""Returns human slang representation of date."""
dt = self.datetime(naive=True, to_timezone=self.local_timezone)
return humanize.naturaldate(dt)
def slang_time(self):
""""Returns human slang representation of time."""
dt = self.datetime(naive=True, to_timezone=self.local_timezone)
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):
if dt.tzinfo is None:
return dt
return dt.astimezone(pytz.utc).replace(tzinfo=None)
def to_utc_offset_aware(dt):
if dt.tzinfo is not None:
return dt
return pytz.utc.localize(dt)
def to_iso8601(dt):
return to_utc_offset_naive(dt).isoformat() + 'Z'
def end_of_day_midnight(dt):
if dt.time() == time.min:
return dt
else:
return (
dt.replace(hour=0, minute=0, second=0, microsecond=0) +
timedelta(days=1)
)
@comparable
class MayaInterval(object):
"""
A MayaInterval represents a range between two datetimes,
inclusive of the start and exclusive of the end.
"""
def __init__(self, start=None, end=None, duration=None):
try:
# Ensure that proper arguments were passed.
assert any(
(
(start and end),
(start and duration is not None),
(end and duration is not None),
)
)
assert not all((start, end, duration is not None))
except AssertionError:
raise ValueError(
'Exactly 2 of start, end, and duration must be specified'
)
# Convert duration to timedelta if seconds were provided.
if duration:
duration = _seconds_or_timedelta(duration)
if not start:
start = end - duration
if not end:
end = start + duration
if start > end:
raise ValueError('MayaInterval cannot end before it starts')
self.start = start
self.end = end
def __repr__(self):
return '<MayaInterval start={0!r} end={1!r}>'.format(
self.start, self.end
)
def iso8601(self):
"""Returns an ISO 8601 representation of the MayaInterval."""
return '{0}/{1}'.format(self.start.iso8601(), self.end.iso8601())
@classmethod
def parse_iso8601_duration(cls, duration, start=None, end=None):
match = re.match(
r'(?:P(?P<weeks>\d+)W)|(?:P(?:(?:(?P<years>\d+)Y)?(?:(?P<months>\d+)M)?(?:(?P<days>\d+)D))?(?:T(?:(?P<hours>\d+)H)?(?:(?P<minutes>\d+)M)?(?:(?P<seconds>\d+)S)?)?)',
duration
)
time_components = {}
if match:
time_components = match.groupdict(0)
for key, value in time_components.items():
time_components[key] = int(value)
duration = relativedelta(**time_components)
if start:
return parse(start.datetime() + duration)
if end:
return parse(end.datetime() - duration)
return None
@classmethod
def from_iso8601(cls, s):
# # Start and end, such as "2007-03-01T13:00:00Z/2008-05-11T15:30:00Z"
start, end = s.split('/')
try:
start = parse(start)
except pendulum.parsing.exceptions.ParserError:
# start = self._parse_iso8601_duration(start, end=end)
raise NotImplementedError()
try:
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"
# # Duration and end, such as "P1Y2M10DT2H30M/2008-05-11T15:30:00Z"
@validate_arguments_type_of_function()
def __and__(self, maya_interval):
return self.intersection(maya_interval)
@validate_arguments_type_of_function()
def __or__(self, maya_interval):
return self.combine(maya_interval)
@validate_arguments_type_of_function()
def __eq__(self, maya_interval):
return (
self.start == maya_interval.start and self.end == maya_interval.end
)
def __hash__(self):
return hash((self.start, self.end))
def __iter__(self):
yield self.start
yield self.end
@validate_arguments_type_of_function()
def __cmp__(self, maya_interval):
return (
cmp(self.start, maya_interval.start)
or cmp(self.end, maya_interval.end)
)
@property
def duration(self):
return self.timedelta.total_seconds()
@property
def timedelta(self):
return timedelta(seconds=(self.end.epoch - self.start.epoch))
@property
def is_instant(self):
return self.timedelta == timedelta(seconds=0)
def intersects(self, maya_interval):
return self & maya_interval is not None
@property
def midpoint(self):
return self.start.add(seconds=(self.duration / 2))
@validate_arguments_type_of_function()
def combine(self, maya_interval):
"""Returns a combined list of timespans, merged together."""
interval_list = sorted([self, maya_interval])
if self & maya_interval or self.is_adjacent(maya_interval):
return [
MayaInterval(
interval_list[0].start,
max(interval_list[0].end, interval_list[1].end),
)
]
return interval_list
@validate_arguments_type_of_function()
def subtract(self, maya_interval):
""""Removes the given interval."""
if not self & maya_interval:
return [self]
elif maya_interval.contains(self):
return []
interval_list = []
if self.start < maya_interval.start:
interval_list.append(MayaInterval(self.start, maya_interval.start))
if self.end > maya_interval.end:
interval_list.append(MayaInterval(maya_interval.end, self.end))
return interval_list
def split(self, duration, include_remainder=True):
# Convert seconds to timedelta, if appropriate.
duration = _seconds_or_timedelta(duration)
if duration <= timedelta(seconds=0):
raise ValueError('cannot call split with a non-positive timedelta')
start = self.start
while start < self.end:
if start + duration <= self.end:
yield MayaInterval(start, start + duration)
elif include_remainder:
yield MayaInterval(start, self.end)
start += duration
def quantize(self, duration, snap_out=False, timezone='UTC'):
"""Returns a quanitzed interval."""
# Convert seconds to timedelta, if appropriate.
duration = _seconds_or_timedelta(duration)
timezone = pytz.timezone(timezone)
if duration <= timedelta(seconds=0):
raise ValueError('cannot quantize by non-positive timedelta')
epoch = timezone.localize(Datetime(1970, 1, 1))
seconds = int(duration.total_seconds())
start_seconds = int(
(self.start.datetime(naive=False) - epoch).total_seconds()
)
end_seconds = int(
(self.end.datetime(naive=False) - epoch).total_seconds()
)
if start_seconds % seconds and not snap_out:
start_seconds += seconds
if end_seconds % seconds and snap_out:
end_seconds += seconds
start_seconds -= start_seconds % seconds
end_seconds -= end_seconds % seconds
if start_seconds > end_seconds:
start_seconds = end_seconds
return MayaInterval(
start=MayaDT.from_datetime(epoch).add(seconds=start_seconds),
end=MayaDT.from_datetime(epoch).add(seconds=end_seconds),
)
@validate_arguments_type_of_function()
def intersection(self, maya_interval):
"""Returns the intersection between two intervals."""
start = max(self.start, maya_interval.start)
end = min(self.end, maya_interval.end)
either_instant = self.is_instant or maya_interval.is_instant
instant_overlap = (self.start == maya_interval.start or start <= end)
if (either_instant and instant_overlap) or (start < end):
return MayaInterval(start, end)
@validate_arguments_type_of_function()
def contains(self, maya_interval):
return (
self.start <= maya_interval.start and self.end >= maya_interval.end
)
def __contains__(self, maya_dt):
if isinstance(maya_dt, MayaDT):
return self.contains_dt(maya_dt)
return self.contains(maya_dt)
def contains_dt(self, dt):
return self.start <= dt < self.end
@validate_arguments_type_of_function()
def is_adjacent(self, maya_interval):
return (
self.start == maya_interval.end or self.end == maya_interval.start
)
@property
def icalendar(self):
ical_dt_format = '%Y%m%dT%H%M%SZ'
return """
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
DTSTART:{0}
DTEND:{1}
END:VEVENT
END:VCALENDAR
""".format(
self.start.datetime().strftime(ical_dt_format),
self.end.datetime().strftime(ical_dt_format),
).replace(
' ', ''
).strip(
'\r\n'
).replace(
'\n', '\r\n'
)
@staticmethod
def flatten(interval_list):
return functools.reduce(
lambda reduced,
maya_interval: (
(
reduced[:-1] + maya_interval.combine(reduced[-1])
) if reduced else [
maya_interval
]
),
sorted(interval_list),
[],
)
@classmethod
def from_datetime(cls, start_dt=None, end_dt=None, duration=None):
start = MayaDT.from_datetime(start_dt) if start_dt else None
end = MayaDT.from_datetime(end_dt) if end_dt else None
return cls(start=start, end=end, duration=duration)
def now():
"""Returns a MayaDT instance for this exact moment."""
epoch = time.time()
return MayaDT(epoch=epoch)
def when(string, timezone='UTC', prefer_dates_from='current_period'):
""""Returns a MayaDT instance for the human moment specified.
Powered by dateparser. Useful for scraping websites.
Examples:
'next week', 'now', 'tomorrow', '300 years ago', 'August 14, 2015'
Keyword Arguments:
string -- string to be parsed
timezone -- timezone referenced from (default: 'UTC')
prefer_dates_from -- what dates are prefered when `string` is ambigous.
options are 'past', 'future', and 'current_period'
(default: 'current_period'). see: [1]
Reference:
[1] dateparser.readthedocs.io/en/latest/usage.html#handling-incomplete-dates
"""
settings = {
'TIMEZONE': timezone,
'RETURN_AS_TIMEZONE_AWARE': True,
'TO_TIMEZONE': 'UTC',
'PREFER_DATES_FROM': prefer_dates_from,
}
dt = dateparser.parse(string, settings=settings)
if dt is None:
raise ValueError('invalid datetime input specified.')
return MayaDT.from_datetime(dt)
def parse(string, timezone='UTC', day_first=False, year_first=True):
""""Returns a MayaDT instance for the machine-produced moment specified.
Powered by pendulum.
Accepts most known formats. Useful for working with data.
Keyword Arguments:
string -- string to be parsed
timezone -- timezone referenced from (default: 'UTC')
day_first -- if true, the first value (e.g. 01/05/2016)
is parsed as day.
if year_first is set to True, this distinguishes
between YDM and YMD. (default: False)
year_first -- if true, the first value (e.g. 2016/05/01)
is parsed as year (default: True)
"""
options = {}
options['tz'] = timezone
options['day_first'] = day_first
options['year_first'] = year_first
dt = pendulum.parse(str(string), **options)
return MayaDT.from_datetime(dt)
def _seconds_or_timedelta(duration):
"""Returns `datetime.timedelta` object for the passed duration.
Keyword Arguments:
duration -- `datetime.timedelta` object or seconds in `int` format.
"""
if isinstance(duration, int):
dt_timedelta = timedelta(seconds=duration)
elif isinstance(duration, timedelta):
dt_timedelta = duration
else:
raise TypeError(
'Expects argument as `datetime.timedelta` object '
'or seconds in `int` format'
)
return dt_timedelta
def intervals(start, end, interval):
"""
Yields MayaDT objects between the start and end MayaDTs given,
at a given interval (seconds or timedelta).
"""
interval = _seconds_or_timedelta(interval)
current_timestamp = start
while current_timestamp.epoch < end.epoch:
yield current_timestamp
current_timestamp = current_timestamp.add(seconds=interval.seconds)
-15
View File
@@ -1,15 +0,0 @@
dateparser==0.5.0
humanize==0.5.1
iso8601==0.1.11
jdatetime==1.8.1
py==1.4.32
pytest==3.0.5
python-dateutil==2.6.0
pytz==2016.10
regex==2016.11.21
ruamel.ordereddict==0.4.9
ruamel.yaml==0.13.4
six==1.10.0
typing==3.5.2.2
tzlocal==1.3
umalqurra==0.2
+2
View File
@@ -0,0 +1,2 @@
[bdist_wheel]
universal = 1
Regular → Executable
+79 -9
View File
@@ -1,10 +1,19 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import io
import os
import sys
import codecs
from shutil import rmtree
from setuptools import setup
from setuptools import find_packages, setup, Command
here = os.path.abspath(os.path.dirname(__file__))
with codecs.open(os.path.join(here, 'README.rst'), encoding='utf-8') as f:
long_description = '\n' + f.read()
if sys.argv[-1] == "publish":
@@ -14,23 +23,84 @@ if sys.argv[-1] == "publish":
required = [
'humanize',
'pytz',
'dateparser',
'iso8601',
'python-dateutil'
'dateparser>=0.7.0',
'tzlocal',
'pendulum>=1.0, <=1.5.1',
'snaptime'
]
packages = [
'maya',
]
class UploadCommand(Command):
"""Support setup.py upload."""
description = 'Build and publish the package.'
user_options = []
@staticmethod
def status(s):
"""Prints things in bold."""
print('\033[1m{0}\033[0m'.format(s))
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
try:
self.status('Removing previous builds…')
rmtree(os.path.join(here, 'dist'))
except OSError:
pass
self.status('Building Source and Wheel (universal) distribution…')
os.system('{0} setup.py sdist bdist_wheel --universal'.format(sys.executable))
self.status('Uploading the package to PyPi via Twine…')
os.system('twine upload dist/*')
self.status('Pushing git tags…')
os.system('git tag v{0}'.format(about['__version__']))
os.system('git push --tags')
sys.exit()
# About dict to store version and package info
about = dict()
with codecs.open(os.path.join(here, 'maya', '__version__.py'), 'r', encoding='utf-8') as f:
exec(f.read(), about)
setup(
name='maya',
version='0.1.0',
version=about['__version__'],
description='Datetimes for Humans.',
long_description=open('README.rst').read(),
long_description=long_description,
author='Kenneth Reitz',
author_email='me@kennethreitz.com',
author_email='me@kennethreitz.org',
url='https://github.com/kennethreitz/maya',
my_modules=['maya'],
packages=packages,
install_requires=required,
license='MIT',
classifiers=(
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Natural Language :: English',
'Operating System :: OS Independent',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: Implementation',
'Programming Language :: Python :: Implementation :: CPython',
'Topic :: Software Development :: Libraries :: Python Modules'
),
cmdclass={
'upload': UploadCommand,
},
)
-61
View File
@@ -1,61 +0,0 @@
import maya
def test_rfc2822():
r = maya.now().rfc2822()
d = maya.MayaDT.from_rfc2822(r)
assert r == d.rfc2822()
def test_iso8601():
r = maya.now().iso8601()
d = maya.MayaDT.from_iso8601(r)
assert r == d.iso8601()
def test_human_when():
r1 = maya.when('yesterday')
r2 = maya.when('today')
assert r2.day - r1.day == 1
def test_machine_parse():
r1 = maya.parse('August 14, 2015')
assert r1.day == 14
r2 = maya.parse('August 15, 2015')
assert r2.day == 15
def test_dt_tz_translation():
d1 = maya.now().datetime()
d2 = maya.now().datetime(to_timezone='US/Eastern')
assert d1.hour - d2.hour == 5
def test_dt_tz_naive():
d1 = maya.now().datetime(naive=True)
assert d1.tzinfo is None
d2 = maya.now().datetime(to_timezone='US/Eastern', naive=True)
assert d2.tzinfo is None
assert d1.hour - d2.hour == 5
def test_random_date():
d = maya.when('11-17-11')
assert d.year == 2011
assert d.month == 11
assert d.day == 17
def test_slang_date():
d = maya.when('tomorrow')
assert d.slang_date() == 'tomorrow'
def test_slang_time():
d = maya.when('one hour ago')
assert d.slang_time() == 'an hour ago'
# rand_day = maya.when('2011-02-07', timezone='US/Eastern')
View File
+24
View File
@@ -0,0 +1,24 @@
from freezegun import freeze_time
import pytest
@pytest.fixture(params=[
("2018-03-25T00:00:00", 2),
("2018-03-25T01:00:00", 2),
("2018-03-25T02:00:00", 2),
("2018-03-25T02:30:00", 2),
("2018-03-25T03:00:00", 2),
("2018-03-25T04:00:00", 2),
("2018-10-28T00:00:00", 2),
("2018-10-28T01:00:00", 2),
("2018-10-28T02:00:00", 2),
("2018-10-28T02:30:00", 2),
("2018-10-28T03:00:00", 2),
("2018-10-28T04:00:00", 2),
], ids=lambda x: x[0] + "_off_" + str(x[1]))
def frozen_now(request):
now_string, tz_offset = request.param
with freeze_time(now_string, tz_offset=tz_offset):
yield
+355
View File
@@ -0,0 +1,355 @@
import copy
import time
import calendar
from datetime import timedelta, datetime as Datetime
import pytz
import pytest
import maya
from maya.core import _seconds_or_timedelta # import private function
@pytest.mark.parametrize("string,expected", [
('February 21, 1994',
'Mon, 21 Feb 1994 00:00:00 GMT'),
])
def test_rfc2822(string, expected):
r = maya.parse(string).rfc2822()
d = maya.MayaDT.from_rfc2822(r)
assert r == expected
assert r == d.rfc2822()
@pytest.mark.parametrize("string,expected", [
('February 21, 1994',
'1994-02-21T00:00:00Z'),
])
def test_iso8601(string, expected):
r = maya.parse(string).iso8601()
d = maya.MayaDT.from_iso8601(r)
assert r == expected
assert r == d.iso8601()
@pytest.mark.parametrize("string,expected", [
('20161001T1430.4+05:30',
'2016-10-01T09:00:00.400000Z'),
('2016T14',
'2016-01-01T14:00:00Z'),
('2016-10T14',
'2016-10-01T14:00:00Z'),
('2012W05',
'2012-01-30T00:00:00Z'),
('2012W055',
'2012-02-03T00:00:00Z'),
('2012007',
'2012-01-07T00:00:00Z'),
('2016-W07T09',
'2016-02-15T09:00:00Z'),
])
def test_parse_iso8601(string, expected):
d = maya.MayaDT.from_iso8601(string)
assert expected == d.iso8601()
@pytest.mark.usefixtures("frozen_now")
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(ts), 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():
r1 = maya.when('yesterday')
r2 = maya.when('today')
assert (r2.day - r1.day) in (1, -30, -29, -28, -27)
def test_machine_parse():
r1 = maya.parse('August 14, 2015')
assert r1.day == 14
r2 = maya.parse('August 15, 2015')
assert r2.day == 15
@pytest.mark.usefixtures("frozen_now")
def test_dt_tz_translation():
d1 = maya.now().datetime()
d2 = maya.now().datetime(to_timezone='EST')
assert (d1.hour - d2.hour) % 24 == 5
@pytest.mark.usefixtures("frozen_now")
def test_dt_tz_naive():
d1 = maya.now().datetime(naive=True)
assert d1.tzinfo is None
d2 = maya.now().datetime(to_timezone='EST', naive=True)
assert d2.tzinfo is None
assert (d1.hour - d2.hour) % 24 == 5
def test_random_date():
# Test properties for maya.when()
d1 = maya.when('11-17-11 08:09:10')
assert d1.year == 2011
assert d1.month == 11
assert d1.day == 17
assert d1.week == 46
assert d1.weekday == 4
assert d1.hour == 8
assert d1.minute == 9
assert d1.second == 10
assert d1.microsecond == 0
# Test properties for maya.parse()
d2 = maya.parse('February 29, 1992 13:12:34')
assert d2.year == 1992
assert d2.month == 2
assert d2.day == 29
assert d2.week == 9
assert d2.weekday == 6
assert d2.hour == 13
assert d2.minute == 12
assert d2.second == 34
assert d2.microsecond == 0
def test_print_date(capsys):
d = maya.when('11-17-11')
print(d)
out, err = capsys.readouterr()
assert out == 'Thu, 17 Nov 2011 00:00:00 GMT\n'
assert repr(d) == '<MayaDT epoch=1321488000.0>'
def test_invalid_date():
with pytest.raises(ValueError):
maya.when('another day')
def test_slang_date():
d = maya.when('tomorrow')
assert d.slang_date() == 'tomorrow'
def test_slang_time():
d = maya.when('1 hour ago')
assert d.slang_time() == 'an hour ago'
@pytest.mark.parametrize("string,kwds,expected", [
('February 21, 1994', {},
'1994-02-21 00:00:00+00:00'),
('01/05/2016', {},
'2016-01-05 00:00:00+00:00'),
('01/05/2016', dict(day_first=True),
'2016-05-01 00:00:00+00:00'),
('2016/05/01', dict(year_first=True, day_first=False),
'2016-05-01 00:00:00+00:00'),
('2016/01/05', dict(year_first=True, day_first=True),
'2016-05-01 00:00:00+00:00'),
('01/05/2016', dict(timezone='UTC'),
'2016-01-05 00:00:00+00:00'),
('01/05/2016', dict(timezone='US/Central'),
'2016-01-05 06:00:00+00:00'),
])
def test_parse(string, kwds, expected):
d = maya.parse(string, **kwds)
assert format(d) == expected
@pytest.mark.usefixtures("frozen_now")
def test_when_past():
two_days_away = maya.now().add(days=2)
past_date = maya.when(
two_days_away.slang_date(),
prefer_dates_from='past')
assert past_date < maya.now()
@pytest.mark.usefixtures("frozen_now")
def test_when_future():
two_days_away = maya.now().add(days=2)
future_date = maya.when(
two_days_away.slang_date(),
prefer_dates_from='future')
assert future_date > maya.now()
@pytest.mark.usefixtures("frozen_now")
def test_when_past_day_name():
two_days_away = maya.now().add(days=2)
past_date = maya.when(
calendar.day_name[two_days_away.weekday],
prefer_dates_from='past')
assert past_date < maya.now()
@pytest.mark.usefixtures("frozen_now")
def test_when_future_day_name():
two_days_away = maya.now().add(days=2)
future_date = maya.when(
calendar.day_name[two_days_away.weekday],
prefer_dates_from='future')
assert future_date > maya.now()
def test_datetime_to_timezone():
dt = maya.when('2016-01-01').datetime(to_timezone='US/Eastern')
assert dt.tzinfo.zone == 'US/Eastern'
def test_rfc3339():
mdt = maya.when('2016-01-01')
out = mdt.rfc3339()
mdt2 = maya.MayaDT.from_rfc3339(out)
assert mdt.epoch == mdt2.epoch
@pytest.mark.usefixtures("frozen_now")
def test_comparison_operations():
now = maya.now()
now_copy = copy.deepcopy(now)
tomorrow = maya.when('tomorrow')
assert (now == now_copy) is True
assert (now == tomorrow) is False
assert (now != now_copy) is False
assert (now != tomorrow) is True
assert (now < now_copy) is False
assert (now < tomorrow) is True
assert (now <= now_copy) is True
assert (now <= tomorrow) is True
assert (now > now_copy) is False
assert (now > tomorrow) is False
assert (now >= now_copy) is True
assert (now >= tomorrow) is False
# Check Exceptions
with pytest.raises(TypeError):
now == 1
with pytest.raises(TypeError):
now != 1
with pytest.raises(TypeError):
now < 1
with pytest.raises(TypeError):
now <= 1
with pytest.raises(TypeError):
now > 1
with pytest.raises(TypeError):
now >= 1
def test_seconds_or_timedelta():
# test for value in seconds
assert _seconds_or_timedelta(1234) == timedelta(0, 1234)
# test for value as `datetime.timedelta`
assert _seconds_or_timedelta(timedelta(0, 1234)) == timedelta(0, 1234)
# test for invalid value
with pytest.raises(TypeError):
_seconds_or_timedelta('invalid interval')
@pytest.mark.usefixtures("frozen_now")
def test_intervals():
now = maya.now()
tomorrow = now.add(days=1)
assert len(list(maya.intervals(now, tomorrow, 60 * 60))) == 24
@pytest.mark.usefixtures("frozen_now")
def test_dunder_add():
now = maya.now()
assert now + 1 == now.add(seconds=1)
assert now + timedelta(seconds=1) == now.add(seconds=1)
@pytest.mark.usefixtures("frozen_now")
def test_dunder_radd():
now = maya.now()
assert now.add(seconds=1) == now + 1
assert now.add(seconds=1) == now + timedelta(seconds=1)
@pytest.mark.usefixtures("frozen_now")
def test_dunder_sub():
now = maya.now()
assert now - 1 == now.subtract(seconds=1)
assert now - timedelta(seconds=1) == now.subtract(seconds=1)
@pytest.mark.usefixtures("frozen_now")
def test_mayaDT_sub():
now = maya.now()
then = now.add(days=1)
assert then - now == timedelta(seconds=24 * 60 * 60)
assert now - then == timedelta(seconds=-24 * 60 * 60)
def test_core_local_timezone(monkeypatch):
@property
def mock_local_tz(self):
class StaticTzInfo(object):
zone = 'local'
def __repr__(self):
return "<StaticTzInfo 'local'>"
return StaticTzInfo()
monkeypatch.setattr(maya.MayaDT, '_local_tz', mock_local_tz)
mdt = maya.MayaDT(0)
assert mdt.local_timezone == 'UTC'
@pytest.mark.parametrize("when_str,snap_str,expected_when", [
('Mon, 21 Feb 1994 21:21:42 GMT', '@d',
'Mon, 21 Feb 1994 00:00:00 GMT'),
])
def test_snaptime(when_str, snap_str, expected_when):
# given
dt = maya.when(when_str)
# when
dt = dt.snap(snap_str)
# then
assert dt == maya.when(expected_when)
+572
View File
@@ -0,0 +1,572 @@
import random
from datetime import datetime, timedelta
import pytest
import pytz
import maya
from maya.compat import cmp
Los_Angeles = pytz.timezone('America/Los_Angeles')
New_York = pytz.timezone('America/New_York')
Melbourne = pytz.timezone('Australia/Melbourne')
def test_interval_requires_2_of_start_end_duration():
start = maya.now()
end = start.add(hours=1)
with pytest.raises(ValueError):
maya.MayaInterval(start=start)
with pytest.raises(ValueError):
maya.MayaInterval(end=end)
with pytest.raises(ValueError):
maya.MayaInterval(duration=60)
with pytest.raises(ValueError):
maya.MayaInterval(start=start, end=end, duration=60)
maya.MayaInterval(start=start, end=end)
maya.MayaInterval(start=start, duration=60)
maya.MayaInterval(end=end, duration=60)
def test_interval_requires_end_time_after_or_on_start_time():
with pytest.raises(ValueError):
maya.MayaInterval(start=maya.now(), duration=0)
maya.MayaInterval(start=maya.now(), duration=-1)
def test_interval_init_start_end():
start = maya.now()
end = start.add(hours=1)
interval = maya.MayaInterval(start=start, end=end)
assert interval.start == start
assert interval.end == end
def test_interval_init_start_duration():
start = maya.now()
duration = 1
interval = maya.MayaInterval(start=start, duration=duration)
assert interval.start == start
assert interval.end == start.add(seconds=duration)
def test_interval_init_end_duration():
end = maya.now()
duration = 1
interval = maya.MayaInterval(end=end, duration=duration)
assert interval.end == end
assert interval.start == end.subtract(seconds=duration)
@pytest.mark.parametrize(
'start_doy1,end_doy1,start_doy2,end_doy2,intersection_doys',
(
(0, 2, 1, 3, (1, 2)),
(0, 2, 3, 4, None),
(0, 2, 2, 3, None),
(0, 1, 0, 1, (0, 1)),
(1, 1, 1, 3, (1, 1)),
(1, 1, 1, 1, (1, 1)),
(1, 1, 2, 3, None),
(2, 2, 1, 3, (2, 2)),
(1, 3, 1, 1, (1, 1)),
(2, 3, 1, 1, None),
(1, 3, 2, 2, (2, 2)),
),
ids=(
'overlapping',
'non-overlapping',
'adjacent',
'equal',
'instant overlapping start only',
'instant equal',
'instant disjoint',
'instant overlapping',
'instant overlapping start only (left)',
'instant disjoint (left)',
'instant overlapping (left)',
),
)
def test_interval_intersection(
start_doy1, end_doy1, start_doy2, end_doy2, intersection_doys
):
base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
interval1 = maya.MayaInterval(
base.add(days=start_doy1), base.add(days=end_doy1)
)
interval2 = maya.MayaInterval(
base.add(days=start_doy2), base.add(days=end_doy2)
)
if intersection_doys:
start_doy_intersection, end_doy_intersection = intersection_doys
assert interval1 & interval2 == maya.MayaInterval(
base.add(days=start_doy_intersection),
base.add(days=end_doy_intersection),
)
else:
assert (interval1 & interval2) is None
# check invalid argument
with pytest.raises(TypeError):
interval1 & 'invalid type'
def test_interval_intersects():
base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
interval = maya.MayaInterval(base, base.add(days=1))
assert interval.intersects(interval)
assert not interval.intersects(
maya.MayaInterval(base.add(days=2), base.add(days=3))
)
# check invalid argument
with pytest.raises(TypeError):
interval.intersects('invalid type')
def test_and_operator():
base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
interval1 = maya.MayaInterval(base, base.add(days=2))
interval2 = maya.MayaInterval(base.add(days=1), base.add(days=3))
assert (
interval1 & interval2 ==
interval2 & interval1 ==
interval1.intersection(interval2)
)
# check invalid argument
with pytest.raises(TypeError):
interval1.intersection('invalid type')
def test_interval_eq_operator():
start = maya.now()
end = start.add(hours=1)
interval = maya.MayaInterval(start=start, end=end)
assert interval == maya.MayaInterval(start=start, end=end)
assert interval != maya.MayaInterval(start=start, end=end.add(days=1))
# check invalid argument
with pytest.raises(TypeError):
interval == 'invalid type'
with pytest.raises(TypeError):
interval != 'invalid type'
def test_interval_timedelta():
start = maya.now()
delta = timedelta(hours=1)
interval = maya.MayaInterval(start=start, duration=delta)
assert interval.timedelta == delta
def test_interval_duration():
start = maya.now()
delta = timedelta(hours=1)
interval = maya.MayaInterval(start=start, duration=delta)
assert interval.duration == delta.total_seconds()
@pytest.mark.parametrize(
'start_doy1,end_doy1,start_doy2,end_doy2,expected',
(
(0, 2, 1, 3, False),
(0, 2, 3, 4, False),
(0, 2, 2, 3, False),
(0, 1, 0, 1, True),
(0, 3, 1, 2, True),
),
ids=('overlapping', 'non-overlapping', 'adjacent', 'equal', 'subset'),
)
def test_interval_contains(
start_doy1, end_doy1, start_doy2, end_doy2, expected
):
base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
interval1 = maya.MayaInterval(
base.add(days=start_doy1), base.add(days=end_doy1)
)
interval2 = maya.MayaInterval(
base.add(days=start_doy2), base.add(days=end_doy2)
)
assert interval1.contains(interval2) is expected
assert (interval2 in interval1) is expected
# check invalid argument
with pytest.raises(TypeError):
interval1.contains('invalid type')
@pytest.mark.parametrize(
'start_doy,end_doy,dt_doy,expected',
(
(2, 4, 1, False),
(2, 4, 2, True),
(2, 4, 3, True),
(2, 4, 4, False),
(2, 4, 5, False),
),
ids=('before-start', 'on-start', 'during', 'on-end', 'after-end'),
)
def test_interval_in_operator_maya_dt(start_doy, end_doy, dt_doy, expected):
base = maya.MayaDT.from_datetime(datetime(2016, 1, 1))
interval = maya.MayaInterval(
start=base.add(days=start_doy), end=base.add(days=end_doy)
)
dt = base.add(days=dt_doy)
assert (dt in interval) is expected
# check invalid argument
with pytest.raises(TypeError):
'invalid type' in interval
def test_interval_hash():
start = maya.now()
end = start.add(hours=1)
interval = maya.MayaInterval(start=start, end=end)
assert hash(interval) == hash(maya.MayaInterval(start=start, end=end))
assert hash(interval) != hash(
maya.MayaInterval(start=start, end=end.add(days=1))
)
def test_interval_iter():
start = maya.now()
end = start.add(days=1)
assert tuple(maya.MayaInterval(start=start, end=end)) == (start, end)
@pytest.mark.parametrize(
'start1,end1,start2,end2,expected',
[(1, 2, 1, 2, 0), (1, 3, 2, 4, -1), (2, 4, 1, 3, 1), (1, 2, 1, 3, -1)],
ids=(
'equal',
'less-than',
'greater-than',
'use-end-time-if-start-time-identical',
),
)
def test_interval_cmp(start1, end1, start2, end2, expected):
base = maya.now()
interval1 = maya.MayaInterval(
start=base.add(days=start1), end=base.add(days=end1)
)
interval2 = maya.MayaInterval(
start=base.add(days=start2), end=base.add(days=end2)
)
assert cmp(interval1, interval2) == expected
# check invalid argument
with pytest.raises(TypeError):
cmp(interval1, 'invalid type')
@pytest.mark.parametrize(
'start1,end1,start2,end2,expected',
[
(1, 2, 2, 3, [(1, 3)]),
(1, 3, 2, 4, [(1, 4)]),
(1, 2, 3, 4, [(1, 2), (3, 4)]),
(1, 5, 2, 3, [(1, 5)]),
],
ids=('adjacent', 'overlapping', 'non-overlapping', 'contains'),
)
def test_interval_combine(start1, end1, start2, end2, expected):
base = maya.now()
interval1 = maya.MayaInterval(
start=base.add(days=start1), end=base.add(days=end1)
)
interval2 = maya.MayaInterval(
start=base.add(days=start2), end=base.add(days=end2)
)
expected_intervals = [
maya.MayaInterval(start=base.add(days=start), end=base.add(days=end))
for start, end in expected
]
assert interval1.combine(interval2) == expected_intervals
assert interval2.combine(interval1) == expected_intervals
# check invalid argument
with pytest.raises(TypeError):
interval2.combine('invalid type')
@pytest.mark.parametrize(
'start1,end1,start2,end2,expected',
[
(1, 2, 3, 4, [(1, 2)]),
(1, 2, 2, 4, [(1, 2)]),
(2, 3, 1, 4, []),
(1, 4, 2, 3, [(1, 2), (3, 4)]),
(1, 4, 0, 2, [(2, 4)]),
(1, 4, 3, 5, [(1, 3)]),
(1, 4, 1, 2, [(2, 4)]),
(1, 4, 3, 4, [(1, 3)]),
],
ids=(
'non-overlapping',
'adjacent',
'contains',
'splits',
'overlaps-left',
'overlaps-right',
'overlaps-left-identical-start',
'overlaps-right-identical-end',
),
)
def test_interval_subtract(start1, end1, start2, end2, expected):
base = maya.now()
interval1 = maya.MayaInterval(
start=base.add(days=start1), end=base.add(days=end1)
)
interval2 = maya.MayaInterval(
start=base.add(days=start2), end=base.add(days=end2)
)
expected_intervals = [
maya.MayaInterval(start=base.add(days=start), end=base.add(days=end))
for start, end in expected
]
assert interval1.subtract(interval2) == expected_intervals
# check invalid argument
with pytest.raises(TypeError):
interval1.subtract('invalid type')
@pytest.mark.parametrize(
'start1,end1,start2,end2,expected',
[
(1, 2, 2, 3, True),
(2, 3, 1, 2, True),
(1, 3, 2, 3, False),
(2, 3, 4, 5, False),
],
ids=('adjacent-right', 'adjacent-left', 'overlapping', 'non-overlapping'),
)
def test_interval_is_adjacent(start1, end1, start2, end2, expected):
base = maya.now()
interval1 = maya.MayaInterval(
start=base.add(days=start1), end=base.add(days=end1)
)
interval2 = maya.MayaInterval(
start=base.add(days=start2), end=base.add(days=end2)
)
assert interval1.is_adjacent(interval2) == expected
# check invalid argument
with pytest.raises(TypeError):
interval1.is_adjacent('invalid type')
@pytest.mark.parametrize(
'start,end,delta,include_remainder,expected',
[
(0, 10, 5, False, [(0, 5), (5, 10)]),
(0, 10, 5, True, [(0, 5), (5, 10)]),
(0, 10, 3, False, [(0, 3), (3, 6), (6, 9)]),
(0, 10, 3, True, [(0, 3), (3, 6), (6, 9), (9, 10)]),
(0, 2, 5, False, []),
(0, 2, 5, True, [(0, 2)]),
],
ids=(
'even-split',
'even-split-include-partial',
'uneven-split-do-not-include-partial',
'uneven-split-include-partial',
'delta-larger-than-timepsan-do-not-include-partial',
'delta-larger-than-timepsan-include-partial',
),
)
def test_interval_split(start, end, delta, include_remainder, expected):
base = maya.now()
interval = maya.MayaInterval(
start=base.add(days=start), end=base.add(days=end)
)
delta = timedelta(days=delta)
expected_intervals = [
maya.MayaInterval(start=base.add(days=s), end=base.add(days=e))
for s, e in expected
]
assert expected_intervals == list(
interval.split(delta, include_remainder=include_remainder)
)
def test_interval_split_non_positive_delta():
start = maya.now()
end = start.add(days=1)
interval = maya.MayaInterval(start=start, end=end)
with pytest.raises(ValueError):
list(interval.split(timedelta(seconds=0)))
with pytest.raises(ValueError):
list(interval.split(timedelta(seconds=-10)))
@pytest.mark.parametrize(
'start,end,minutes,timezone,snap_out,expected_start,expected_end',
[
((5, 12), (8, 48), 30, None, False, (5, 30), (8, 30)),
((5, 12), (8, 48), 30, None, True, (5, 0), (9, 0)),
((5, 15), (9, 0), 15, None, False, (5, 15), (9, 0)),
((5, 15), (9, 0), 15, None, True, (5, 15), (9, 0)),
((6, 50), (9, 15), 60, 'America/New_York', False, (7, 0), (9, 0)),
((6, 50), (9, 15), 60, 'America/New_York', True, (6, 0), (10, 0)),
((6, 20), (6, 50), 60, None, False, (6, 0), (6, 0)),
((6, 20), (6, 50), 60, None, True, (6, 0), (7, 0)),
((6, 20), (6, 50), 60, 'America/Chicago', False, (6, 0), (6, 0)),
((6, 20), (6, 50), 60, 'America/Chicago', True, (6, 0), (7, 0)),
],
ids=(
'normal',
'normal-snap_out',
'already-quantized',
'already-quantized-snap_out',
'with-timezone',
'with-timezone-snap_out',
'too-small',
'too-small-snap_out',
'too-small-with-timezone',
'too-small-with-timezone-snap_out',
),
)
def test_quantize(
start, end, minutes, timezone, snap_out, expected_start, expected_end
):
base = maya.MayaDT.from_datetime(datetime(2017, 1, 1))
interval = maya.MayaInterval(
start=base.add(hours=start[0], minutes=start[1]),
end=base.add(hours=end[0], minutes=end[1]),
)
kwargs = {'timezone': timezone} if timezone is not None else {}
quantized_interval = interval.quantize(
timedelta(minutes=minutes), snap_out=snap_out, **kwargs
)
assert quantized_interval == maya.MayaInterval(
start=base.add(hours=expected_start[0], minutes=expected_start[1]),
end=base.add(hours=expected_end[0], minutes=expected_end[1]),
)
def test_quantize_invalid_delta():
start = maya.now()
end = start.add(days=1)
interval = maya.MayaInterval(start=start, end=end)
with pytest.raises(ValueError):
interval.quantize(timedelta(minutes=0))
with pytest.raises(ValueError):
interval.quantize(timedelta(minutes=-1))
def test_interval_flatten_non_overlapping():
step = 2
max_hour = 20
base = maya.now()
intervals = [
maya.MayaInterval(
start=base.add(hours=hour), duration=timedelta(hours=step - 1)
)
for hour in range(0, max_hour, step)
]
random.shuffle(intervals)
assert maya.MayaInterval.flatten(intervals) == sorted(intervals)
def test_interval_flatten_adjacent():
step = 2
max_hour = 20
base = maya.when('jan/1/2011')
intervals = [
maya.MayaInterval(
start=base.add(hours=hour), duration=timedelta(hours=step)
)
for hour in range(0, max_hour, step)
]
random.shuffle(intervals)
assert maya.MayaInterval.flatten(intervals) == [
maya.MayaInterval(start=base, duration=timedelta(hours=max_hour))
]
def test_interval_flatten_intersecting():
step = 2
max_hour = 20
base = maya.now()
intervals = [
maya.MayaInterval(
start=base.add(hours=hour),
duration=timedelta(hours=step, minutes=30),
)
for hour in range(0, max_hour, step)
]
random.shuffle(intervals)
assert maya.MayaInterval.flatten(intervals) == [
maya.MayaInterval(
start=base, duration=timedelta(hours=max_hour, minutes=30)
)
]
def test_interval_flatten_containing():
step = 2
max_hour = 20
base = maya.now()
containing_interval = maya.MayaInterval(
start=base, end=base.add(hours=max_hour + step)
)
intervals = [
maya.MayaInterval(
start=base.add(hours=hour), duration=timedelta(hours=step - 1)
)
for hour in range(2, max_hour, step)
]
intervals.append(containing_interval)
random.shuffle(intervals)
assert maya.MayaInterval.flatten(intervals) == [containing_interval]
def test_interval_from_datetime():
start = maya.now()
duration = timedelta(hours=1)
end = start + duration
interval = maya.MayaInterval.from_datetime(
start_dt=start.datetime(naive=False), end_dt=end.datetime(naive=False)
)
assert interval.start == start
assert interval.end == end
interval2 = maya.MayaInterval.from_datetime(
start_dt=start.datetime(naive=False), duration=duration
)
assert interval2.start == start
assert interval2.end == end
interval3 = maya.MayaInterval.from_datetime(
end_dt=end.datetime(naive=False), duration=duration
)
assert interval3.start == start
assert interval3.end == end
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
+14
View File
@@ -0,0 +1,14 @@
[tox]
envlist = py27, py34, py35, py36
[testenv]
deps = pipenv
commands=
pipenv install --dev --skip-lock
pytest tests/ {posargs}
[testenv:py27]
basepython = python2.7
deps =
{[testenv]deps}
funcsigs