1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
|
from dataclasses import dataclass
from mypy.nodes import (
CallExpr,
Expression,
IndexExpr,
IntExpr,
MemberExpr,
NameExpr,
OpExpr,
SliceExpr,
StrExpr,
UnaryExpr,
Var,
)
from refurb.error import Error
from refurb.settings import Settings
@dataclass
class ErrorInfo(Error):
"""
Python 3.11 adds support for parsing UTC timestamps that end with `Z`, thus
removing the need to strip and append the `+00:00` timezone.
Bad:
```
date = "2023-02-21T02:23:15Z"
start_date = datetime.fromisoformat(date.replace("Z", "+00:00"))
```
Good:
```
date = "2023-02-21T02:23:15Z"
start_date = datetime.fromisoformat(date)
```
"""
name = "simplify-fromisoformat"
code = 162
categories = ("datetime", "python311", "readability")
def is_string(node: Expression) -> bool:
match node:
case StrExpr():
return True
case NameExpr(node=Var(type=ty)) if str(ty) == "builtins.str":
return True
return False
def is_utc_timezone(timezone: str) -> bool:
return timezone.startswith(("+", "-")) and timezone.strip("+-") in {
"00:00",
"0000",
"00",
}
def check(node: CallExpr, errors: list[Error], settings: Settings) -> None:
if settings.get_python_version() < (3, 11):
return
match node:
case CallExpr(
callee=MemberExpr(
expr=NameExpr(fullname="datetime.datetime"),
name="fromisoformat",
),
args=[arg],
):
match arg:
case CallExpr(
callee=MemberExpr(expr=date, name="replace"),
args=[
StrExpr(value="Z"),
StrExpr(value=timezone),
],
) if is_string(date) and is_utc_timezone(timezone):
old = f'fromisoformat(x.replace("Z", "{timezone}"))'
case OpExpr(
left=IndexExpr(
base=date,
index=SliceExpr(
begin_index=None,
end_index=UnaryExpr(op="-", expr=IntExpr(value=1)),
stride=None,
),
),
op="+",
right=StrExpr(value=timezone),
) if is_string(date) and is_utc_timezone(timezone):
old = f'fromisoformat(x[:-1] + "{timezone}")'
case OpExpr(
left=CallExpr(
callee=MemberExpr(expr=date, name="strip" | "rstrip" as func_name),
args=[StrExpr(value="Z")],
),
op="+",
right=StrExpr(value=timezone),
) if is_string(date) and is_utc_timezone(timezone):
old = f'fromisoformat(x.{func_name}("Z") + "{timezone}")'
case _:
return
errors.append(ErrorInfo.from_node(node, f"Replace `{old}` with `fromisoformat(x)`"))
|