diff --git a/.gitignore b/.gitignore index 29615ef8..f400f9a1 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ nosetests.xml .DS_Store .idea/* +.python-version /test.py /test_*.* @@ -27,3 +28,4 @@ benchmark.py results.json profile.html /wheelhouse +pyproject.lock diff --git a/.travis.yml b/.travis.yml index 73101e67..73fad2c3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,12 @@ matrix: - python: 3.6 env: PENDULUM_EXTENSIONS=0 - python: pypy + - python: pypy3 + +cache: + pip: true + directories: + - $HOME/.cache/pypoetry before_install: - pip install codecov @@ -37,10 +43,16 @@ install: virtualenv --python="$PYENV_ROOT/versions/pypy-$PYPY_VERSION/bin/python" "$HOME/virtualenvs/pypy-$PYPY_VERSION" source "$HOME/virtualenvs/pypy-$PYPY_VERSION/bin/activate" fi - - pip install -r tests-requirements.txt - - python setup.py develop + - wget https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py + - python get-poetry.py --preview + - poetry install -v + - poetry build -v + - | + if [ "$PENDULUM_EXTENSIONS" == "1" ]; then + find dist/ -iname pendulum*.whl -exec unzip -o {} 'pendulum/*' -d . \; + fi -script: py.test --cov=pendulum --cov-config=.coveragerc tests/ +script: poetry run pytest --cov=pendulum --cov-config=.coveragerc tests/ -W ignore after_success: - codecov diff --git a/CHANGELOG.md b/CHANGELOG.md index cfbc46d5..3716d138 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,72 @@ # Change Log -## [Unreleased] +## [1.5.1] - 2018-04-24 + +### Fixed + +- Fixed `set()` not acception the `tz` keyword argument. +- Fixed `datetime()` not setting the timezone to `UTC` by default. + + +## [1.5.0] - 2018-04-16 + +### Added + +- Added the `datetime()` helper. +- Added the `set()` method to set properties. +- Added the `is_utc()` and `is_local()` methods. + +### Deprecated + +- `year_()`, `month_()`, `day_()`, `hour_()`, `minute_()`, `second_()`, `microsecond_()` are now deprecated. +- `timezone_()` and `tz_()` are now deprecated. +- `timestamp_()` is now deprecated. +- `with_date_time()`, `with_time_from_string()` and `with_timestamp()` are now deprecated. +- `between()` is now deprecated. +- `min_()`, `max_()`, `minimum()`, `maximum()` are now deprecated. +- `is_today()`, `is_yesterday()`, `is_tomorrow()` and `is_same_day()` are now depecreated. +- `is_sunday()` -> `is_saturday()` are now deprecated. +- The `utc` and `local` properties are now deprecated. Use `is_utc()` and `is_local()` instead. + + +## [1.4.4] - 2018-03-21 + +### Fixed + +- Fixed extension building script. + + +## [1.4.3] - 2018-03-20 + +### Fixed + +- Fixed an error when adding intervals to a Pendulum instance across DST transition. +- Fixed an error when subtracting two pendulum instances in the same timezone. + + +## [1.4.2] - 2018-02-22 + +### Fixed + +- Fixed an offset error when subtracting datetimes in the same timezone. +- Fixed wrong value returned by `tzname()` for the UTC timezone. +- Fixed `deepcopy()` raising an error when using `UTC`. + + +## [1.4.1] - 2018-02-05 + +### Fixed + +- Fixed an error when comparing a Period to a timedelta in PyPy. +- Fixed an offset error for datetimes between the before last and last transition. +- Fixed unpickling with undefined / empty timezone name. (Thanks to [Delgan](https://github.com/Delgan)) + + +## [1.4.0] - 2018-01-22 ### Changed -- `format()`, `diff_for_humans()`, `in_words()` and `to_xxx_string()` methods now return unicode strings. +- `format()`, `diff_for_humans()`, `in_words()` and `to_xxx_string()` methods now return unicode strings for Python 2.7. - Improved performance of `now()` and `utcnow()`. (Thanks to [pganssle](https://github.com/pganssle)) ### Fixed @@ -440,7 +502,14 @@ Initial release -[Unreleased]: https://github.com/sdispater/pendulum/compare/1.3.2...master +[Unreleased]: https://github.com/sdispater/pendulum/compare/1.5.1...1.x +[1.5.1]: https://github.com/sdispater/pendulum/releases/tag/1.5.1 +[1.5.0]: https://github.com/sdispater/pendulum/releases/tag/1.5.0 +[1.4.4]: https://github.com/sdispater/pendulum/releases/tag/1.4.4 +[1.4.3]: https://github.com/sdispater/pendulum/releases/tag/1.4.3 +[1.4.2]: https://github.com/sdispater/pendulum/releases/tag/1.4.2 +[1.4.1]: https://github.com/sdispater/pendulum/releases/tag/1.4.1 +[1.4.0]: https://github.com/sdispater/pendulum/releases/tag/1.4.0 [1.3.2]: https://github.com/sdispater/pendulum/releases/tag/1.3.2 [1.3.1]: https://github.com/sdispater/pendulum/releases/tag/1.3.1 [1.3.0]: https://github.com/sdispater/pendulum/releases/tag/1.3.0 diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 4e978a51..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1,2 +0,0 @@ -include README.rst LICENSE -exclude test* diff --git a/Makefile b/Makefile index 35b9c94b..518d1bf8 100644 --- a/Makefile +++ b/Makefile @@ -23,16 +23,16 @@ setup: setup-python test: @py.test --cov=pendulum --cov-config .coveragerc tests/ -sq -release: tar wheels_x64 cp_wheels_x64 wheels_i686 cp_wheels_i686 wheel +release: wheels_x64 cp_wheels_x64 wheels_i686 cp_wheels_i686 wheel publish: - @python -m twine upload dist/pendulum-$(PENDULUM_RELEASE)* + @poetry publish --no-build tar: python setup.py sdist --formats=gztar wheel: - @pip wheel --no-index --no-deps --wheel-dir dist dist/pendulum-$(PENDULUM_RELEASE).tar.gz + @poetry build -v wheels_x64: clean_wheels build_wheels_x64 @@ -40,11 +40,13 @@ wheels_i686: clean_wheels build_wheels_i686 build_wheels_x64: rm -rf wheelhouse/ + mkdir wheelhouse docker pull quay.io/pypa/manylinux1_x86_64 docker run --rm -v `pwd`:/io quay.io/pypa/manylinux1_x86_64 /io/build-wheels.sh build_wheels_i686: rm -rf wheelhouse/ + mkdir wheelhouse docker pull quay.io/pypa/manylinux1_i686 docker run --rm -v `pwd`:/io quay.io/pypa/manylinux1_i686 /io/build-wheels.sh @@ -52,10 +54,10 @@ clean_wheels: rm -rf wheelhouse/ cp_wheels_x64: - cp wheelhouse/*manylinux1_x86_64.whl dist/ + mv wheelhouse/*manylinux1_x86_64.whl dist/ cp_wheels_i686: - cp wheelhouse/*manylinux1_i686.whl dist/ + mv wheelhouse/*manylinux1_i686.whl dist/ upload_wheels_x64: @for f in wheelhouse/*manylinux1_x86_64.whl ; do \ diff --git a/appveyor.yml b/appveyor.yml index a2bf497a..739197f2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,24 +4,33 @@ environment: matrix: - PYTHON: "C:/Python27" - PYTHON: "C:/Python27-x64" - - PYTHON: "C:/Python34" - - PYTHON: "C:/Python34-x64" - PYTHON: "C:/Python35" - PYTHON: "C:/Python35-x64" + - PYTHON: "C:/Python36" + - PYTHON: "C:/Python36-x64" + +cache: + - '%LocalAppData%\pip\Cache' + - '%LocalAppData%\pypoetry\Cache' + install: - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" # Upgrade to the latest version of pip to avoid it displaying warnings # about it being out of date. - - "pip install --disable-pip-version-check --user --upgrade pip" + - "python -m pip install --disable-pip-version-check --user --upgrade pip" + + # Downloading and installing poetry + - curl -fsS -o get-poetry.py https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py + - python get-poetry.py --preview # Install dependencies - - "%CMD_IN_ENV% pip install -r tests-requirements.txt" - - "%CMD_IN_ENV% pip install codecov" + - python -m pip install codecov + - poetry install -v test_script: - - "py.test --cov=pendulum --cov-config=.coveragerc tests/" + - "poetry run pytest --cov=pendulum --cov-config=.coveragerc tests/ -W ignore" after_test: - "codecov" diff --git a/build-wheels.sh b/build-wheels.sh index cfc83949..63139ece 100755 --- a/build-wheels.sh +++ b/build-wheels.sh @@ -1,25 +1,38 @@ #!/bin/bash -PYTHON_VERSIONS="cp27-cp27m cp35-cp35m cp36-cp36m" +PYTHON_VERSIONS="cp27-cp27m cp34-cp34m cp35-cp35m cp36-cp36m" + +POETRY_PYTHON="cp36-cp36m" +POETRY_VENV="/opt/python/poetry" +echo "Create Poetry's virtualenv" +/opt/python/${POETRY_PYTHON}/bin/pip install virtualenv +/opt/python/${POETRY_PYTHON}/bin/virtualenv --python /opt/python/${POETRY_PYTHON}/bin/python ${POETRY_VENV} +${POETRY_VENV}/bin/pip install poetry --pre + +RELEASE=$(sed -n "s/VERSION = '\(.*\)'/\1/p" /io/pendulum/version.py) echo "Compile wheels" for PYTHON in ${PYTHON_VERSIONS}; do cd /io - /opt/python/${PYTHON}/bin/pip install -r wheels-requirements.txt - /opt/python/${PYTHON}/bin/pip install -r tests-requirements.txt - /opt/python/${PYTHON}/bin/python setup.py sdist --dist-dir wheelhouse --formats=gztar - /opt/python/${PYTHON}/bin/pip wheel --no-index --no-deps --wheel-dir wheelhouse wheelhouse/*.tar.gz + /opt/python/${POETRY_PYTHON}/bin/virtualenv --python /opt/python/${PYTHON}/bin/python /opt/python/venv-${PYTHON} + . /opt/python/venv-${PYTHON}/bin/activate + ${POETRY_VENV}/bin/poetry install -v + ${POETRY_VENV}/bin/poetry build -v + mv dist/*-${RELEASE}-*-linux_*.whl wheelhouse/ + deactivate cd - done echo "Bundle external shared libraries into the wheels" -for whl in /io/wheelhouse/*.whl; do - auditwheel repair $whl -w /io/wheelhouse/ +for whl in /io/wheelhouse/pendulum-${RELEASE}-*-linux_*.whl; do + auditwheel repair "$whl" -w /io/wheelhouse/ done echo "Install packages and test" for PYTHON in ${PYTHON_VERSIONS}; do - /opt/python/${PYTHON}/bin/pip install pendulum --no-index -f /io/wheelhouse + . /opt/python/venv-${PYTHON}/bin/activate + pip install pendulum==${RELEASE} --no-index --find-links /io/wheelhouse find ./io/tests | grep -E "(__pycache__|\.pyc$)" | xargs rm -rf - /opt/python/${PYTHON}/bin/py.test /io/tests + pytest /io/tests -W ignore find ./io/tests | grep -E "(__pycache__|\.pyc$)" | xargs rm -rf + deactivate done diff --git a/build.py b/build.py new file mode 100644 index 00000000..852a7acc --- /dev/null +++ b/build.py @@ -0,0 +1,60 @@ +import os +import sys + + +from distutils.core import Extension + +from distutils.errors import (CCompilerError, DistutilsExecError, + DistutilsPlatformError) +from distutils.command.build_ext import build_ext + + +# C Extensions +with_extensions = os.getenv('PENDULUM_EXTENSIONS', None) + +if with_extensions == '1' or with_extensions is None: + with_extensions = True + +if with_extensions == '0' or hasattr(sys, 'pypy_version_info'): + with_extensions = False + +extensions = [] +if with_extensions: + extensions = [ + Extension('pendulum._extensions._helpers', + ['pendulum/_extensions/_helpers.c']), + ] + + +class BuildFailed(Exception): + + pass + + +class ExtBuilder(build_ext): + # This class allows C extension building to fail. + + def run(self): + try: + build_ext.run(self) + except (DistutilsPlatformError, FileNotFoundError): + pass + + def build_extension(self, ext): + try: + build_ext.build_extension(self, ext) + except (CCompilerError, DistutilsExecError, + DistutilsPlatformError, ValueError): + pass + + +def build(setup_kwargs): + """ + This function is mandatory in order to build the extensions. + """ + setup_kwargs.update({ + 'ext_modules': extensions, + 'cmdclass': { + 'build_ext': ExtBuilder + } + }) diff --git a/docs/_docs/addition_subtraction.rst b/docs/_docs/addition_subtraction.rst index df32ddad..37b3f6da 100644 --- a/docs/_docs/addition_subtraction.rst +++ b/docs/_docs/addition_subtraction.rst @@ -9,7 +9,7 @@ Each method returns a new ``Pendulum`` instance. import pendulum - dt = pendulum.create(2012, 1, 31, 0) + dt = pendulum.datetime(2012, 1, 31, 0) dt.to_datetime_string() '2012-01-31 00:00:00' diff --git a/docs/_docs/attributes_properties.rst b/docs/_docs/attributes_properties.rst index 0fb48cfd..b030d669 100644 --- a/docs/_docs/attributes_properties.rst +++ b/docs/_docs/attributes_properties.rst @@ -41,7 +41,7 @@ Pendulum gives access to more attributes and properties than the default ``datet dt.int_timestamp 1346887571 - pendulum.create(1975, 5, 21).age + pendulum.datetime(1975, 5, 21).age 41 # calculated vs now in the same tz dt.quarter 3 @@ -59,9 +59,9 @@ Pendulum gives access to more attributes and properties than the default ``datet 9.5 # Indicates if day light savings time is on - pendulum.create(2012, 1, 1, tz='America/Toronto').is_dst + pendulum.datetime(2012, 1, 1, tz='America/Toronto').is_dst False - pendulum.create(2012, 9, 1, tz='America/Toronto').is_dst + pendulum.datetime(2012, 9, 1, tz='America/Toronto').is_dst True # Indicates if the instance is in the same timezone as the local timezone diff --git a/docs/_docs/comparison.rst b/docs/_docs/comparison.rst index 10369bbb..7edfb696 100644 --- a/docs/_docs/comparison.rst +++ b/docs/_docs/comparison.rst @@ -8,8 +8,8 @@ Remember that the comparison is done in the UTC timezone so things aren't always import pendulum - first = pendulum.create(2012, 9, 5, 23, 26, 11, 0, tz='America/Toronto') - second = pendulum.create(2012, 9, 5, 20, 26, 11, 0, tz='America/Vancouver') + first = pendulum.datetime(2012, 9, 5, 23, 26, 11, tz='America/Toronto') + second = pendulum.datetime(2012, 9, 5, 20, 26, 11, tz='America/Vancouver') first.to_datetime_string() '2012-09-05 23:26:11' @@ -58,14 +58,14 @@ The default is ``True`` which determines if its between or equal to the boundari import pendulum - first = pendulum.create(2012, 9, 5, 1) - second = pendulum.create(2012, 9, 5, 5) + first = pendulum.datetime(2012, 9, 5, 1) + second = pendulum.datetime(2012, 9, 5, 5) - pendulum.create(2012, 9, 5, 3).between(first, second) + pendulum.datetime(2012, 9, 5, 3).between(first, second) True - pendulum.create(2012, 9, 5, 3).between(first, second) + pendulum.datetime(2012, 9, 5, 3).between(first, second) True - pendulum.create(2012, 9, 5, 5).between(first, second, False) + pendulum.datetime(2012, 9, 5, 5).between(first, second, False) False There are also the ``min_()`` and ``max_()`` methods. @@ -75,8 +75,8 @@ As usual the default parameter is ``now`` if ``None`` is specified. import pendulum - dt1 = pendulum.create(2012, 1, 1, 0, 0, 0, 0) - dt2 = pendulum.create(2014, 1, 30, 0, 0, 0, 0) + dt1 = pendulum.datetime(2012, 1, 1, 0, 0, 0, 0) + dt2 = pendulum.datetime(2014, 1, 30, 0, 0, 0, 0) print(dt1.min_(dt2)) '2012-01-01T00:00:00+00:00' @@ -102,17 +102,11 @@ the ``now()`` is created in the same timezone as the instance. import pendulum - dt = Pendulum.now() + dt = pendulum.now() - dt.is_weekday() - dt.is_weekend() - dt.is_yesterday() - dt.is_today() - dt.is_tomorrow() dt.is_future() dt.is_past() dt.is_leap_year() - dt.is_same_day(Pendulum.now()) born = pendulum.create(1987, 4, 23) not_birthday = pendulum.create(2014, 9, 26) diff --git a/docs/_docs/difference.rst b/docs/_docs/difference.rst index 53c85706..81c6bdc8 100644 --- a/docs/_docs/difference.rst +++ b/docs/_docs/difference.rst @@ -17,8 +17,8 @@ This will default to ``True``, return the absolute value. The comparisons are do import pendulum - dt_ottawa = pendulum.create(2000, 1, 1, tz='America/Toronto') - dt_vancouver = pendulum.create(2000, 1, 1, tz='America/Vancouver') + dt_ottawa = pendulum.datetime(2000, 1, 1, tz='America/Toronto') + dt_vancouver = pendulum.datetime(2000, 1, 1, tz='America/Vancouver') dt_ottawa.diff(dt_vancouver).in_hours() 3 @@ -27,19 +27,19 @@ This will default to ``True``, return the absolute value. The comparisons are do dt_vancouver.diff(dt_ottawa, False).in_hours() -3 - dt = pendulum.create(2012, 1, 31, 0) + dt = pendulum.datetime(2012, 1, 31, 0) dt.diff(dt.add(months=1)).in_days() 29 dt.diff(dt.subtract(months=1), False).in_days() -31 - dt = pendulum.create(2012, 4, 30, 0) + dt = pendulum.datetime(2012, 4, 30, 0) dt.diff(dt.add(months=1)).in_days() 30 dt.diff(dt.add(weeks=1)).in_days() 7 - dt = pendulum.create(2012, 1, 1, 0) + dt = pendulum.datetime(2012, 1, 1, 0) dt.diff(dt.add(seconds=59)).in_minutes() 0 dt.diff(dt.add(seconds=60)).in_minutes() @@ -86,10 +86,10 @@ You may also pass ``True`` as a 2nd parameter to remove the modifiers `ago`, `fr pendulum.now().subtract(days=1).diff_for_humans() '5 days ago' - pendulum.now().diff_for_humans(Pendulum.now().subtract(years=1)) + pendulum.now().diff_for_humans(pendulum.now().subtract(years=1)) '1 year after' - dt = pendulum.create(2011, 8, 1) + dt = pendulum.datetime(2011, 8, 1) dt.diff_for_humans(dt.add(months=1)) '1 month before' dt.diff_for_humans(dt.subtract(months=1)) diff --git a/docs/_docs/fluent_helpers.rst b/docs/_docs/fluent_helpers.rst index 7f42a9f4..dd96d200 100644 --- a/docs/_docs/fluent_helpers.rst +++ b/docs/_docs/fluent_helpers.rst @@ -14,21 +14,21 @@ setting the timestamp will not set the corresponding timezone to UTC. dt = pendulum.now() - dt = dt.year_(1975).month_(5).day_(21).to_datetime_string() + dt = dt.set(year=1975, month=5, day=21).to_datetime_string() '1975-05-21 13:45:18' - dt.hour_(22).minute_(32).second_(5).to_datetime_string() + dt.set(hour=22, minute=32, second=5).to_datetime_string() '2016-11-16 22:32:05' dt.on(1975, 5, 21).at(22, 32, 5).to_datetime_string() '1975-05-21 22:32:05' - dt.timestamp_(169957925).timezone_('Europe/London') + dt.set(tz='Europe/London') - dt.tz_('America/Toronto').in_timezone('America/Vancouver') + dt.set(tz='America/Toronto').in_timezone('America/Vancouver') .. note:: - ``timezone_()`` and ``tz_()`` just modify the timezone information without + ``set(tz=...)`` just modify the timezone information without making any conversion while ``in_timezone()`` converts the time in the appropriate timezone. diff --git a/docs/_docs/instantiation.rst b/docs/_docs/instantiation.rst index 36af5bc2..cf1e84a1 100644 --- a/docs/_docs/instantiation.rst +++ b/docs/_docs/instantiation.rst @@ -2,22 +2,19 @@ Instantiation ============= There are several different methods available to create a new instance of Pendulum. -First there is a constructor. It accepts the same parameters as the standard class. +First there is a the `datetime()` helper. .. code-block:: python - from pendulum import Pendulum + import pendulum - dt = Pendulum(2015, 2, 5, tzinfo='America/Vancouver') + dt = pendulum.datetime(2015, 2, 5, tz='America/Vancouver') isinstance(dt, datetime) True - dt = Pendulum.now(-5) - -You'll notice above that the timezone (2nd) parameter was passed as a string and an integer -rather than a ``tzinfo`` instance. All timezone parameters have been augmented -so you can pass a ``tzinfo`` instance, string or integer offset to GMT -and the timezone will be created for you. +`datetime()` sets the time to `00:00:00` if it's not specified, +and the timezone (the `tz` keyword argument) to `UTC`. +It otherwise can be a `Timezone` instance or simply a string timezone value. .. note:: @@ -44,7 +41,7 @@ and the timezone will be created for you. 'US/Pacific-New', 'US/Samoa') -This is again shown in the next example which also introduces the ``now()`` function. +There is also the ``now()`` helper. .. code-block:: python @@ -87,7 +84,7 @@ besides behaving as expected, all accept a timezone parameter and each has their print(yesterday) '2016-06-27T00:00:00-05:00' -The next helper is ``create()`` which allows you to provide +The next helper is ``datetime()`` which allows you to provide as many or as few arguments as you want and will provide default values for all others. .. code-block:: python @@ -129,7 +126,7 @@ The difference being the addition the ``tz`` argument that can be a ``tzinfo`` i Note that it will be the only one supported in the next major version. -The final ``create`` function is for working with unix timestamps. +The final helper is for working with unix timestamps. ``from_timestamp()`` will create a ``Pendulum`` instance equal to the given timestamp and will set the timezone as well or default it to ``UTC``. @@ -141,21 +138,6 @@ and will set the timezone as well or default it to ``UTC``. pendulum.from_timestamp(-1, 'Europe/London').to_datetime_string() '1970-01-01 00:59:59' - # Using the standard fromtimestamp is also possible - pendulum.fromtimestamp(-1).to_datetime_string() - '1969-12-31 23:59:59' - -You can also create a ``copy()`` of an existing ``Pendulum`` instance. -As expected the date, time and timezone values are all copied to the new instance. - -.. code-block:: python - - dt = pendulum.now() - print(dt.diff(dt.copy().add(years=1)).in_years()) - 1 - - # dt was unchanged and still holds the value of pendulum.now() - Finally, if you find yourself inheriting a ``datetime`` instance, you can create a ``Pendulum`` instance via the ``instance()`` function. diff --git a/docs/_docs/interval.rst b/docs/_docs/interval.rst index 54fbbe9f..7b31aa02 100644 --- a/docs/_docs/interval.rst +++ b/docs/_docs/interval.rst @@ -12,6 +12,8 @@ It has many improvements over the base class. .. code-block:: python + import pendulum + d1 = datetime(2012, 1, 1, 1, 2, 3, tzinfo=pytz.UTC) d2 = datetime(2011, 12, 31, 22, 2, 3, tzinfo=pytz.UTC) delta = d2 - d1 @@ -20,8 +22,8 @@ It has many improvements over the base class. delta.seconds 75600 - d1 = Pendulum(2012, 1, 1, 1, 2, 3) - d2 = Pendulum(2011, 12, 31, 22, 2, 3) + d1 = pendulum.datetime(2012, 1, 1, 1, 2, 3) + d2 = pendulum.datetime(2011, 12, 31, 22, 2, 3) delta = d2 - d1 delta.days 0 @@ -37,7 +39,6 @@ You can create an instance in the following ways: import pendulum - it = pendulum.Interval(days=1177, seconds=7284, microseconds=1234) it = pendulum.interval(days=1177, seconds=7284, microseconds=1234) # You can use an existing timedelta instance @@ -63,7 +64,7 @@ The ``Interval`` class brings more properties than the default ``days``, ``secon 1117 # If you want the remaining days not included in full weeks - it.remaning_days + it.remaining_days 1 # The remaining number in each unit diff --git a/docs/_docs/introduction.rst b/docs/_docs/introduction.rst index 3002c438..3deb2a34 100644 --- a/docs/_docs/introduction.rst +++ b/docs/_docs/introduction.rst @@ -16,8 +16,8 @@ For example all comparisons are done in UTC or in the timezone of the datetime b import pendulum - dt_toronto = pendulum.create(2012, 1, 1, tz='America/Toronto') - dt_vancouver = pendulum.create(2012, 1, 1, tz='America/Vancouver') + dt_toronto = pendulum.datetime(2012, 1, 1, tz='America/Toronto') + dt_vancouver = pendulum.datetime(2012, 1, 1, tz='America/Vancouver') print(dt_vancouver.diff(dt_toronto).in_hours()) 3 diff --git a/docs/_docs/localization.rst b/docs/_docs/localization.rst index 20fc434e..a87ade9d 100644 --- a/docs/_docs/localization.rst +++ b/docs/_docs/localization.rst @@ -5,9 +5,9 @@ Localization occurs when using the ``format()`` method which accepts a ``locale` .. code-block:: python - from pendulum import Pendulum + import pendulum - dt = Pendulum(1975, 5, 21) + dt = pendulum.datetime(1975, 5, 21) dt.format('%A %d %B %Y', locale='de') 'Mittwoch 21 Mai 1975' @@ -22,9 +22,10 @@ Localization occurs when using the ``format()`` method which accepts a ``locale` .. code-block:: python import locale - from pendulum import Pendulum - dt = Pendulum(1975, 5, 21) + import pendulum + + dt = pendulum.datetime(1975, 5, 21) locale.setlocale(locale.setlocale(locale.LC_ALL, 'de_DE.UTF-8')) dt.format('%A %d %B %Y') diff --git a/docs/_docs/modifiers.rst b/docs/_docs/modifiers.rst index 87df0388..69f55628 100644 --- a/docs/_docs/modifiers.rst +++ b/docs/_docs/modifiers.rst @@ -12,90 +12,90 @@ It moves your instance to the middle date between itself and the provided Pendul import pendulum - dt = pendulum.create(2012, 1, 31, 12, 0, 0) + dt = pendulum.datetime(2012, 1, 31, 12, 0, 0) dt.start_of('day') '2012-01-31 00:00:00' - dt = pendulum.create(2012, 1, 31, 12, 0, 0) + dt = pendulum.datetime(2012, 1, 31, 12, 0, 0) dt.end_of('day') '2012-01-31 23:59:59' - dt = pendulum.create(2012, 1, 31, 12, 0, 0) + dt = pendulum.datetime(2012, 1, 31, 12, 0, 0) dt.start_of('month') '2012-01-01 00:00:00' - dt = pendulum.create(2012, 1, 31, 12, 0, 0) + dt = pendulum.datetime(2012, 1, 31, 12, 0, 0) dt.end_of('month') '2012-01-31 23:59:59' - dt = pendulum.create(2012, 1, 31, 12, 0, 0) + dt = pendulum.datetime(2012, 1, 31, 12, 0, 0) dt.start_of('year') '2012-01-01 00:00:00' - dt = pendulum.create(2012, 1, 31, 12, 0, 0) + dt = pendulum.datetime(2012, 1, 31, 12, 0, 0) dt.end_of('year') '2012-01-31 23:59:59' - dt = pendulum.create(2012, 1, 31, 12, 0, 0) + dt = pendulum.datetime(2012, 1, 31, 12, 0, 0) dt.start_of('decade') '2010-01-01 00:00:00' - dt = pendulum.create(2012, 1, 31, 12, 0, 0) + dt = pendulum.datetime(2012, 1, 31, 12, 0, 0) dt.end_of('decade') '2019-01-31 23:59:59' - dt = pendulum.create(2012, 1, 31, 12, 0, 0) + dt = pendulum.datetime(2012, 1, 31, 12, 0, 0) dt.start_of('century') '2000-01-01 00:00:00' - dt = pendulum.create(2012, 1, 31, 12, 0, 0) + dt = pendulum.datetime(2012, 1, 31, 12, 0, 0) dt.end_of('century') '2099-12-31 23:59:59' - dt = pendulum.create(2012, 1, 31, 12, 0, 0) + dt = pendulum.datetime(2012, 1, 31, 12, 0, 0) dt.start_of('week') '2012-01-30 00:00:00' dt.day_of_week == pendulum.MONDAY True # ISO8601 week starts on Monday - dt = pendulum.create(2012, 1, 31, 12, 0, 0) + dt = pendulum.datetime(2012, 1, 31, 12, 0, 0) dt.end_of('week') '2012-02-05 23:59:59' dt.day_of_week == pendulum.SUNDAY True # ISO8601 week ends on SUNDAY - dt = pendulum.create(2012, 1, 31, 12, 0, 0) + dt = pendulum.datetime(2012, 1, 31, 12, 0, 0) dt.end_of('week') '2012-02-05 23:59:59' dt.day_of_week == pendulum.SUNDAY True # ISO8601 week ends on SUNDAY - dt = pendulum.create(2012, 1, 31, 12, 0, 0) + dt = pendulum.datetime(2012, 1, 31, 12, 0, 0) dt.next(pendulum.WEDNESDAY) '2012-02-01 00:00:00' dt.day_of_week == pendulum.WEDNESDAY True - dt = pendulum.create(2012, 1, 1, 12, 0, 0) + dt = pendulum.datetime(2012, 1, 1, 12, 0, 0) dt.next() '2012-01-08 00:00:00' dt.next(keep_time=True) '2012-01-08T12:00:00+00:00' - dt = pendulum.create(2012, 1, 31, 12, 0, 0) + dt = pendulum.datetime(2012, 1, 31, 12, 0, 0) dt.previous(pendulum.WEDNESDAY) '2012-01-25 00:00:00' dt.day_of_week == pendulum.WEDNESDAY True - dt = pendulum.create(2012, 1, 1, 12, 0, 0) + dt = pendulum.datetime(2012, 1, 1, 12, 0, 0) dt.previous() '2011-12-25 00:00:00' dt.previous(keep_time=True) '2011-12-25 12:00:00' - start = pendulum.create(2014, 1, 1, 0, 0, 0) - end = pendulum.create(2014, 1, 30, 0, 0, 0) + start = pendulum.datetime(2014, 1, 1, 0, 0, 0) + end = pendulum.datetime(2014, 1, 30, 0, 0, 0) start.average(end) '2014-01-15 12:00:00' diff --git a/docs/_docs/period.rst b/docs/_docs/period.rst index bbe60fb8..63c47779 100644 --- a/docs/_docs/period.rst +++ b/docs/_docs/period.rst @@ -7,22 +7,17 @@ instances that generated it, so that it can give access to more methods and prop .. code-block:: python - from pendulum import Pendulum + import pendulum - start = Pendulum(2000, 1, 1) - end = Pendulum(2000, 1, 31) + start = pendulum.datetime(2000, 1, 1) + end = pendulum.datetime(2000, 1, 31) period = end - start - period.in_weekdays() - 21 - - period.in_weekend_days() - 10 - # You also have access to the years and months + # You have access to the years and months # properties and there related methods - start = Pendulum(2000, 11, 20) - end = Pendulum(2016, 11, 5) + start = pendulum.datetime(2000, 11, 20) + end = pendulum.datetime(2016, 11, 5) period = end - start @@ -53,7 +48,7 @@ transitions that might have occurred and adjust accordingly. Let's take an examp import pendulum - start = pendulum.create(2017, 3, 7, tz='America/Toronto') + start = pendulum.datetime(2017, 3, 7, tz='America/Toronto') end = start.add(days=6) period = end - start @@ -80,9 +75,11 @@ transitions that might have occurred and adjust accordingly. Let's take an examp .. code-block:: python - dt1 = Pendulum(2016, 8, 7, 12, 34, 56) + import pendulum + + dt1 = pendulum.datetime(2016, 8, 7, 12, 34, 56) dt2 = dt1.add(days=6, seconds=34) - period = Period(dt1, dt2) + period = pendulum.period(dt1, dt2) period * 2 # @@ -96,22 +93,19 @@ You can create an instance in the following ways: import pendulum - start = pendulum.Pendulum(2000, 1, 1) - end = pendulum.Pendulum(2000, 1, 31) + start = pendulum.datetime(2000, 1, 1) + end = pendulum.datetime(2000, 1, 31) - period = pendulum.Period(start, end) period = pendulum.period(start, end) + period = end - start You can also make an inverted period: .. code-block:: python period = pendulum.period(end, start) - period.in_weekdays() - -21 - - period.in_weekend_days() - -10 + period.days + -30 If you have inverted dates but want to make sure that the period is positive, you set the ``absolute`` keyword argument to ``True``: @@ -119,11 +113,8 @@ you set the ``absolute`` keyword argument to ``True``: .. code-block:: python period = pendulum.period(end, start, absolute=True) - period.in_weekdays() - 21 - - period.in_weekend_days() - 10 + period.days + 30 Range ----- @@ -134,8 +125,8 @@ If you want to iterate over a period, you can use the ``range()`` method: import pendulum - start = pendulum.Pendulum(2000, 1, 1) - end = pendulum.Pendulum(2000, 1, 10) + start = pendulum.datetime(2000, 1, 1) + end = pendulum.datetime(2000, 1, 10) period = pendulum.period(start, end) @@ -186,7 +177,7 @@ You can check if a ``Pendulum`` instance is inside a period using the ``in`` key .. code-block:: python - dt = Pendulum(2000, 1, 4) + dt = pendulum.datetime(2000, 1, 4) dt in period True @@ -202,12 +193,12 @@ using the ``intersect()`` method. import pendulum - monday = pendulum.create(2016, 9, 12) + monday = pendulum.datetime(2016, 9, 12) wednesday = monday.next(pendulum.WEDNESDAY) friday = monday.next(pendulum.FRIDAY) saturday = monday.next(pendulum.SATURDAY) - period = pendulum.period(monday, friday) + period = pendulum.datetime(monday, friday) period.intersect(pendulum.period(wednesday, saturday)) # 2016-09-16T00:00:00+00:00]> @@ -219,7 +210,7 @@ You can also pass multiple period to ``intersect()``. import pendulum - monday = pendulum.create(2016, 9, 12) + monday = pendulum.datetime(2016, 9, 12) wednesday = monday.next(pendulum.WEDNESDAY) thursday = monday.next(pendulum.THURSDAY) friday = monday.next(pendulum.FRIDAY) diff --git a/docs/_docs/string_formatting.rst b/docs/_docs/string_formatting.rst index f30e3ba8..496ed1d3 100644 --- a/docs/_docs/string_formatting.rst +++ b/docs/_docs/string_formatting.rst @@ -9,9 +9,9 @@ The default string representation is the same as the one returned by the ``isofo .. code-block:: python - from pendulum import Pendulum + import pendulum - dt = Pendulum(1975, 12, 25, 14, 15, 16) + dt = pendulum.datetime(1975, 12, 25, 14, 15, 16) print(dt) '1975-12-25T14:15:16+00:00' @@ -133,7 +133,7 @@ or globally by using ``pendulum.set_formatter()``. import pendulum - dt = pendulum.Pendulum(1975, 12, 25, 14, 15, 16) + dt = pendulum.datetime(1975, 12, 25, 14, 15, 16) dt.format('YYYY-MM-DD HH:mm:ss', formatter='alternative') '1975-12-25 14:15:16' diff --git a/docs/_docs/testing.rst b/docs/_docs/testing.rst index fb435b49..5e7323ca 100644 --- a/docs/_docs/testing.rst +++ b/docs/_docs/testing.rst @@ -13,7 +13,7 @@ The provided instance will be returned specifically under the following conditio import pendulum # Create testing datetime - known = pendulum.create(2001, 5, 21, 12) + known = pendulum.datetime(2001, 5, 21, 12) # Set the mock pendulum.set_test_now(known) @@ -50,7 +50,7 @@ you can use the provided ``test()`` contextmanager. import pendulum - known = pendulum.create(2001, 5, 21, 12) + known = pendulum.datetime(2001, 5, 21, 12) with pendulum.test(known): print(pendulum.now()) diff --git a/docs/_docs/timezones.rst b/docs/_docs/timezones.rst index b17d19dc..9e048727 100644 --- a/docs/_docs/timezones.rst +++ b/docs/_docs/timezones.rst @@ -21,12 +21,12 @@ given timezone to properly handle any transition that might have occurred. import pendulum - pendulum.create(2013, 3, 31, 2, 30, 0, 0, 'Europe/Paris') + pendulum.datetime(2013, 3, 31, 2, 30, 'Europe/Paris') # 2:30 for the 31th of March 2013 does not exist # so pendulum will return the actual time which is 3:30+02:00 '2013-03-31T03:30:00+02:00' - pendulum.create(2013, 10, 27, 2, 30, 0, 0, 'Europe/Paris') + pendulum.datetime(2013, 10, 27, 2, 30, 'Europe/Paris') # Here, 2:30 exists twice in the day so pendulum will # assume that the transition already occurred '2013-10-27T02:30:00+01:00' @@ -42,16 +42,16 @@ given timezone to properly handle any transition that might have occurred. pendulum.set_transition_rule(pendulum.PRE_TRANSITION) - pendulum.create(2013, 3, 31, 2, 30, 0, 0, 'Europe/Paris') + pendulum.datetime(2013, 3, 31, 2, 30, 'Europe/Paris') '2013-03-31T01:30:00+01:00' - pendulum.create(2013, 10, 27, 2, 30, 0, 0, 'Europe/Paris') + pendulum.datetime(2013, 10, 27, 2, 30, 'Europe/Paris') '2013-10-27T02:30:00+02:00' pendulum.set_transition_rule(pendulum.TRANSITION_ERROR) - pendulum.create(2013, 3, 31, 2, 30, 0, 0, 'Europe/Paris') + pendulum.datetime(2013, 3, 31, 2, 30, 'Europe/Paris') # NonExistingTime: The datetime 2013-03-31 02:30:00 does not exist - pendulum.create(2013, 10, 27, 2, 30, 0, 0, 'Europe/Paris') + pendulum.datetime(2013, 10, 27, 2, 30, 'Europe/Paris') # AmbiguousTime: The datetime 2013-10-27 02:30:00 is ambiguous. Note that it only affects instances at creation time. Shifting time around @@ -69,23 +69,23 @@ given timezone to properly handle any transition that might have occurred. from pendulum import Pendulum - dt = Pendulum(2013, 3, 31, 2, 30, 0, 0, 'Europe/Paris') + dt = Pendulum(2013, 3, 31, 2, 30, 'Europe/Paris') dt.isoformat() '2013-03-31T03:30:00+02:00' - dt = Pendulum(2013, 3, 31, 2, 30, 0, 0, 'Europe/Paris', fold=0) + dt = Pendulum(2013, 3, 31, 2, 30, 'Europe/Paris', fold=0) dt.isoformat() '2013-03-31T01:30:00+01:00' - dt = Pendulum(2013, 3, 31, 2, 30, 0, 0, 'Europe/Paris', fold=1) + dt = Pendulum(2013, 3, 31, 2, 30, 'Europe/Paris', fold=1) dt.isoformat() '2013-03-31T03:30:00+02:00' - dt = Pendulum(2013, 10, 27, 2, 30, 0, 0, 'Europe/Paris', fold=0) + dt = Pendulum(2013, 10, 27, 2, 30, 'Europe/Paris', fold=0) dt.isoformat() '2013-10-27T02:30:00+02:00' - dt = Pendulum(2013, 10, 27, 2, 30, 0, 0, 'Europe/Paris', fold=1) + dt = Pendulum(2013, 10, 27, 2, 30, 'Europe/Paris', fold=1) dt.isoformat() '2013-10-27T02:30:00+01:00' @@ -101,17 +101,17 @@ adopt the proper behavior and apply the transition accordingly. import pendulum - dt = pendulum.create(2013, 3, 31, 1, 59, 59, 999999, 'Europe/Paris') + dt = pendulum.datetime(2013, 3, 31, 1, 59, 59, 999999, 'Europe/Paris') '2013-03-31T01:59:59.999999+01:00' dt = dt.add(microseconds=1) '2013-03-31T03:00:00+02:00' dt.subtract(microseconds=1) '2013-03-31T01:59:59.999998+01:00' - dt = pendulum.create(2013, 10, 27, 1, 59, 59, 999999, 'Europe/Paris') + dt = pendulum.datetime(2013, 10, 27, 1, 59, 59, 999999, 'Europe/Paris') dt = dt.add(hours=1) # We can't just do - # pendulum.create(2013, 10, 27, 2, 59, 59, 999999, 'Europe/Paris') + # pendulum.datetime(2013, 10, 27, 2, 59, 59, 999999, 'Europe/Paris') # because of the default normalization '2013-10-27T02:59:59.999999+02:00' dt = dt.add(microseconds=1) @@ -131,7 +131,7 @@ with the ``in_timezone()`` method. .. code-block:: python - in_paris = pendulum.create(2016, 8, 7, 22, 24, 30, tz='Europe/Paris') + in_paris = pendulum.datetime(2016, 8, 7, 22, 24, 30, tz='Europe/Paris') '2016-08-07T22:24:30+02:00' in_paris.in_timezone('America/New_York') '2016-08-07T16:24:30-04:00' diff --git a/docs/conf.py b/docs/conf.py index f5b7be44..f3fa34cf 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,7 +50,7 @@ # General information about the project. project = 'Pendulum' -copyright = '2016, Sébastien Eustace' +copyright = '2016-2018, Sébastien Eustace' author = 'Sébastien Eustace' # The version info for the project you're documenting, acts as replacement for @@ -58,9 +58,9 @@ # built documents. # # The short X.Y version. -version = '1.3' +version = '1.5' # The full version, including alpha/beta/rc tags. -release = '1.3.2' +release = '1.5.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/pendulum/__init__.py b/pendulum/__init__.py index 43d8b025..bb4403e6 100644 --- a/pendulum/__init__.py +++ b/pendulum/__init__.py @@ -8,7 +8,6 @@ from .period import Period # Mimicking standard library -datetime = Pendulum date = Date time = Time @@ -78,3 +77,19 @@ # Timezones from .tz import timezone, timezones, local_timezone, UTC + + +def datetime(year, month, day, + hour=0, minute=0, second=0, microsecond=0, + tzinfo=None, tz=None, dst_rule=None): + fold = None + if dst_rule == POST_TRANSITION: + fold = 1 + elif dst_rule == PRE_TRANSITION: + fold = 0 + + return Pendulum( + year, month, day, hour, minute, second, microsecond, + tzinfo=tzinfo or tz or UTC, + fold=fold + ) diff --git a/pendulum/date.py b/pendulum/date.py index 41f266ee..fd06ac58 100644 --- a/pendulum/date.py +++ b/pendulum/date.py @@ -4,9 +4,12 @@ import calendar import math +import warnings + from datetime import date, timedelta from dateutil.relativedelta import relativedelta +from .exceptions import PendulumDeprecationWarning from .period import Period from .formatting.difference_formatter import DifferenceFormatter from .mixins.default import TranslatableMixin, FormattableMixing, TestableMixin @@ -113,16 +116,39 @@ def tomorrow(cls): ### Getters/Setters def year_(self, year): - return self._setter(year=year) + warnings.warn( + 'The year_() method will be removed in version 2.0. ' + 'Use set(year={}) instead.'.format(year), + PendulumDeprecationWarning, + stacklevel=2 + ) + + return self.set(year=year) def month_(self, month): - return self._setter(month=month) + warnings.warn( + 'The month_() method will be removed in version 2.0. ' + 'Use set(month={}) instead.'.format(month), + PendulumDeprecationWarning, + stacklevel=2 + ) + + return self.set(month=month) def day_(self, day): - return self._setter(day=day) + warnings.warn( + 'The day_() method will be removed in version 2.0. ' + 'Use set(day={}) instead.'.format(day), + PendulumDeprecationWarning, + stacklevel=2 + ) - def _setter(self, **kwargs): - return self.replace(**kwargs) + return self.set(day=day) + + def set(self, year=None, month=None, day=None): + return self.replace( + year=year, month=month, day=day + ) @property def day_of_week(self): @@ -266,6 +292,12 @@ def between(self, dt1, dt2, equal=True): :rtype: bool """ + warnings.warn( + 'The between() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + if dt1 > dt2: dt1, dt2 = dt2, dt1 @@ -317,6 +349,12 @@ def min_(self, dt=None): :rtype: Date """ + warnings.warn( + 'The min_() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + if dt is None: dt = Date.today() @@ -334,6 +372,12 @@ def minimum(self, dt=None): :rtype: Date """ + warnings.warn( + 'The minimum() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + return self.min_(dt) def max_(self, dt=None): @@ -345,6 +389,12 @@ def max_(self, dt=None): :rtype: Date """ + warnings.warn( + 'The max_() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + if dt is None: dt = Date.today() @@ -362,6 +412,12 @@ def maximum(self, dt=None): :rtype: Date """ + warnings.warn( + 'The maximum() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + return self.max_(dt) def is_weekday(self): @@ -370,6 +426,12 @@ def is_weekday(self): :rtype: bool """ + warnings.warn( + 'The is_weekday() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + return not self.is_weekend() def is_weekend(self): @@ -378,6 +440,12 @@ def is_weekend(self): :rtype: bool """ + warnings.warn( + 'The is_weekend() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + return self.day_of_week in self._weekend_days def is_yesterday(self): @@ -386,6 +454,12 @@ def is_yesterday(self): :rtype: bool """ + warnings.warn( + 'The is_yesterday() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + return self == self.yesterday() def is_today(self): @@ -394,6 +468,12 @@ def is_today(self): :rtype: bool """ + warnings.warn( + 'The is_today() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + return self == self.today() def is_tomorrow(self): @@ -402,6 +482,12 @@ def is_tomorrow(self): :rtype: bool """ + warnings.warn( + 'The is_tomorrow() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + return self == self.tomorrow() def is_future(self): @@ -446,6 +532,12 @@ def is_same_day(self, dt): :rtype: bool """ + warnings.warn( + 'The is_same_day() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + return self == dt def is_sunday(self): @@ -454,6 +546,12 @@ def is_sunday(self): :rtype: bool """ + warnings.warn( + 'The is_sunday() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + return self.day_of_week == SUNDAY def is_monday(self): @@ -462,6 +560,12 @@ def is_monday(self): :rtype: bool """ + warnings.warn( + 'The is_monday() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + return self.day_of_week == MONDAY def is_tuesday(self): @@ -470,6 +574,12 @@ def is_tuesday(self): :rtype: bool """ + warnings.warn( + 'The is_tuesday() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + return self.day_of_week == TUESDAY def is_wednesday(self): @@ -478,6 +588,12 @@ def is_wednesday(self): :rtype: bool """ + warnings.warn( + 'The is_wednesday() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + return self.day_of_week == WEDNESDAY def is_thursday(self): @@ -486,6 +602,12 @@ def is_thursday(self): :rtype: bool """ + warnings.warn( + 'The is_thursday() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + return self.day_of_week == THURSDAY def is_friday(self): @@ -494,6 +616,12 @@ def is_friday(self): :rtype: bool """ + warnings.warn( + 'The is_friday() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + return self.day_of_week == FRIDAY def is_saturday(self): @@ -502,6 +630,12 @@ def is_saturday(self): :rtype: bool """ + warnings.warn( + 'The is_saturday() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + return self.day_of_week == SATURDAY def is_birthday(self, dt=None): @@ -582,6 +716,12 @@ def add_timedelta(self, delta): :rtype: Date """ + warnings.warn( + 'The add_timedelta() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + return self.add(days=delta.days) def subtract_timedelta(self, delta): @@ -593,6 +733,12 @@ def subtract_timedelta(self, delta): :rtype: Date """ + warnings.warn( + 'The subtract_timedelta() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + return self.subtract(days=delta.days) def __add__(self, other): diff --git a/pendulum/exceptions.py b/pendulum/exceptions.py index b777018b..ba278362 100644 --- a/pendulum/exceptions.py +++ b/pendulum/exceptions.py @@ -1,8 +1,16 @@ # -*- coding: utf-8 -*- -from .parsing.exceptions import ParserError - class PendulumException(BaseException): pass + + +class PendulumWarning(Warning): + + pass + + +class PendulumDeprecationWarning(PendulumWarning): + + pass diff --git a/pendulum/helpers.py b/pendulum/helpers.py index 23b70232..094dea79 100644 --- a/pendulum/helpers.py +++ b/pendulum/helpers.py @@ -159,9 +159,7 @@ def precise_diff(d1, d2): d1, d2 = d2, d1 sign = -1 - y_diff = d2.year - d1.year - m_diff = d2.month - d1.month - d_diff = d2.day - d1.day + d_diff = 0 hour_diff = 0 min_diff = 0 sec_diff = 0 @@ -170,6 +168,7 @@ def precise_diff(d1, d2): _day_number(d2.year, d2.month, d2.day) - _day_number(d1.year, d1.month, d1.day) ) + in_same_tz = False tz1 = None tz2 = None @@ -194,7 +193,10 @@ def precise_diff(d1, d2): if hasattr(d2, 'hour'): # If we are not in the same timezone # we need to adjust - if not in_same_tz: + # + # We also need to adjust if we do not + # have variable-length units + if not in_same_tz or total_days == 0: offset1 = d1.utcoffset() offset2 = d2.utcoffset() @@ -225,6 +227,14 @@ def precise_diff(d1, d2): hour_diff += 24 d_diff -= 1 + if d1 > d2: + d1, d2 = d2, d1 + sign = -1 + + y_diff = d2.year - d1.year + m_diff = d2.month - d1.month + d_diff += d2.day - d1.day + if d_diff < 0: year = d2.year month = d2.month diff --git a/pendulum/mixins/default.py b/pendulum/mixins/default.py index b2177740..77aba1e6 100644 --- a/pendulum/mixins/default.py +++ b/pendulum/mixins/default.py @@ -1,8 +1,11 @@ # -*- coding: utf-8 -*- import locale as _locale +import warnings + from contextlib import contextmanager +from ..exceptions import PendulumDeprecationWarning from ..translator import Translator from ..formatting import FORMATTERS @@ -77,6 +80,12 @@ def reset_to_string_format(cls): Reset the format used to the default when type juggling a Date instance to a string. """ + warnings.warn( + 'The reset_to_string_format() helper ' + 'will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) cls.set_to_string_format(cls.DEFAULT_TO_STRING_FORMAT) @classmethod @@ -87,6 +96,12 @@ def set_to_string_format(cls, fmt): :type fmt: str """ + warnings.warn( + 'The set_to_string_format() helper ' + 'will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) cls._to_string_format = fmt def format(self, fmt, locale=None, formatter=None): diff --git a/pendulum/parsing/parser.py b/pendulum/parsing/parser.py index d64fd7c6..2e61fa80 100644 --- a/pendulum/parsing/parser.py +++ b/pendulum/parsing/parser.py @@ -7,6 +7,7 @@ from datetime import datetime, date, time from dateutil import parser +from ..exceptions import PendulumDeprecationWarning from ..helpers import parse_iso8601, week_day, days_in_year from .exceptions import ParserError @@ -68,7 +69,7 @@ def __init__(self, **options): warnings.warn( 'The "strict" keyword when parsing will have ' 'another meaning in version 2.0. Use "exact" instead.', - DeprecationWarning, + PendulumDeprecationWarning, stacklevel=2 ) @@ -80,7 +81,7 @@ def __init__(self, **options): def is_strict(self): warnings.warn( 'is_strict() is deprecated. Use is_exact() instead.', - DeprecationWarning, + PendulumDeprecationWarning, stacklevel=2 ) diff --git a/pendulum/pendulum.py b/pendulum/pendulum.py index cbbf94ac..733d5b07 100644 --- a/pendulum/pendulum.py +++ b/pendulum/pendulum.py @@ -11,6 +11,7 @@ from .date import Date from .time import Time from .period import Period +from .exceptions import PendulumDeprecationWarning from .exceptions import PendulumException from .tz import Timezone, UTC, FixedTimezone, local_timezone from .tz.timezone_info import TimezoneInfo @@ -352,6 +353,12 @@ def create(cls, year=None, month=None, day=None, :rtype: Pendulum """ + warnings.warn( + 'The create() helper will no longer exist in version 2.0. ' + 'Use datetime() instead.', + PendulumDeprecationWarning, + stacklevel=2 + ) tz = cls._safe_create_datetime_zone(tz) if any([year is None, month is None, day is None]): @@ -403,7 +410,7 @@ def create_from_format(cls, time, fmt, tz=UTC, formatter='classic'): 'Using the classic formatter in from_format() ' 'is deprecated and will no longer be possible ' 'in version 2.0. Use the alternative formatter instead.', - DeprecationWarning, + PendulumDeprecationWarning, stacklevel=2 ) dt = datetime.datetime.strptime(time, fmt) @@ -512,28 +519,73 @@ def copy(self): :rtype: Pendulum """ + warnings.warn( + 'The copy() method will be removed in version 2.0. ', + PendulumDeprecationWarning, + stacklevel=2 + ) return self.instance(self._datetime) # Getters/Setters def hour_(self, hour): - return self._setter(hour=hour) + warnings.warn( + 'The hour_() method will be removed in version 2.0. ' + 'Use set(hour={}) instead.'.format(hour), + PendulumDeprecationWarning, + stacklevel=2 + ) + + return self.set(hour=hour) def minute_(self, minute): - return self._setter(minute=minute) + warnings.warn( + 'The minute_() method will be removed in version 2.0. ' + 'Use set(minute={}) instead.'.format(minute), + PendulumDeprecationWarning, + stacklevel=2 + ) + + return self.set(minute=minute) def second_(self, second): - return self._setter(second=second) + warnings.warn( + 'The second_() method will be removed in version 2.0. ' + 'Use set(second={}) instead.'.format(second), + PendulumDeprecationWarning, + stacklevel=2 + ) + + return self.set(second=second) def microsecond_(self, microsecond): - return self._setter(microsecond=microsecond) + warnings.warn( + 'The microsecond_() method will be removed in version 2.0. ' + 'Use set(microsecond={}) instead.'.format(microsecond), + PendulumDeprecationWarning, + stacklevel=2 + ) + + return self.set(microsecond=microsecond) + + def set(self, **kwargs): + if 'tz' in kwargs: + kwargs['tzinfo'] = kwargs.pop('tz') + + return self.replace(**kwargs) - def _setter(self, **kwargs): kwargs['tzinfo'] = True return self._tz.convert(self.replace(**kwargs)) def timezone_(self, tz): + warnings.warn( + 'The timezone_() method will be removed in version 2.0. ' + 'Use set(tz={}) instead.'.format(tz), + PendulumDeprecationWarning, + stacklevel=2 + ) + return self.__class__( self.year, self.month, self.day, self.hour, self.minute, self.second, self.microsecond, @@ -541,9 +593,22 @@ def timezone_(self, tz): ) def tz_(self, tz): + warnings.warn( + 'The tz_() method will be removed in version 2.0. ' + 'Use set(tz={}) instead.'.format(tz), + PendulumDeprecationWarning, + stacklevel=2 + ) + return self.timezone_(tz) def timestamp_(self, timestamp, tz=UTC): + warnings.warn( + 'The timestamp_() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + return self.create_from_timestamp(timestamp, tz) @property @@ -615,10 +680,30 @@ def offset_hours(self): @property def local(self): + warnings.warn( + 'The local property will be removed in version 2.0. ' + 'Use is_local() instead.', + PendulumDeprecationWarning, + stacklevel=2 + ) + + return self.is_local() + + def is_local(self): return self.offset == self.in_timezone(self._local_timezone()).offset @property def utc(self): + warnings.warn( + 'The utc property will be removed in version 2.0. ' + 'Use is_utc() instead.', + PendulumDeprecationWarning, + stacklevel=2 + ) + + return self.is_utc() + + def is_utc(self): return self.offset == 0 @property @@ -708,6 +793,13 @@ def with_date_time(self, year, month, day, hour, minute, second, microsecond=0): :rtype: Pendulum """ + warnings.warn( + 'The with_date_time() method will be removed in version 2.0. ' + 'Use both on() and at() methods instead.', + PendulumDeprecationWarning, + stacklevel=2 + ) + return self.replace( year=year, month=month, day=day, hour=hour, minute=minute, second=second, @@ -723,6 +815,12 @@ def with_time_from_string(self, time): :rtype: Pendulum """ + warnings.warn( + 'The with_time_from_string() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + time = time.split(':') hour = int(time[0]) @@ -763,6 +861,12 @@ def with_timestamp(self, timestamp): :type timestamp: int or float :rtype: Pendulum """ + warnings.warn( + 'The with_timestamp() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + dt = datetime.datetime.fromtimestamp(timestamp, UTC).astimezone(self._tz) return self.instance(dt) @@ -951,6 +1055,12 @@ def between(self, dt1, dt2, equal=True): :rtype: bool """ + warnings.warn( + 'The between() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + if dt1 > dt2: dt1, dt2 = dt2, dt1 @@ -1002,6 +1112,12 @@ def min_(self, dt=None): :rtype: Pendulum """ + warnings.warn( + 'The min_() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + if dt is None: dt = Pendulum.now(self.timezone) @@ -1019,6 +1135,12 @@ def minimum(self, dt=None): :rtype: Pendulum """ + warnings.warn( + 'The minimum() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + return self.min_(dt) def max_(self, dt=None): @@ -1030,6 +1152,12 @@ def max_(self, dt=None): :rtype: Pendulum """ + warnings.warn( + 'The max_() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + if dt is None: dt = Pendulum.now(self.timezone) @@ -1047,6 +1175,12 @@ def maximum(self, dt=None): :rtype: Pendulum """ + warnings.warn( + 'The maximum() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + return self.max_(dt) def is_yesterday(self): @@ -1055,6 +1189,12 @@ def is_yesterday(self): :rtype: bool """ + warnings.warn( + 'The is_yesterday() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + return self.to_date_string() == self.yesterday(self._tz).to_date_string() def is_today(self): @@ -1063,6 +1203,12 @@ def is_today(self): :rtype: bool """ + warnings.warn( + 'The is_today() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + return self.to_date_string() == self.now(self._tz).to_date_string() def is_tomorrow(self): @@ -1071,6 +1217,11 @@ def is_tomorrow(self): :rtype: bool """ + warnings.warn( + 'The is_tomorrow() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) return self.to_date_string() == self.tomorrow(self._tz).to_date_string() def is_future(self): @@ -1107,6 +1258,11 @@ def is_same_day(self, dt): :rtype: bool """ + warnings.warn( + 'The is_same_day() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) dt = self._get_datetime(dt, True) return self.to_date_string() == dt.to_date_string() @@ -1225,6 +1381,12 @@ def add_timedelta(self, delta): :rtype: Pendulum """ + warnings.warn( + 'The add_timedelta() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + if isinstance(delta, Period): return self.add( years=delta.years, months=delta.months, @@ -1233,8 +1395,7 @@ def add_timedelta(self, delta): seconds=delta.remaining_seconds, microseconds=delta.microseconds ) - return self.add(days=delta.days, seconds=delta.seconds, - microseconds=delta.microseconds) + return self.add(seconds=delta.total_seconds()) def subtract_timedelta(self, delta): """ @@ -1245,6 +1406,12 @@ def subtract_timedelta(self, delta): :rtype: Pendulum """ + warnings.warn( + 'The subtract_timedelta() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + if isinstance(delta, Period): return self.subtract( years=delta.years, months=delta.months, @@ -1264,6 +1431,13 @@ def seconds_since_midnight(self): :rtype: int """ + warnings.warn( + 'The seconds_since_midnight() ' + 'method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + return self.diff(self.start_of('day')).in_seconds() def seconds_until_end_of_day(self): @@ -1272,6 +1446,13 @@ def seconds_until_end_of_day(self): :rtype: int """ + warnings.warn( + 'The seconds_until_end_of_day() ' + 'method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + return self.diff(self.end_of('day')).in_seconds() def diff(self, dt=None, abs=True): @@ -1980,16 +2161,10 @@ def __getnewargs__(self): return(self, ) def _getstate(self, protocol=3): - tz = self.timezone_name - - # Fix for fixed timezones not being properly unpickled - if isinstance(self.tz, FixedTimezone): - tz = self.offset_hours - return ( self.year, self.month, self.day, self.hour, self.minute, self.second, self.microsecond, - tz, + self.tzinfo, self.fold ) diff --git a/pendulum/period.py b/pendulum/period.py index 5b7cd0cc..e62361f8 100644 --- a/pendulum/period.py +++ b/pendulum/period.py @@ -1,10 +1,13 @@ # -*- coding: utf-8 -*- import operator +import warnings + import pendulum -from datetime import datetime, date +from datetime import datetime, date, timedelta +from .exceptions import PendulumDeprecationWarning from .mixins.interval import WordableIntervalMixin from .interval import BaseInterval, Interval from .constants import MONTHS_PER_YEAR @@ -144,6 +147,12 @@ def in_days(self): return self._delta['total']['days'] def in_weekdays(self): + warnings.warn( + 'The in_weekdays() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + start, end = self.start.start_of('day'), self.end.start_of('day') if not self._absolute and self.invert: start, end = self.end.start_of('day'), self.start.start_of('day') @@ -158,6 +167,12 @@ def in_weekdays(self): return days * (-1 if not self._absolute and self.invert else 1) def in_weekend_days(self): + warnings.warn( + 'The in_weekend_days() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + start, end = self.start.start_of('day'), self.end.start_of('day') if not self._absolute and self.invert: start, end = self.end.start_of('day'), self.start.start_of('day') @@ -300,6 +315,17 @@ def __repr__(self): self._start, self._end ) + def _cmp(self, other): + # Only needed for PyPy3 + assert isinstance(other, timedelta) + + if isinstance(other, Period): + other = other.as_timedelta() + + td = self.as_timedelta() + + return 0 if td == other else 1 if td > other else -1 + def _getstate(self, protocol=3): start, end = self.start, self.end diff --git a/pendulum/time.py b/pendulum/time.py index 4d7e0a3d..06dd94e0 100644 --- a/pendulum/time.py +++ b/pendulum/time.py @@ -1,7 +1,10 @@ # -*- coding: utf-8 -*- +import warnings + from datetime import time, datetime, timedelta +from .exceptions import PendulumDeprecationWarning from .interval import Interval, AbsoluteInterval from .mixins.default import TranslatableMixin, FormattableMixing, TestableMixin from .constants import ( @@ -129,6 +132,12 @@ def between(self, dt1, dt2, equal=True): :rtype: bool """ + warnings.warn( + 'The between() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + if dt1 > dt2: dt1, dt2 = dt2, dt1 @@ -180,6 +189,12 @@ def min_(self, dt=None): :rtype: Time """ + warnings.warn( + 'The min_() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + if dt is None: dt = Time.now() @@ -197,6 +212,12 @@ def minimum(self, dt=None): :rtype: Time """ + warnings.warn( + 'The minimum() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + return self.min_(dt) def max_(self, dt=None): @@ -208,6 +229,12 @@ def max_(self, dt=None): :rtype: Time """ + warnings.warn( + 'The max() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + if dt is None: dt = Time.now() @@ -225,6 +252,12 @@ def maximum(self, dt=None): :rtype: Time """ + warnings.warn( + 'The maximum() method will be removed in version 2.0.', + PendulumDeprecationWarning, + stacklevel=2 + ) + return self.max_(dt) def __hash__(self): diff --git a/pendulum/tz/timezone.py b/pendulum/tz/timezone.py index 88592396..fad44e5c 100644 --- a/pendulum/tz/timezone.py +++ b/pendulum/tz/timezone.py @@ -218,65 +218,55 @@ def _normalize(self, dt, dst_rule=None): offset = self._tzinfos[tzinfo_index].offset unix_time = (dt - datetime(1970, 1, 1)).total_seconds() - offset else: - # tr.pre_time < dt < tr.time - # Skipped time - if dst_rule == self.TRANSITION_ERROR: - raise NonExistingTime(dt) - elif dst_rule == self.PRE_TRANSITION: - # We do not apply the transition - (unix_time, - tzinfo_index) = self._get_previous_transition_time(tr, dt, skipped=True) - else: - unix_time = tr.unix_time - (tr.time - dt).total_seconds() + unix_time, tzinfo_index = self._get_time(dt, tr, dst_rule) elif tr is end: if tr.pre_time < dt: # After the last transition. unix_time = tr.unix_time + (dt - tr.time).total_seconds() else: - # tr.time <= dt <= tr.pre_time - # Repeated time - if dst_rule == self.TRANSITION_ERROR: - raise AmbiguousTime(dt) - elif dst_rule == self.PRE_TRANSITION: - # We do not apply the transition - (unix_time, - tzinfo_index) = self._get_previous_transition_time(tr, dt) - else: - unix_time = tr.unix_time + (dt - tr.time).total_seconds() + unix_time, tzinfo_index = self._get_time(dt, tr, dst_rule) else: - if tr.pre_time <= dt < tr.time: - # tr.pre_time <= dt < tr.time - # Skipped time - if dst_rule == self.TRANSITION_ERROR: - raise NonExistingTime(dt) - elif dst_rule == self.PRE_TRANSITION: - # We do not apply the transition - (unix_time, - tzinfo_index) = self._get_previous_transition_time(tr, dt, skipped=True) - else: - unix_time = tr.unix_time - (tr.pre_time - dt).total_seconds() - elif tr.time <= dt <= tr.pre_time: - # tr.time <= dt <= tr.pre_time - # Repeated time - if dst_rule == self.TRANSITION_ERROR: - raise AmbiguousTime(dt) - elif dst_rule == self.PRE_TRANSITION: - # We do not apply the transition - (unix_time, - tzinfo_index) = self._get_previous_transition_time(tr, dt) - else: - unix_time = tr.unix_time + (dt - tr.time).total_seconds() - else: - # In between transitions - # The actual transition type is the previous transition one - (unix_time, - tzinfo_index) = self._get_previous_transition_time(tr, dt) + unix_time, tzinfo_index = self._get_time(dt, tr, dst_rule) return self._to_local_time( unix_time, dt.microsecond, tzinfo_index, fold ) + def _get_time(self, dt, tr, dst_rule): + tzinfo_index = tr._tzinfo_index + + if tr.pre_time <= dt < tr.time: + # tr.pre_time <= dt < tr.time + # Skipped time + if dst_rule == self.TRANSITION_ERROR: + raise NonExistingTime(dt) + elif dst_rule == self.PRE_TRANSITION: + # We do not apply the transition + (unix_time, + tzinfo_index) = self._get_previous_transition_time(tr, dt, + skipped=True) + else: + unix_time = tr.unix_time - (tr.pre_time - dt).total_seconds() + elif tr.time <= dt <= tr.pre_time: + # tr.time <= dt <= tr.pre_time + # Repeated time + if dst_rule == self.TRANSITION_ERROR: + raise AmbiguousTime(dt) + elif dst_rule == self.PRE_TRANSITION: + # We do not apply the transition + (unix_time, + tzinfo_index) = self._get_previous_transition_time(tr, dt) + else: + unix_time = tr.unix_time + (dt - tr.time).total_seconds() + else: + # In between transitions + # The actual transition type is the previous transition one + (unix_time, + tzinfo_index) = self._get_previous_transition_time(tr, dt) + + return unix_time, tzinfo_index + def _convert(self, dt): """ Converts a timezone-aware datetime to local time. @@ -522,13 +512,13 @@ def fromutc(self, dt): return (dt + self._tzinfo.adjusted_offset).replace(tzinfo=self._tzinfo) def __getinitargs__(self): - return self._offset + return (self._offset, ) class _UTC(FixedTimezone): def __init__(self): - super(_UTC, self).__init__(0, 'UTC', TransitionType(0, False, 'GMT')) + super(_UTC, self).__init__(0, 'UTC', TransitionType(0, False, 'UTC')) UTC._tz = self diff --git a/pendulum/tz/timezone_info.py b/pendulum/tz/timezone_info.py index 8dc1f4a7..a4ab1975 100644 --- a/pendulum/tz/timezone_info.py +++ b/pendulum/tz/timezone_info.py @@ -134,4 +134,8 @@ def dst(self, dt): def fromutc(self, dt): return dt.replace(tzinfo=self) + def __getinitargs__(self): + return () + + UTC = _UTC() diff --git a/pendulum/version.py b/pendulum/version.py index fa653fde..3224c5e7 100644 --- a/pendulum/version.py +++ b/pendulum/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- -VERSION = '1.3.2' +VERSION = '1.5.1' diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..24c86c8a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,32 @@ +[tool.poetry] +name = "pendulum" +version = "1.5.1" +description = "Python datetimes made easy." + +license = "MIT" + +authors = [ + "Sébastien Eustace " +] + +readme = 'README.rst' + +homepage = "https://pendulum.eustace.io" +repository = "https://github.com/sdispater/pendulum" + +keywords = ['datetime', 'date', 'time', 'timezone'] + +build = "build.py" + + +[tool.poetry.dependencies] +python = "~2.7 || ^3.4" +python-dateutil = "^2.6" +tzlocal = "^1.5" +pytzdata = ">=2018.3" + +[tool.poetry.dev-dependencies] +pytest = "^3.4" +pytest-cov = "^2.4" +pytz = ">=2018.3" +sphinx = "^1.7" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 390579a2..00000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -tzlocal -python-dateutil -pytzdata>=2017.2.2 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 2a9acf13..00000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[bdist_wheel] -universal = 1 diff --git a/setup.py b/setup.py deleted file mode 100644 index bd780d6a..00000000 --- a/setup.py +++ /dev/null @@ -1,121 +0,0 @@ -# -*- coding: utf-8 -*- - -import os -import sys -from setuptools import setup, find_packages, Extension -from distutils.errors import (CCompilerError, DistutilsExecError, - DistutilsPlatformError) -from distutils.command.build_ext import build_ext - - -try: - FileNotFoundError -except NameError: - FileNotFoundError = IOError # py2k - - -def get_version(): - basedir = os.path.dirname(__file__) - with open(os.path.join(basedir, 'pendulum/version.py')) as f: - variables = {} - exec(f.read(), variables) - - version = variables.get('VERSION') - if version: - return version - - raise RuntimeError('No version info found.') - - -__version__ = get_version() - -# C Extensions -with_extensions = os.getenv('PENDULUM_EXTENSIONS', None) - -if with_extensions == '1' or with_extensions is None: - with_extensions = True - -if with_extensions == '0' or hasattr(sys, 'pypy_version_info'): - with_extensions = False - -extensions = [] -if with_extensions: - extensions = [ - Extension('pendulum._extensions._helpers', - ['pendulum/_extensions/_helpers.c']), - ] - -class BuildFailed(Exception): - - pass - - -class ve_build_ext(build_ext): - # This class allows C extension building to fail. - - def run(self): - try: - build_ext.run(self) - except (DistutilsPlatformError, FileNotFoundError): - raise BuildFailed() - - def build_extension(self, ext): - try: - build_ext.build_extension(self, ext) - except (CCompilerError, DistutilsExecError, - DistutilsPlatformError, ValueError): - raise BuildFailed() - -packages = ['pendulum'] -for pkg in find_packages('pendulum'): - packages.append('pendulum.' + pkg) - -kwargs = dict( - name='pendulum', - license='MIT', - version=__version__, - description='Python datetimes made easy.', - long_description=open('README.rst').read(), - author='Sébastien Eustace', - author_email='sebastien@eustace.io', - url='https://github.com/sdispater/pendulum', - download_url='https://github.com/sdispater/pendulum/archive/%s.tar.gz' % __version__, - packages=packages, - install_requires=[ - 'tzlocal', - 'python-dateutil', - 'pytzdata', - ], - include_package_data=True, - tests_require=['pytest'], - test_suite='nose.collector', - classifiers=[ - 'Intended Audience :: Developers', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: Implementation :: CPython', - 'Programming Language :: Python :: Implementation :: PyPy' - ], -) - -if extensions: - kwargs['ext_modules'] = extensions - kwargs['cmdclass'] = dict(build_ext=ve_build_ext) - - -try: - setup(**kwargs) -except BuildFailed: - print("************************************************************") - print("Cannot compile C accelerator module, use pure python version") - print("************************************************************") - del kwargs['ext_modules'] - del kwargs['cmdclass'] - setup(**kwargs) diff --git a/tests-requirements.txt b/tests-requirements.txt deleted file mode 100644 index edfd951d..00000000 --- a/tests-requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ --r requirements.txt -pytest -pytest-cov -coverage<4 -pytz diff --git a/tests/pendulum_tests/test_add.py b/tests/pendulum_tests/test_add.py index e11fd031..8b75d40e 100644 --- a/tests/pendulum_tests/test_add.py +++ b/tests/pendulum_tests/test_add.py @@ -236,3 +236,17 @@ def test_add_time_to_new_transition_does_not_use_transition_rule(self): self.assertEqual('Europe/Paris', dt.timezone_name) self.assertEqual(7200, dt.offset) self.assertTrue(dt.is_dst) + + def test_period_over_midnight_tz(self): + start = pendulum.create(2018, 2, 25, tz='Europe/Paris') + end = start.add(hours=1) + period = end - start + new_end = start + period + + assert new_end == end + + def test_add_interval(self): + dt = pendulum.create(2017, 3, 11, 10, 45, tz='America/Los_Angeles') + new = dt + pendulum.interval(hours=24) + + self.assertPendulum(new, 2017, 3, 12, 11, 45) diff --git a/tests/pendulum_tests/test_behavior.py b/tests/pendulum_tests/test_behavior.py index 12a12854..acfbd65a 100644 --- a/tests/pendulum_tests/test_behavior.py +++ b/tests/pendulum_tests/test_behavior.py @@ -6,6 +6,7 @@ from datetime import datetime, date, time, timedelta from pendulum import Pendulum, timezone from pendulum.tz.timezone import Timezone +from pendulum.tz.loader import Loader from .. import AbstractTestCase @@ -101,6 +102,14 @@ def test_pickle_with_integer_tzinfo(self): self.assertEqual(dt1, dt2) + def test_pickle_with_empty_tzinfo_name(self): + empty_timezone = Timezone('', *Loader.load('Europe/Paris')) + dt1 = Pendulum(2016, 8, 27, 12, 34, 56, 123456, empty_timezone) + s = pickle.dumps(dt1) + dt2 = pickle.loads(s) + + self.assertEqual(dt1, dt2) + def test_proper_dst(self): dt = pendulum.create(1941, 7, 1, tz='Europe/Amsterdam') @@ -116,6 +125,11 @@ def test_deepcopy_datetime(self): self.assertEqual(dt._datetime, deepcopy(dt._datetime)) + def test_deepcopy_datetime_utcnow(self): + dt = pendulum.utcnow() + + self.assertEqual(dt._datetime, deepcopy(dt._datetime)) + def test_pickle_timezone(self): dt1 = pendulum.timezone('Europe/Amsterdam') s = pickle.dumps(dt1) diff --git a/tests/pendulum_tests/test_fluent_setters.py b/tests/pendulum_tests/test_fluent_setters.py index ddd68d9c..d3d9347c 100644 --- a/tests/pendulum_tests/test_fluent_setters.py +++ b/tests/pendulum_tests/test_fluent_setters.py @@ -184,3 +184,19 @@ def test_replace_tzinfo_dst_with_error_transition_rule(self): d = Pendulum.create(2013, 3, 31, 2, 30) self.assertRaises(NonExistingTime, d.replace, tzinfo='Europe/Paris') + + def test_set(self): + dt = pendulum.datetime(2016, 7, 2, 0, 41, 20) + + assert dt.set(year=1995).year == 1995 + assert dt.set(month=11).month == 11 + assert dt.set(day=9).day == 9 + assert dt.set(hour=12).hour == 12 + assert dt.set(minute=34).minute == 34 + assert dt.set(second=56).second == 56 + assert dt.set(tz='Europe/Paris').timezone_name == 'Europe/Paris' + + dt = pendulum.datetime(2013, 3, 31, 2, 30) + dt = dt.set(tz='Europe/Paris') + + self.assertPendulum(dt, 2013, 3, 31, 3, 30) diff --git a/tests/period_tests/test_add_subtract.py b/tests/period_tests/test_add_subtract.py index d645401f..3b177a1d 100644 --- a/tests/period_tests/test_add_subtract.py +++ b/tests/period_tests/test_add_subtract.py @@ -12,6 +12,15 @@ def test_dst_add(): assert new_end == end +def test_dst_add_non_variable_units(): + start = pendulum.create(2013, 3, 31, 1, 30, tz='Europe/Paris') + end = start.add(hours=1) + period = end - start + new_end = start + period + + assert new_end == end + + def test_dst_subtract(): start = pendulum.create(2017, 3, 7, tz='America/Toronto') end = start.add(days=6) diff --git a/tests/period_tests/test_behavior.py b/tests/period_tests/test_behavior.py index f93f86fa..3f2700bd 100644 --- a/tests/period_tests/test_behavior.py +++ b/tests/period_tests/test_behavior.py @@ -2,6 +2,9 @@ import pickle import pendulum + +from datetime import timedelta + from .. import AbstractTestCase @@ -34,3 +37,11 @@ def test_pickle(self): self.assertEqual(p.start, p2.start) self.assertEqual(p.end, p2.end) self.assertEqual(p.invert, p2.invert) + + def test_comparison_to_timedelta(self): + dt1 = pendulum.create(2016, 11, 18) + dt2 = pendulum.create(2016, 11, 20) + + period = dt2 - dt1 + + assert period < timedelta(days=4) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index d1ef4511..854972c6 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- +import pendulum + from datetime import datetime, date, time from pendulum.helpers import precise_diff, parse_iso8601 -from pendulum.tz.timezone import FixedTimezone from . import AbstractTestCase @@ -175,6 +176,29 @@ def test_parse_ios8601_invalid(self): self.assertRaises(ValueError, parse_iso8601, '2012W12-3') # Missing separator self.assertRaises(ValueError, parse_iso8601, '2012-W123') # Missing separator + def test_datetime(self): + dt = pendulum.datetime(2018, 4, 4, 12, 34, 56, 123456) + + self.assertPendulum(dt, 2018, 4, 4, 12, 34, 56, 123456) + assert dt.timezone_name == 'UTC' + + dt = pendulum.datetime( + 2013, 3, 31, 2, 30, + tzinfo='Europe/Paris' + ) + + self.assertPendulum(dt, 2013, 3, 31, 3, 30) + assert dt.timezone_name == 'Europe/Paris' + + dt = pendulum.datetime( + 2013, 3, 31, 2, 30, + tz='Europe/Paris', + dst_rule=pendulum.PRE_TRANSITION + ) + + self.assertPendulum(dt, 2013, 3, 31, 1, 30) + assert dt.timezone_name == 'Europe/Paris' + def assert_diff(self, diff, years=0, months=0, days=0, hours=0, minutes=0, seconds=0, microseconds=0): diff --git a/tests/tz_tests/test_timezone.py b/tests/tz_tests/test_timezone.py index 7b2ccd55..2b389aab 100644 --- a/tests/tz_tests/test_timezone.py +++ b/tests/tz_tests/test_timezone.py @@ -227,6 +227,15 @@ def test_on_last_transition(self): assert dt.microsecond == 0 assert dt.utcoffset().total_seconds() == 7200 + def test_just_before_last_transition(self): + tz = pendulum.timezone('Asia/Shanghai') + dt = datetime(1991, 4, 20, 1, 49, 8) + dt = tz.convert(dt, dst_rule=tz.POST_TRANSITION) + + epoch = datetime(1970, 1, 1, tzinfo=timezone('UTC')) + expected = (dt - epoch).total_seconds() + assert expected == 672079748.0 + def test_convert_fold_attribute_is_honored(self): self.skip_if_not_36() @@ -370,3 +379,9 @@ def test_fixed_timezone(self): self.assertEqual(tz2.utcoffset(dt).total_seconds(), 18000) self.assertIsNone(tz2.dst(dt)) + + def test_utc_tzname(self): + tz = Timezone.load('UTC') + dt = datetime(2016, 11, 26) + + assert tz.tzname(dt) == 'UTC' diff --git a/tox.ini b/tox.ini index f664d936..c3305105 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py{27,35,36}-{without,with}-extensions, pypy +envlist = py{27,35,36}-{without,with}-extensions, pypy, pypy3 [testenv] deps = -rtests-requirements.txt diff --git a/wheels-requirements.txt b/wheels-requirements.txt deleted file mode 100644 index e079f8a6..00000000 --- a/wheels-requirements.txt +++ /dev/null @@ -1 +0,0 @@ -pytest