diff --git a/Pipfile b/Pipfile index 494bfadb..1de46551 100644 --- a/Pipfile +++ b/Pipfile @@ -13,6 +13,7 @@ pytest-tap = "*" flaky = "*" stdeb = {version="*", markers="sys_platform == 'linux'"} white = {version="*", markers="python_version >= '3.6'"} +pytz = "*" [packages] diff --git a/Pipfile.lock b/Pipfile.lock index deee6963..91d17404 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "5941b36256503b9729e927f3249a366748da5c353b59c1e32ca7fd919b31a4d6" + "sha256": "bc81bb0e64d7ed1eed2627819b4a806d8cba554c1e0398191ba1ba32a216ed2a" }, "pipfile-spec": 6, "requires": {}, @@ -277,10 +277,10 @@ }, "pbr": { "hashes": [ - "sha256:56b7a8ba7d64bf6135a9dfefb85a80d95924b3fde5ed6343a1a1d464a040dae3", - "sha256:de75cf1d510542c746beeff66b52241eb12c8f95f2ef846ee50ed5d72392caa4" - ], - "version": "==4.0.1" + "sha256:4e8a0ed6a8705a26768f4c3da26026013b157821fe5f95881599556ea9d91c19", + "sha256:dae4aaa78eafcad10ce2581fc34d694faa616727837fd8e55c1a00951ad6744f" + ], + "version": "==4.0.2" }, "pipenv": { "editable": true, @@ -295,6 +295,7 @@ }, "pluggy": { "hashes": [ + "sha256:714306e9b9a7b24ee4c1e3ff6463d7f652cdd30f4693121b31572e2fe1fdaea3", "sha256:d345c8fe681115900d6da8d048ba67c25df42973bda370783cd58826442dcd7c", "sha256:e160a7fcf25762bb60efc7e171d4497ff1d8d2d75a3d0df7a21b76821ecbf5c5", "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff" @@ -310,6 +311,8 @@ }, "pycodestyle": { "hashes": [ + "sha256:1ec08a51c901dfe44921576ed6e4c1f5b7ecbad403f871397feedb5eb8e4fa14", + "sha256:5ff2fbcbab997895ba9ead77e1b38b3ebc2e5c3b8a6194ef918666e4c790a00e", "sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766", "sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9" ], @@ -452,10 +455,10 @@ }, "tqdm": { "hashes": [ - "sha256:4f2eb1d14804caf7095500fe11da0e481a47af912e7b57c93f886ac3c40a49dd", - "sha256:91ac47ec2ba6bb92b7ba37706f4dea37019ddd784b22fd279a4b12d93327191d" - ], - "version": "==4.20.0" + "sha256:597e7526c85df881d51e094360181a84533aede1cb3f5a1cada8bbd4de557efd", + "sha256:fe3d218d5b61993d415aa2a9db6dd64c0e4cefb90164ebb197ef3b1d99f531dc" + ], + "version": "==4.23.0" }, "twine": { "hashes": [ diff --git a/pipenv/patched/prettytoml/tokens/py2toml.py b/pipenv/patched/prettytoml/tokens/py2toml.py index 4043601d..82991958 100755 --- a/pipenv/patched/prettytoml/tokens/py2toml.py +++ b/pipenv/patched/prettytoml/tokens/py2toml.py @@ -5,11 +5,8 @@ A converter of python values to TOML Token instances. import codecs import datetime import six -import strict_rfc3339 -import timestamp from prettytoml import tokens import re -from prettytoml.elements.metadata import NewlineElement from prettytoml.errors import TOMLError from prettytoml.tokens import Token from prettytoml.util import chunkate_string @@ -49,8 +46,10 @@ def create_primitive_token(value, multiline_strings_allowed=True): elif isinstance(value, float): return tokens.Token(tokens.TYPE_FLOAT, u'{}'.format(value)) elif isinstance(value, (datetime.datetime, datetime.date, datetime.time)): - ts = timestamp(value) // 1000 - return tokens.Token(tokens.TYPE_DATE, strict_rfc3339.timestamp_to_rfc3339_utcoffset(ts)) + s = value.isoformat() + if s.endswith('+00:00'): + s = s[:-6] + 'Z' + return tokens.Token(tokens.TYPE_DATE, s) elif isinstance(value, six.string_types): return create_string_token(value, multiline_strings_allowed=multiline_strings_allowed) diff --git a/pipenv/vendor/strict_rfc3339.py b/pipenv/vendor/strict_rfc3339.py deleted file mode 100644 index 4558c01b..00000000 --- a/pipenv/vendor/strict_rfc3339.py +++ /dev/null @@ -1,202 +0,0 @@ -# Copyright 2012 (C) Daniel Richman, Adam Greig -# -# This file is part of strict_rfc3339. -# -# strict_rfc3339 is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# strict_rfc3339 is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with strict_rfc3339. If not, see . - -""" -Super simple lightweight RFC3339 functions -""" - -import re -import time -import calendar - -__all__ = ["validate_rfc3339", - "InvalidRFC3339Error", - "rfc3339_to_timestamp", - "timestamp_to_rfc3339_utcoffset", - "timestamp_to_rfc3339_localoffset", - "now_to_rfc3339_utcoffset", - "now_to_rfc3339_localoffset"] - -rfc3339_regex = re.compile( - r"^(\d\d\d\d)\-(\d\d)\-(\d\d)T" - r"(\d\d):(\d\d):(\d\d)(\.\d+)?(Z|([+\-])(\d\d):(\d\d))$") - - -def validate_rfc3339(datestring): - """Check an RFC3339 string is valid via a regex and some range checks""" - - m = rfc3339_regex.match(datestring) - if m is None: - return False - - groups = m.groups() - - year, month, day, hour, minute, second = [int(i) for i in groups[:6]] - - if not 1 <= year <= 9999: - # Have to reject this, unfortunately (despite it being OK by rfc3339): - # calendar.timegm/calendar.monthrange can't cope (since datetime can't) - return False - - if not 1 <= month <= 12: - return False - - (_, max_day) = calendar.monthrange(year, month) - if not 1 <= day <= max_day: - return False - - if not (0 <= hour <= 23 and 0 <= minute <= 59 and 0 <= second <= 59): - # forbid leap seconds :-(. See README - return False - - if groups[7] != "Z": - (offset_sign, offset_hours, offset_mins) = groups[8:] - if not (0 <= int(offset_hours) <= 23 and 0 <= int(offset_mins) <= 59): - return False - - # all OK - return True - - -class InvalidRFC3339Error(ValueError): - """Subclass of ValueError thrown by rfc3339_to_timestamp""" - pass - - -def rfc3339_to_timestamp(datestring): - """Convert an RFC3339 date-time string to a UTC UNIX timestamp""" - - if not validate_rfc3339(datestring): - raise InvalidRFC3339Error - - groups = rfc3339_regex.match(datestring).groups() - - time_tuple = [int(p) for p in groups[:6]] - timestamp = calendar.timegm(time_tuple) - - seconds_part = groups[6] - if seconds_part is not None: - timestamp += float("0" + seconds_part) - - if groups[7] != "Z": - (offset_sign, offset_hours, offset_mins) = groups[8:] - offset_seconds = int(offset_hours) * 3600 + int(offset_mins) * 60 - if offset_sign == '-': - offset_seconds = -offset_seconds - timestamp -= offset_seconds - - return timestamp - - -def _seconds_and_microseconds(timestamp): - """ - Split a floating point timestamp into an integer number of seconds since - the epoch, and an integer number of microseconds (having rounded to the - nearest microsecond). - - If `_seconds_and_microseconds(x) = (y, z)` then the following holds (up to - the error introduced by floating point operations): - - * `x = y + z / 1_000_000.` - * `0 <= z < 1_000_000.` - """ - - if isinstance(timestamp, int): - return (timestamp, 0) - else: - timestamp_us = int(round(timestamp * 1e6)) - return divmod(timestamp_us, 1000000) - -def _make_datestring_start(time_tuple, microseconds): - ds_format = "{0:04d}-{1:02d}-{2:02d}T{3:02d}:{4:02d}:{5:02d}" - datestring = ds_format.format(*time_tuple) - - seconds_part_str = "{0:06d}".format(microseconds) - # There used to be a bug here where it could be 1000000 - assert len(seconds_part_str) == 6 and seconds_part_str[0] != '-' - seconds_part_str = seconds_part_str.rstrip("0") - if seconds_part_str != "": - datestring += "." + seconds_part_str - - return datestring - - -def timestamp_to_rfc3339_utcoffset(timestamp): - """Convert a UTC UNIX timestamp to RFC3339, with the offset as 'Z'""" - - seconds, microseconds = _seconds_and_microseconds(timestamp) - - time_tuple = time.gmtime(seconds) - datestring = _make_datestring_start(time_tuple, microseconds) - datestring += "Z" - - assert abs(rfc3339_to_timestamp(datestring) - timestamp) < 0.000001 - return datestring - - -def timestamp_to_rfc3339_localoffset(timestamp): - """ - Convert a UTC UNIX timestamp to RFC3339, using the local offset. - - localtime() provides the time parts. The difference between gmtime and - localtime tells us the offset. - """ - - seconds, microseconds = _seconds_and_microseconds(timestamp) - - time_tuple = time.localtime(seconds) - datestring = _make_datestring_start(time_tuple, microseconds) - - gm_time_tuple = time.gmtime(seconds) - offset = calendar.timegm(time_tuple) - calendar.timegm(gm_time_tuple) - - if abs(offset) % 60 != 0: - raise ValueError("Your local offset is not a whole minute") - - offset_minutes = abs(offset) // 60 - offset_hours = offset_minutes // 60 - offset_minutes %= 60 - - offset_string = "{0:02d}:{1:02d}".format(offset_hours, offset_minutes) - - if offset < 0: - datestring += "-" - else: - datestring += "+" - - datestring += offset_string - assert abs(rfc3339_to_timestamp(datestring) - timestamp) < 0.000001 - - return datestring - - -def now_to_rfc3339_utcoffset(integer=True): - """Convert the current time to RFC3339, with the offset as 'Z'""" - - timestamp = time.time() - if integer: - timestamp = int(timestamp) - return timestamp_to_rfc3339_utcoffset(timestamp) - - -def now_to_rfc3339_localoffset(integer=True): - """Convert the current time to RFC3339, using the local offset.""" - - timestamp = time.time() - if integer: - timestamp = int(timestamp) - return timestamp_to_rfc3339_localoffset(timestamp) diff --git a/pipenv/vendor/timestamp.py b/pipenv/vendor/timestamp.py deleted file mode 100644 index effb3326..00000000 --- a/pipenv/vendor/timestamp.py +++ /dev/null @@ -1,9 +0,0 @@ -import sys - -def timestamp(d=None): - import datetime - import time - return int(time.mktime(d.timetuple()) * 1000) if d else int(time.time() * 1000) - -sys.modules[__name__] = timestamp - diff --git a/tests/unit/test_vendor.py b/tests/unit/test_vendor.py index a0e377fa..915af863 100644 --- a/tests/unit/test_vendor.py +++ b/tests/unit/test_vendor.py @@ -1,14 +1,21 @@ -# Make sure we use the patched packages. +# We need to import the patched packages directly from sys.path, so the +# identity checks can pass. import pipenv # noqa + +import datetime import os -from prettytoml import lexer +import pytest +import pytz + +from pipfile.api import PipfileParser +from prettytoml import lexer, tokens from prettytoml.elements.atomic import AtomicElement from prettytoml.elements.metadata import ( WhitespaceElement, PunctuationElement, CommentElement ) from prettytoml.elements.table import TableElement -from pipenv.patched.pipfile.api import PipfileParser +from prettytoml.tokens.py2toml import create_primitive_token def test_table(): @@ -62,3 +69,38 @@ class TestPipfileParser: assert parsed_dict["list"][1] == {} assert parsed_dict["bool"] is True assert parsed_dict["none"] is None + + +@pytest.mark.parametrize('dt, content', [ + ( # Date. + datetime.date(1992, 8, 19), + '1992-08-19', + ), + ( # Naive time. + datetime.time(15, 10), + '15:10:00', + ), + ( # Aware time in UTC. + datetime.time(15, 10, tzinfo=pytz.UTC), + '15:10:00Z', + ), + ( # Aware local time. + datetime.time(15, 10, tzinfo=pytz.FixedOffset(8 * 60)), + '15:10:00+08:00', + ), + ( # Naive datetime. + datetime.datetime(1992, 8, 19, 15, 10), + '1992-08-19T15:10:00', + ), + ( # Aware datetime in UTC. + datetime.datetime(1992, 8, 19, 15, 10, tzinfo=pytz.UTC), + '1992-08-19T15:10:00Z', + ), + ( # Aware local datetime. + datetime.datetime(1992, 8, 19, 15, 10, tzinfo=pytz.FixedOffset(8 * 60)), + '1992-08-19T15:10:00+08:00', + ), +]) +def test_token_date(dt, content): + token = create_primitive_token(dt) + assert token == tokens.Token(tokens.TYPE_DATE, content)