src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Miscellaneous/DateTimeVisitor.py

branch
eric7
changeset 11150
73d80859079c
equal deleted inserted replaced
11149:fc45672fae42 11150:73d80859079c
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2025 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a node visitor to check datetime function calls.
8 """
9
10 import ast
11
12 import AstUtilities
13
14
15 class DateTimeVisitor(ast.NodeVisitor):
16 """
17 Class implementing a node visitor to check datetime function calls.
18
19 Note: This class is modeled after flake8_datetimez v20.10.0 checker.
20 """
21
22 def __init__(self):
23 """
24 Constructor
25 """
26 super().__init__()
27
28 self.violations = []
29
30 def __getFromKeywords(self, keywords, name):
31 """
32 Private method to get a keyword node given its name.
33
34 @param keywords list of keyword argument nodes
35 @type list of ast.AST
36 @param name name of the keyword node
37 @type str
38 @return keyword node
39 @rtype ast.AST
40 """
41 for keyword in keywords:
42 if keyword.arg == name:
43 return keyword
44
45 return None
46
47 def visit_Call(self, node):
48 """
49 Public method to handle a function call.
50
51 Every datetime related function call is check for use of the naive
52 variant (i.e. use without TZ info).
53
54 @param node reference to the node to be processed
55 @type ast.Call
56 """
57 # datetime.something()
58 isDateTimeClass = (
59 isinstance(node.func, ast.Attribute)
60 and isinstance(node.func.value, ast.Name)
61 and node.func.value.id == "datetime"
62 )
63
64 # datetime.datetime.something()
65 isDateTimeModuleAndClass = (
66 isinstance(node.func, ast.Attribute)
67 and isinstance(node.func.value, ast.Attribute)
68 and node.func.value.attr == "datetime"
69 and isinstance(node.func.value.value, ast.Name)
70 and node.func.value.value.id == "datetime"
71 )
72
73 if isDateTimeClass:
74 if node.func.attr == "datetime":
75 # datetime.datetime(2000, 1, 1, 0, 0, 0, 0,
76 # datetime.timezone.utc)
77 isCase1 = len(node.args) >= 8 and not (
78 AstUtilities.isNameConstant(node.args[7])
79 and AstUtilities.getValue(node.args[7]) is None
80 )
81
82 # datetime.datetime(2000, 1, 1, tzinfo=datetime.timezone.utc)
83 tzinfoKeyword = self.__getFromKeywords(node.keywords, "tzinfo")
84 isCase2 = tzinfoKeyword is not None and not (
85 AstUtilities.isNameConstant(tzinfoKeyword.value)
86 and AstUtilities.getValue(tzinfoKeyword.value) is None
87 )
88
89 if not (isCase1 or isCase2):
90 self.violations.append((node, "M-301"))
91
92 elif node.func.attr == "time":
93 # time(12, 10, 45, 0, datetime.timezone.utc)
94 isCase1 = len(node.args) >= 5 and not (
95 AstUtilities.isNameConstant(node.args[4])
96 and AstUtilities.getValue(node.args[4]) is None
97 )
98
99 # datetime.time(12, 10, 45, tzinfo=datetime.timezone.utc)
100 tzinfoKeyword = self.__getFromKeywords(node.keywords, "tzinfo")
101 isCase2 = tzinfoKeyword is not None and not (
102 AstUtilities.isNameConstant(tzinfoKeyword.value)
103 and AstUtilities.getValue(tzinfoKeyword.value) is None
104 )
105
106 if not (isCase1 or isCase2):
107 self.violations.append((node, "M-321"))
108
109 elif node.func.attr == "date":
110 self.violations.append((node, "M-311"))
111
112 if isDateTimeClass or isDateTimeModuleAndClass:
113 if node.func.attr == "today":
114 self.violations.append((node, "M-302"))
115
116 elif node.func.attr == "utcnow":
117 self.violations.append((node, "M-303"))
118
119 elif node.func.attr == "utcfromtimestamp":
120 self.violations.append((node, "M-304"))
121
122 elif node.func.attr in "now":
123 # datetime.now(UTC)
124 isCase1 = (
125 len(node.args) == 1
126 and len(node.keywords) == 0
127 and not (
128 AstUtilities.isNameConstant(node.args[0])
129 and AstUtilities.getValue(node.args[0]) is None
130 )
131 )
132
133 # datetime.now(tz=UTC)
134 tzKeyword = self.__getFromKeywords(node.keywords, "tz")
135 isCase2 = tzKeyword is not None and not (
136 AstUtilities.isNameConstant(tzKeyword.value)
137 and AstUtilities.getValue(tzKeyword.value) is None
138 )
139
140 if not (isCase1 or isCase2):
141 self.violations.append((node, "M-305"))
142
143 elif node.func.attr == "fromtimestamp":
144 # datetime.fromtimestamp(1234, UTC)
145 isCase1 = (
146 len(node.args) == 2
147 and len(node.keywords) == 0
148 and not (
149 AstUtilities.isNameConstant(node.args[1])
150 and AstUtilities.getValue(node.args[1]) is None
151 )
152 )
153
154 # datetime.fromtimestamp(1234, tz=UTC)
155 tzKeyword = self.__getFromKeywords(node.keywords, "tz")
156 isCase2 = tzKeyword is not None and not (
157 AstUtilities.isNameConstant(tzKeyword.value)
158 and AstUtilities.getValue(tzKeyword.value) is None
159 )
160
161 if not (isCase1 or isCase2):
162 self.violations.append((node, "M-306"))
163
164 elif node.func.attr == "strptime":
165 # datetime.strptime(...).replace(tzinfo=UTC)
166 parent = getattr(node, "_dtCheckerParent", None)
167 pparent = getattr(parent, "_dtCheckerParent", None)
168 if not (
169 isinstance(parent, ast.Attribute) and parent.attr == "replace"
170 ) or not isinstance(pparent, ast.Call):
171 isCase1 = False
172 else:
173 tzinfoKeyword = self.__getFromKeywords(pparent.keywords, "tzinfo")
174 isCase1 = tzinfoKeyword is not None and not (
175 AstUtilities.isNameConstant(tzinfoKeyword.value)
176 and AstUtilities.getValue(tzinfoKeyword.value) is None
177 )
178
179 if not isCase1:
180 self.violations.append((node, "M-307"))
181
182 elif node.func.attr == "fromordinal":
183 self.violations.append((node, "M-308"))
184
185 # date.something()
186 isDateClass = (
187 isinstance(node.func, ast.Attribute)
188 and isinstance(node.func.value, ast.Name)
189 and node.func.value.id == "date"
190 )
191
192 # datetime.date.something()
193 isDateModuleAndClass = (
194 isinstance(node.func, ast.Attribute)
195 and isinstance(node.func.value, ast.Attribute)
196 and node.func.value.attr == "date"
197 and isinstance(node.func.value.value, ast.Name)
198 and node.func.value.value.id == "datetime"
199 )
200
201 if isDateClass or isDateModuleAndClass:
202 if node.func.attr == "today":
203 self.violations.append((node, "M-312"))
204
205 elif node.func.attr == "fromtimestamp":
206 self.violations.append((node, "M-313"))
207
208 elif node.func.attr == "fromordinal":
209 self.violations.append((node, "M-314"))
210
211 elif node.func.attr == "fromisoformat":
212 self.violations.append((node, "M-315"))
213
214 self.generic_visit(node)
215
216
217 #
218 # eflag: noqa = M-891

eric ide

mercurial