Skip to content

Commit 938f786

Browse files
avara1986brettlangdonYun-Kim
authored
fix: parse datetime isoformat in python2 [backport #4874 to 1.7] (#4879)
Backport #4874 Co-authored-by: Brett Langdon <[email protected]> Co-authored-by: Yun Kim <[email protected]>
1 parent b63f2b8 commit 938f786

File tree

2 files changed

+102
-3
lines changed

2 files changed

+102
-3
lines changed

ddtrace/internal/utils/time.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,44 @@
11
from datetime import datetime
2+
from datetime import timedelta
23
from types import TracebackType
34
from typing import Optional
45
from typing import Type
56

67
from ddtrace.internal import compat
8+
from ddtrace.internal.logger import get_logger
9+
10+
11+
log = get_logger(__name__)
12+
13+
14+
def fromisoformat_py2(t):
15+
# type: (str) -> datetime
16+
"""Alternative function to datetime.fromisoformat that does not exist in python 2. This function parses dates with
17+
this format: 2022-09-01T01:00:00+02:00
18+
"""
19+
ret = datetime.strptime(t[:19], "%Y-%m-%dT%H:%M:%S")
20+
if t[19] == "+":
21+
ret -= timedelta(hours=int(t[20:22]), minutes=int(t[23:]))
22+
elif t[19] == "-":
23+
ret += timedelta(hours=int(t[20:22]), minutes=int(t[23:]))
24+
return ret
725

826

927
def parse_isoformat(date):
28+
# type: (str) -> Optional[datetime]
1029
if date.endswith("Z"):
1130
try:
1231
return datetime.strptime(date, "%Y-%m-%dT%H:%M:%S.%fZ")
1332
except ValueError:
1433
return datetime.strptime(date, "%Y-%m-%dT%H:%M:%SZ")
15-
elif hasattr(datetime, "fromisoformat"):
16-
return datetime.fromisoformat(date)
17-
raise ValueError("unsupported isoformat")
34+
try:
35+
if hasattr(datetime, "fromisoformat"):
36+
return datetime.fromisoformat(date)
37+
else:
38+
return fromisoformat_py2(date)
39+
except (ValueError, IndexError):
40+
log.debug("unsupported isoformat: %s", date)
41+
return None
1842

1943

2044
class StopWatch(object):

tests/internal/test_utils_time.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import datetime
2+
import sys
3+
4+
import pytest
5+
6+
from ddtrace.internal.utils.time import parse_isoformat
7+
8+
9+
@pytest.mark.parametrize(
10+
"date_str,expected",
11+
[
12+
("2022-09-01T01:00:00+02:00", datetime.datetime(2022, 8, 31, 23, 0)),
13+
("2022-07-31T22:00:00Z", datetime.datetime(2022, 7, 31, 22, 0)),
14+
("2022-07-31T22:00:00", None),
15+
("2022-07-31", None),
16+
("2022-07-3122:00:00", None),
17+
("", None),
18+
("aa", None),
19+
],
20+
)
21+
@pytest.mark.skipif(sys.version_info > (3, 0, 0), reason="Python 2 tests")
22+
def test_parse_isoformat_py2(date_str, expected):
23+
# type: (str, datetime.datetime) -> None
24+
result = parse_isoformat(date_str)
25+
assert result == expected
26+
27+
28+
@pytest.mark.parametrize(
29+
"date_str,expected",
30+
[
31+
("2022-07-31T22:00:00Z", datetime.datetime(2022, 7, 31, 22, 0)),
32+
("2022-07-31T22:00:00", None),
33+
("2022-07-31", None),
34+
("2022-07-3122:00:00", None),
35+
("", None),
36+
("aa", None),
37+
],
38+
)
39+
@pytest.mark.skipif(sys.version_info < (3, 5, 0) or sys.version_info >= (3, 7, 0), reason="Python 3.5 and 3.6 tests")
40+
def test_parse_isoformat_py36(date_str, expected):
41+
# type: (str, datetime.datetime) -> None
42+
result = parse_isoformat(date_str)
43+
assert result == expected
44+
45+
46+
@pytest.mark.skipif(sys.version_info < (3, 5, 0) or sys.version_info >= (3, 7, 0), reason="Python 3.5 and 3.6 tests")
47+
def test_parse_isoformat_py36_with_timezone():
48+
# type: () -> None
49+
result = parse_isoformat("2022-09-01T01:00:00+02:00")
50+
assert result == datetime.datetime(2022, 8, 31, 23, 0)
51+
52+
53+
@pytest.mark.parametrize(
54+
"date_str,expected",
55+
[
56+
("2022-07-31T22:00:00Z", datetime.datetime(2022, 7, 31, 22, 0)),
57+
("2022-07-31T22:00:00", datetime.datetime(2022, 7, 31, 22, 0)),
58+
("2022-07-31", datetime.datetime(2022, 7, 31, 0, 0)),
59+
("2022-07-3122:00:00", None),
60+
("", None),
61+
("aa", None),
62+
],
63+
)
64+
@pytest.mark.skipif(sys.version_info < (3, 7, 0), reason="Python 3 tests")
65+
def test_parse_isoformat_py3(date_str, expected):
66+
# type: (str, datetime.datetime) -> None
67+
result = parse_isoformat(date_str)
68+
assert result == expected
69+
70+
71+
@pytest.mark.skipif(sys.version_info < (3, 7, 0), reason="Python 3 tests")
72+
def test_parse_isoformat_py3_with_timezone():
73+
# type: () -> None
74+
result = parse_isoformat("2022-09-01T01:00:00+02:00")
75+
assert result == datetime.datetime(2022, 9, 1, 1, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200)))

0 commit comments

Comments
 (0)