diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 9d53c08..218bdb5 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -7,20 +7,19 @@ on: branches: - master jobs: - tests-python2: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - - run: sudo apt install python2 - - run: curl https://bootstrap.pypa.io/pip/2.7/get-pip.py --output get-pip.py - - run: sudo python2 get-pip.py - - run: pip2 install -r requirements-2.txt - - run: python2 tests.py tests-python3: - runs-on: ubuntu-20.04 + runs-on: ubuntu-20.04 # this should probably get updated to a python 3.9+ image steps: - uses: actions/checkout@v2 + - name: print python version + run: python3 --version - name: install requirements run: pip3 install -r requirements-3.txt - name: run the tests run: python3 tests.py + - name: verify type hints + run: mypy datemath + - name: verify package install + run: python3 setup.py install --user + - name: verify we can import + run: python3 -c "from datemath import datemath; print(datemath('now-1d'))" diff --git a/CHANGELOG.md b/CHANGELOG.md index 99cc1f3..bb5dae6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,48 +1,87 @@ # Changelog -## 1.5.5 (2021-04-26) -* [FIX] [Issue #28](https://github.com/nickmaccarthy/python-datemath/issues/28) + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] +### fixed +- Fix: Race condition in timezone tests: https://github.com/nickmaccarthy/python-datemath/issues/36 +- Fix: Updated arrow version: https://github.com/nickmaccarthy/python-datemath/issues/32 +- Fix: mypy type hint checking in tests: https://github.com/nickmaccarthy/python-datemath/issues/31 +- Fix: SyntaxWarning: invalid escape sequence in `re.match()`: https://github.com/nickmaccarthy/python-datemath/pull/39 +- Fix: Licence Classifier: https://github.com/nickmaccarthy/python-datemath/pull/34 +- Fix: Bump certifi to latest: https://github.com/nickmaccarthy/python-datemath/pull/38 +### added +- Feat: Typehint support: https://github.com/nickmaccarthy/python-datemath/issues/31 +- Feat: Revamed CHANGELOG.md to keepachangelog.org format + +### todo +- todo: Fix pypi: https://github.com/nickmaccarthy/python-datemath/issues/33 + +## deprecated +- python 2.7 support. Python 3.8+ will only be supported going forward + +## [1.5.5] - 2021-04-26 +### fixed +- fix: [Issue #28](https://github.com/nickmaccarthy/python-datemath/issues/28) * `datemath()` object now returns the expected `datetime` object instead of an `Arrow` object * added tests to catch invalid object types of helpers -## 1.5.4 (2021-04-20) -* skipped due to name conflict on pypi, all changes in this are from `1.5.3` +## [1.5.4] - 2021-04-20 +### Unused +- skipped due to name conflict on pypi, all changes in this are from `1.5.3` -## 1.5.3 (2021-04-16) -* [FIX] [Issue #25](https://github.com/nickmaccarthy/python-datemath/issues/25) - Fixed an issue where if you provided an invalid timestamp, i.e. `datemath('2')` you would not get an DateMathException back. Also bumped dependencies. +## [1.5.3] - 2021-04-16 +### fixed +- FIX: [Issue #25](https://github.com/nickmaccarthy/python-datemath/issues/25) - Fixed an issue where if you provided an invalid timestamp, i.e. `datemath('2')` you would not get an DateMathException back. Also bumped dependencies. -## 1.5.2 (2020-10-01) -* [FIX] [Issue #21](https://github.com/nickmaccarthy/python-datemath/issues/21) - Fixed an issue where if timezone offset was in a datetime string (ISO8601), the timezone of the returned datemath object would be UTC and not the timezone as specified in the datetime string. +## [1.5.2] - 2020-10-01 +### fixed +- FIX: [Issue #21](https://github.com/nickmaccarthy/python-datemath/issues/21) - Fixed an issue where if timezone offset was in a datetime string (ISO8601), the timezone of the returned datemath object would be UTC and not the timezone as specified in the datetime string. ## 1.5.1 (2020-03-25) -* [FIX] [Issue #15](https://github.com/nickmaccarthy/python-datemath/issues/15) - Fixed issue with parser finding invalid timeunits and throwing correct errors -* [NEW] [Issue #16](https://github.com/nickmaccarthy/python-datemath/issues/16) - Added support for parser to accecpt a epoch/unix timestamp but throw an error on epoch milli's since arrow can't support that. +### fixed +- FIX: [Issue #15](https://github.com/nickmaccarthy/python-datemath/issues/15) - Fixed issue with parser finding invalid timeunits and throwing correct errors +### added +- Feat: [Issue #16](https://github.com/nickmaccarthy/python-datemath/issues/16) - Added support for parser to accecpt a epoch/unix timestamp but throw an error on epoch milli's since arrow can't support that. -## 1.5.0 (2019-11-09) +## 1.5.0 - 2019-11-09 -* [FIX] [Issue #12](https://github.com/nickmaccarthy/python-datemath/issues/12) - missing VERSION.txt. Added MANIFEST.in for sdist build -* [FIX] [PR #13](https://github.com/nickmaccarthy/python-datemath/pull/13) - Fix `BaseException` to `Exception` inheritence, thank you for your contribution @yury-primer! +### fixed +- [Issue #12](https://github.com/nickmaccarthy/python-datemath/issues/12) - missing VERSION.txt. Added MANIFEST.in for sdist build +- [PR #13](https://github.com/nickmaccarthy/python-datemath/pull/13) - Fix `BaseException` to `Exception` inheritence, thank you for your contribution @yury-primer! -## 1.4.9 (2019-10-26) +## [1.4.9] - 2019-10-26 ** PLEASE DO NOT USE THIS VERSION, use `1.5.0+` instead. This may not compile on your system due to a missing VERSION.txt which was fixed in `1.5.0+` ** -* [FIX] [Issue #9](https://github.com/nickmaccarthy/python-datemath/issues/9) && [Issue #8](https://github.com/nickmaccarthy/python-datemath/issues/8) - Fixing deprecated arrow `replace()` function with `shift()`. -* [FIX] Arrow upgrade to `0.15.2` to fix the above mentioned issues -* [NEW] Breakout of python2 and python3 requirements -* [NEW] Breakout of python2 and python3 specific CICD pipelines -* [NEW] Derecated the following python version (although they may still work, they are no longer supported) - `2.4`,`2.6`,`3.4`,`3.5` -* [FIX] Modifed `tests.py` to account for the timestamp change (tz is now `+0000`, instead of `-0000`) -* [FIX] replaced `ts = ts.replace(tzinfo=tz.gettz(timezone))` with `ts = ts.replace(tzinfo=timezone)` in `datemath.helpers.parseTime()` to fix [Issue #7](https://github.com/nickmaccarthy/python-datemath/issues/7) - -## v1.4.8 (2019-10-25) + +### fixed +- [FIX] [Issue #9](https://github.com/nickmaccarthy/python-datemath/issues/9) && [Issue #8](https://github.com/nickmaccarthy/python-datemath/issues/8) - Fixing deprecated arrow `replace()` function with `shift()`. +- [FIX] Arrow upgrade to `0.15.2` to fix the above mentioned issues +- [FIX] Modifed `tests.py` to account for the timestamp change (tz is now `+0000`, instead of `-0000`) +- [FIX] replaced `ts = ts.replace(tzinfo=tz.gettz(timezone))` with `ts = ts.replace(tzinfo=timezone)` in `datemath.helpers.parseTime()` to fix [Issue #7](https://github.com/nickmaccarthy/python-datemath/issues/7) +### added + +- [NEW] Breakout of python2 and python3 requirements +- [NEW] Breakout of python2 and python3 specific CICD pipelines +- [NEW] Derecated the following python version (although they may still work, they are no longer supported) - `2.4`,`2.6`,`3.4`,`3.5` + + +## [1.4.8] - 2019-10-25 +** dont use this version ** * skipped due to name conflict on pypi, all changes are in `1.4.9` -## v1.4.7 (2017-11-10) -* [FIX] Fixed timezone for date strings: [Issue #6](https://github.com/nickmaccarthy/python-datemath/issues/6) +## [1.4.7] - 2017-11-10 +### fixed +- [FIX] Fixed timezone for date strings: [Issue #6](https://github.com/nickmaccarthy/python-datemath/issues/6) -## v1.4.5 (2017-03-21) -* [NEW] Added roundDown functionality. Allows user to specify the default rounding for expressions such as `/d`. -* example - assuming the time is currently 2016-01-01 12:00:00, we should get the following +## [1.4.5] - 2017-03-21 +### added +- [NEW] Added roundDown functionality. Allows user to specify the default rounding for expressions such as `/d`. +- example - assuming the time is currently 2016-01-01 12:00:00, we should get the following ``` >>> # now = 2016-01-01 14:00:00+00:00 >>> dm('now+/d', roundDown=False) @@ -51,8 +90,10 @@ ``` -## v1.4.4 (2016-12-28) -* [FIX] Fixed bug with expression logic and rounding: https://github.com/nickmaccarthy/python-datemath/pull/2 +## [1.4.4] - 2016-12-28 +### fixed +- [FIX] Fixed bug with expression logic and rounding: https://github.com/nickmaccarthy/python-datemath/pull/2 -## 1.4.3 (2016-03-31) -* [NEW] Floats are now supported for days, hours, and seconds units. Example ```now-2.5d```, ```now-3.2h```. Any other unit other than days, hours, or seconds that is a float will be converted to an int and floored due to the datetime() module not being able to handle them. \ No newline at end of file +## [1.4.3] - 2016-03-31 +### added +[NEW] Floats are now supported for days, hours, and seconds units. Example ```now-2.5d```, ```now-3.2h```. Any other unit other than days, hours, or seconds that is a float will be converted to an int and floored due to the datetime() module not being able to handle them. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..91f7e43 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ + + +tests: + python3 tests.py + diff --git a/README.md b/README.md index 7d0f9cd..2545e33 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,12 @@ -[![Build Status](https://travis-ci.org/nickmaccarthy/python-datemath.svg?branch=master)](https://travis-ci.org/nickmaccarthy/python-datemath.svg?branch=master) - # Python Datemath ## What? -A date math (aka datemath) parser compatiable with the elasticsearch 'date math' format - -## Why? - -Working with date objects in python has always been interesting. Having a background in php, I have been looking for quite some time ( no pun intended ) for a way to do date time interpolation similar to php's ```strtotime()``` function. While the arrow module comes close, I needed something that could turn date math type strings into datetime objects for use in [tattle.io](http://tattle.io) and other projects I use in elasticsearch. I have found even more uses for it, including AWS cloudwatch and various other projects and hopefully you will too. +A date math (aka datemath) parser compatiable with the elasticsearch "date math" format -## What is date math? +## What is "date math"? -Date Math is the short hand arithmetic to find relative time to fixed moments in date and time. Similar to the SOLR date math format, Elasticsearch has its own built in format for short hand date math and this module aims to support that same coverage in python. - -Documentation from elasticsearch: -[http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/mapping-date-format.html#date-math](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/mapping-date-format.html#date-math) +Date Math is the short hand arithmetic to find relative time to fixed moments in date and time. Similar to the SOLR date math format, we aim to support that same coverage in python. > The date type supports using date math expression when using it in a query/filter (mainly makes sense in range query/filter). > @@ -31,6 +22,7 @@ Documentation from elasticsearch: ## Unit Maps +The "unit maps" here define the shorthand sytax for the dates/timeframes we are working with: ```yaml y or Y = 'year' M = 'month' @@ -39,17 +31,14 @@ d or D = 'day' w = 'week' h or H = 'hour' s or S = 'second' -``` - -## Install - -```python -pip install python-datemath +now = ``` ## Examples -Assuming our datetime is currently: `2016-01-01T00:00:00-00:00` +Here are some examples of using date math to find dates both in the past and in the future + +Assuming our "now" datetime is currently: `2016-01-01T00:00:00-00:00` ```yaml Expression: Result: @@ -73,7 +62,7 @@ now/Y 2016-12-31T23:59:59+00:00 ## Usage -By default datemath return an arrow date object representing your timestamp. +If you use the `dm` function in the datemath module, we will return an arrow date object representing your timestamp. ```python >>> from datemath import dm @@ -118,8 +107,25 @@ If you would rather have a string, you can use arrow's ```.format()``` method. u'2015.12.18' ``` -Rather have a python datetime object instead? Just pass along the 'datetime' type +If you would rather have your time object come back in standard python `datetime`, use the `datemath` function instead: + +```python +>>> from datemath import datemath +>>> ## Assuming "now" is 2016-01-01T00:00:00 +>>> datemath("now-1h") +datetime.datetime(2015, 12, 31, 23, 0, tzinfo=tzutc()) +# Cast it as a str() get a string of the timestamp back too +>>> str(datemath("now-1h")) +'2015-12-31 23:00:00+00:00' +>>> # roundDown=True is default and implied +>>> datemath('2016-01-01T16:20:00||/d') +datetime.datetime(2016, 1, 1, 0, 0, tzinfo=tzutc()) +>>> # Using the roundDown option +>>> datemath('2016-01-01T16:20:00||/d', roundDown=False) +datetime.datetime(2016, 1, 1, 23, 59, 59, 999999, tzinfo=tzutc()) +``` +Or you can use the `dm` function and set its `type` to `datetime`: ```python from datemath import dm >>> dm('now', type='datetime') @@ -129,21 +135,7 @@ datetime.datetime(2016, 1, 22, 22, 58, 28, 338060, tzinfo=tzutc()) datetime.datetime(2016, 1, 24, 22, 57, 45, 394470, tzinfo=tzutc()) ``` -Or you can just import the `datemath` module, this will always give us a native `datetime` object - -```python ->>> from datemath import datemath ->>> ->>> datemath('2016-01-01T16:20:00||/d', roundDown=False) -datetime.datetime(2016, 1, 1, 23, 59, 59, 999999, tzinfo=tzutc()) ->>> ->>> ->>> # roundDown=True is default and implied ->>> datemath('2016-01-01T16:20:00||/d') -datetime.datetime(2016, 1, 1, 0, 0, tzinfo=tzutc()) -``` - -If you want a Epoch timestamp back instead, we can do that. +If you want a Epoch timestamp back instead, we can do that too. ```python >>> dm('now+2d-1m', type='timestamp') @@ -152,7 +144,7 @@ If you want a Epoch timestamp back instead, we can do that. ## What timezone are my objects in? -By default all object returned by datemath are in UTC. +By default all objects returned by datemath are in UTC. If you want them them back in a different timezone, just pass along the ```tz``` argument. Timezone list can be found here: [https://gist.github.com/pamelafox/986163](https://gist.github.com/pamelafox/986163) @@ -188,6 +180,12 @@ Note - currently timestrings with a timezone offset and the usage of the ```tz`` ``` +## Install + +```python +pip install python-datemath +``` + ## Debugging If you would like more verbose output to debug the process of what datemath is doing, simply set `export DATEMATH_DEBUG=true` in your shell then run some datemath tests. To stop debugging, run `unset DATEMATH_DEBUG`. diff --git a/RELEASE.md b/RELEASE.md index 837fedf..88619e3 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,5 +1,5 @@ # How to release -* Create a new tag/release in github. +* Create a new tag/release in github. * Ensure new tag version matches VERSION.txt * Actions should take care of the rest diff --git a/VERSION.txt b/VERSION.txt index 9075be4..cb2b00e 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.5.5 +3.0.1 diff --git a/datemath/helpers.py b/datemath/helpers.py index f953611..62e6b16 100644 --- a/datemath/helpers.py +++ b/datemath/helpers.py @@ -39,19 +39,22 @@ ''' import arrow +from arrow import Arrow +from datetime import datetime import re import os from dateutil import tz import dateutil import sys from pprint import pprint +from typing import Any, Optional debug = True if os.environ.get('DATEMATH_DEBUG') else False class DateMathException(Exception): pass -def unitMap(c): +def unitMap(c: str) -> str: ''' maps our units ( 'd', 'y', 'M', etc ) to shorthands required for arrow ''' @@ -75,14 +78,7 @@ def unitMap(c): else: raise DateMathException("Not a valid timeunit: {0}".format(c)) -def as_datetime(expression, now, tz='UTC'): - ''' - returns our datemath expression as a python datetime object - note: this has been deprecated and the 'type' argument in parse is the current way - ''' - return parse(expression, now, tz) - -def parse(expression, now=None, tz='UTC', type=None, roundDown=True): +def parse(expression: str, now: Any = None, tz: str = 'UTC', type: Any = None, roundDown: bool = True) -> Arrow: ''' the main meat and potatoes of this this whole thing takes our datemath expression and does our date math @@ -99,7 +95,7 @@ def parse(expression, now=None, tz='UTC', type=None, roundDown=True): if debug: print("parse() - Orig Expression: {0}".format(expression)) math = '' - time = '' + time = None if 'UTC' not in tz: if debug: print("parse() - will now convert tz to {0}".format(tz)) @@ -111,7 +107,7 @@ def parse(expression, now=None, tz='UTC', type=None, roundDown=True): return getattr(now, type) else: return now - elif re.match('\d{10,}', str(expression)): + elif re.match(r'\d{10,}', str(expression)): if debug: print('parse() - found an epoch timestamp') if len(str(expression)) == 13: raise DateMathException('Unable to parse epoch timestamps in millis, please convert to the nearest second to continue - i.e. 1451610061 / 1000') @@ -150,7 +146,7 @@ def parse(expression, now=None, tz='UTC', type=None, roundDown=True): else: return rettime -def parseTime(timestamp, timezone='UTC'): +def parseTime(timestamp: str, timezone: str = 'UTC') -> Arrow: ''' parses a datetime string and returns and arrow object ''' @@ -179,7 +175,7 @@ def parseTime(timestamp, timezone='UTC'): if debug: print('parseTime() - Doesnt look like we have a valid timestamp, raise an exception. timestamp={}'.format(timestamp)) raise DateMathException('Valid length timestamp not provide, you gave me a timestamp of "{}", but I need something that has a len() >= 4'.format(timestamp)) -def roundDate(now, unit, tz='UTC', roundDown=True): +def roundDate(now: Any, unit: str, tz: str = 'UTC', roundDown: bool = True) -> Arrow: ''' rounds our date object ''' @@ -190,7 +186,7 @@ def roundDate(now, unit, tz='UTC', roundDown=True): if debug: print("roundDate() Now: {0}".format(now)) return now -def calculate(now, offsetval, unit): +def calculate(now: Arrow, offsetval: float, unit: str) -> Arrow: ''' calculates our dateobject using arrows replace method see unitMap() for more details @@ -204,13 +200,13 @@ def calculate(now, offsetval, unit): except Exception as e: raise DateMathException('Unable to calculate date: now: {0}, offsetvalue: {1}, unit: {2} - reason: {3}'.format(now,offsetval,unit,e)) -def evaluate(expression, now, timeZone='UTC', roundDown=True): +def evaluate(expression: str, now: Arrow, timeZone: str = 'UTC', roundDown: bool = True) -> Arrow: ''' evaluates our datemath style expression ''' if debug: print('evaluate() - Expression: {0}'.format(expression)) if debug: print('evaluate() - Now: {0}'.format(now)) - val = 0 + val = float(0) i = 0 while i < len(expression): char = expression[i] @@ -225,7 +221,7 @@ def evaluate(expression, now, timeZone='UTC', roundDown=True): val = 0 try: - m = re.match('(\d*[.]?\d+)[\w+-/]', expression[i+1:]) + m = re.match(r'(\d*[.]?\d+)[\w+-/]', expression[i+1:]) if m: num = m.group(1) val = val * 10 + float(num) @@ -239,7 +235,7 @@ def evaluate(expression, now, timeZone='UTC', roundDown=True): val = float(val) else: val = float(-val) - elif re.match('[a-zA-Z]+', char): + elif re.match(r'[a-zA-Z]+', char): now = calculate(now, val, unitMap(char)) else: raise DateMathException(''''{}' is not a valid timeunit for expression: '{}' '''.format(char, expression)) diff --git a/requirements-3.txt b/requirements-3.txt index 268d6ea..a4920a8 100644 --- a/requirements-3.txt +++ b/requirements-3.txt @@ -1,18 +1,22 @@ appdirs==1.4.3 args==0.1.0 -arrow==0.15.2 +arrow==1.2.3 bleach==3.3.0 -certifi==2019.9.11 +certifi==2024.7.4 chardet==3.0.4 clint==0.5.1 docutils==0.15.2 +freezegun==1.2.2 idna==2.7 linecache2==1.0.0 +mypy==1.5.1 +mypy-extensions==1.0.0 packaging==16.8 pkginfo==1.4.2 Pygments==2.7.4 pyparsing==2.2.0 -python-dateutil==2.6.0 +python-dateutil==2.8.2 +pytz==2023.3 readme-renderer==24.0 requests==2.20.0 requests-toolbelt==0.9.1 @@ -20,6 +24,9 @@ six==1.10.0 tqdm==4.36.1 traceback2==1.4.0 twine==2.0.0 +types-python-dateutil==2.8.19.14 +typing_extensions==4.7.1 +tzdata==2024.1 unittest2==1.1.0 urllib3==1.24.3 webencodings==0.5.1 diff --git a/setup.py b/setup.py index 8f9a915..38af6db 100644 --- a/setup.py +++ b/setup.py @@ -40,7 +40,7 @@ author_email='nickmaccarthy@gmail.com', # Choose your license - license='MIT', + license='Apache-2.0', # See https://pypi.python.org/pypi?%3Aaction=list_classifiers classifiers=[ @@ -55,14 +55,14 @@ 'Topic :: Software Development :: Build Tools', # Pick your license as you wish (should match "license" above) - 'License :: OSI Approved :: MIT License', + 'Apache-2.0', # Specify the Python versions you support here. In particular, ensure # that you indicate whether you support Python 2, Python 3 or both. - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7' + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', ], # What does your project relate to? diff --git a/tests.py b/tests.py index 700aa5a..c0f9900 100644 --- a/tests.py +++ b/tests.py @@ -1,20 +1,22 @@ # !/usr/bin/python # coding=utf-8 +import unittest -import unittest2 as unittest import arrow from datetime import datetime as pydatetime from datetime import timedelta from datemath import dm, datemath from datemath.helpers import DateMathException as DateMathException from dateutil import tz +from freezegun import freeze_time +import pytz iso8601 = 'YYYY-MM-DDTHH:mm:ssZZ' class TestDM(unittest.TestCase): - def testParse(self): + def testBasic(self): # Make sure our helpers return the correct objects self.assertIsInstance(datemath('now'), pydatetime) self.assertIsInstance(dm('now'), arrow.arrow.Arrow) @@ -24,6 +26,8 @@ def testParse(self): self.assertEqual(dm('2016-01-02').format(iso8601), '2016-01-02T00:00:00+00:00') self.assertEqual(dm('2016-01-02 01:00:00').format(iso8601), '2016-01-02T01:00:00+00:00') + + def testRounding(self): # Rounding Tests self.assertEqual(dm('2016-01-01||/d').format('YYYY-MM-DDTHH:mm:ssZZ'), '2016-01-01T00:00:00+00:00') self.assertEqual(dm('2014-11-18||/y').format('YYYY-MM-DDTHH:mm:ssZZ'), '2014-01-01T00:00:00+00:00') @@ -38,13 +42,25 @@ def testParse(self): self.assertEqual(dm('2016-01-01||/d', roundDown=False).format('YYYY-MM-DDTHH:mm:ssZZ'), '2016-01-01T23:59:59+00:00') self.assertEqual(dm('2014-11-18||/y', roundDown=False).format('YYYY-MM-DDTHH:mm:ssZZ'), '2014-12-31T23:59:59+00:00') + def testTimezone(self): # Timezone Tests - self.assertEqual(dm('now', tz='US/Pacific').format(iso8601), arrow.utcnow().to('US/Pacific').format(iso8601)) - self.assertEqual(dm('2017-09-22 10:20:00', tz='US/Pacific').datetime, pydatetime(2017, 9, 22, 10, 20, 00, tzinfo=tz.gettz('US/Pacific'))) - self.assertEqual(dm('2016-01-01', tz='UTC'), arrow.get('2016-01-01').to('UTC')) - self.assertEqual(dm('2016-01-01', tz='US/Eastern'), pydatetime(2016, 1, 1, tzinfo=tz.gettz('US/Eastern'))) - self.assertEqual(datemath('2016-01-01T01:00:00', tz='US/Central'), pydatetime(2016, 1, 1, 1, 0, 0, tzinfo=tz.gettz('US/Central'))) - self.assertEqual(datemath('2016-01-01T02:00:00', tz='US/Eastern'), pydatetime(2016, 1, 1, 2, tzinfo=tz.gettz('US/Eastern'))) + with freeze_time(datemath('now/d', tz='US/Pacific')): + self.assertEqual(datemath('now/d', tz='US/Pacific'), pydatetime.now(tz=pytz.timezone("US/Pacific"))) + + with freeze_time(pydatetime(2017, 9, 22, 10, 20, 00, tzinfo=tz.gettz('US/Pacific'))): + self.assertEqual(dm('2017-09-22 10:20:00', tz='US/Pacific').datetime, pydatetime.now(tz=pytz.timezone("US/Pacific"))) + + with freeze_time(datemath('2016-01-01T00:00:00', tz='UTC')): + self.assertEqual(dm('2016-01-01', tz='UTC'), arrow.get('2016-01-01').to('UTC')) + + with freeze_time(datemath('2016-01-01', tz='US/Eastern')): + self.assertEqual(dm('2016-01-01', tz='US/Eastern'), pydatetime(2016, 1, 1, tzinfo=tz.gettz('US/Eastern'))) + + with freeze_time(datemath('2016-01-01T01:00:00', tz='US/Central')): + self.assertEqual(datemath('2016-01-01T01:00:00', tz='US/Central'), pydatetime(2016, 1, 1, 1, 0, 0, tzinfo=tz.gettz('US/Central'))) + + with freeze_time(datemath('2016-01-01T02:00:00', tz='US/Eastern')): + self.assertEqual(datemath('2016-01-01T02:00:00', tz='US/Eastern'), pydatetime(2016, 1, 1, 2, tzinfo=tz.gettz('US/Eastern'))) # TZ offset inside of date string self.assertEqual(datemath('2016-01-01T16:20:00.5+12:00'), pydatetime(2016, 1, 1, 16, 20, 0, 500000, tzinfo=tz.tzoffset(None, timedelta(hours=12)))) @@ -58,7 +74,10 @@ def testParse(self): # If a TZ offset is in a datetime string, and there is a tz param used, the TZ offset will take precedence for the returned timeobj self.assertEqual(datemath('2016-01-01T16:20:00.6+12:00||+2d+1h', tz='US/Eastern'), pydatetime(2016, 1, 3, 17, 20, 0, 600000, tzinfo=tz.tzoffset(None, timedelta(hours=12)))) + + def testRelativeFormats(self): # relitive formats + # addition self.assertEqual(dm('+1s').format(iso8601), arrow.utcnow().shift(seconds=+1).format(iso8601)) self.assertEqual(dm('+1m').format(iso8601), arrow.utcnow().shift(minutes=+1).format(iso8601)) @@ -115,6 +134,8 @@ def testParse(self): self.assertEqual(dm('now+10d/d').format(iso8601), arrow.utcnow().shift(days=10).floor('day').format(iso8601)) self.assertEqual(dm('now-29d/d').format(iso8601), arrow.utcnow().shift(days=-29).floor('day').format(iso8601)) + + def testFuture(self): # future self.assertEqual(dm('+1s').format(iso8601), arrow.utcnow().shift(seconds=+1).format(iso8601)) self.assertEqual(dm('+1s+2m+3h').format(iso8601), arrow.utcnow().shift(seconds=+1, minutes=+2, hours=+3).format(iso8601)) @@ -134,7 +155,7 @@ def testParse(self): self.assertEqual(dm('-3w-2d-22h-36s').format(iso8601), arrow.utcnow().shift(weeks=-3, days=-2, hours=-22, seconds=-36).format(iso8601)) self.assertEqual(dm('-6y-3w-2d-22h-36s').format(iso8601), arrow.utcnow().shift(years=-6, weeks=-3, days=-2, hours=-22, seconds=-36).format(iso8601)) - + def testOther(self): import datetime delta = datetime.timedelta(seconds=1) # datetime objects @@ -157,6 +178,8 @@ def testParse(self): except DateMathException as e: self.assertTrue('Unable to parse epoch timestamps in millis' in str(e)) + + def testExceptions(self): # Catch invalid timeunits self.assertRaises(DateMathException, dm, '+1,') self.assertRaises(DateMathException, dm, '+1.') @@ -176,6 +199,7 @@ def testParse(self): if __name__ == "__main__": + print(datemath('now/d', tz='US/Pacific')) unittest.main()