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

Thu, 27 Feb 2025 14:42:39 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Thu, 27 Feb 2025 14:42:39 +0100
branch
eric7
changeset 11150
73d80859079c
permissions
-rw-r--r--

Code Style Checkers
- Refactored the various code style checkers for better maintainability.

# -*- coding: utf-8 -*-

# Copyright (c) 2025 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing a node visitor to check datetime function calls.
"""

import ast

import AstUtilities


class DateTimeVisitor(ast.NodeVisitor):
    """
    Class implementing a node visitor to check datetime function calls.

    Note: This class is modeled after flake8_datetimez v20.10.0 checker.
    """

    def __init__(self):
        """
        Constructor
        """
        super().__init__()

        self.violations = []

    def __getFromKeywords(self, keywords, name):
        """
        Private method to get a keyword node given its name.

        @param keywords list of keyword argument nodes
        @type list of ast.AST
        @param name name of the keyword node
        @type str
        @return keyword node
        @rtype ast.AST
        """
        for keyword in keywords:
            if keyword.arg == name:
                return keyword

        return None

    def visit_Call(self, node):
        """
        Public method to handle a function call.

        Every datetime related function call is check for use of the naive
        variant (i.e. use without TZ info).

        @param node reference to the node to be processed
        @type ast.Call
        """
        # datetime.something()
        isDateTimeClass = (
            isinstance(node.func, ast.Attribute)
            and isinstance(node.func.value, ast.Name)
            and node.func.value.id == "datetime"
        )

        # datetime.datetime.something()
        isDateTimeModuleAndClass = (
            isinstance(node.func, ast.Attribute)
            and isinstance(node.func.value, ast.Attribute)
            and node.func.value.attr == "datetime"
            and isinstance(node.func.value.value, ast.Name)
            and node.func.value.value.id == "datetime"
        )

        if isDateTimeClass:
            if node.func.attr == "datetime":
                # datetime.datetime(2000, 1, 1, 0, 0, 0, 0,
                #                   datetime.timezone.utc)
                isCase1 = len(node.args) >= 8 and not (
                    AstUtilities.isNameConstant(node.args[7])
                    and AstUtilities.getValue(node.args[7]) is None
                )

                # datetime.datetime(2000, 1, 1, tzinfo=datetime.timezone.utc)
                tzinfoKeyword = self.__getFromKeywords(node.keywords, "tzinfo")
                isCase2 = tzinfoKeyword is not None and not (
                    AstUtilities.isNameConstant(tzinfoKeyword.value)
                    and AstUtilities.getValue(tzinfoKeyword.value) is None
                )

                if not (isCase1 or isCase2):
                    self.violations.append((node, "M-301"))

            elif node.func.attr == "time":
                # time(12, 10, 45, 0, datetime.timezone.utc)
                isCase1 = len(node.args) >= 5 and not (
                    AstUtilities.isNameConstant(node.args[4])
                    and AstUtilities.getValue(node.args[4]) is None
                )

                # datetime.time(12, 10, 45, tzinfo=datetime.timezone.utc)
                tzinfoKeyword = self.__getFromKeywords(node.keywords, "tzinfo")
                isCase2 = tzinfoKeyword is not None and not (
                    AstUtilities.isNameConstant(tzinfoKeyword.value)
                    and AstUtilities.getValue(tzinfoKeyword.value) is None
                )

                if not (isCase1 or isCase2):
                    self.violations.append((node, "M-321"))

            elif node.func.attr == "date":
                self.violations.append((node, "M-311"))

        if isDateTimeClass or isDateTimeModuleAndClass:
            if node.func.attr == "today":
                self.violations.append((node, "M-302"))

            elif node.func.attr == "utcnow":
                self.violations.append((node, "M-303"))

            elif node.func.attr == "utcfromtimestamp":
                self.violations.append((node, "M-304"))

            elif node.func.attr in "now":
                # datetime.now(UTC)
                isCase1 = (
                    len(node.args) == 1
                    and len(node.keywords) == 0
                    and not (
                        AstUtilities.isNameConstant(node.args[0])
                        and AstUtilities.getValue(node.args[0]) is None
                    )
                )

                # datetime.now(tz=UTC)
                tzKeyword = self.__getFromKeywords(node.keywords, "tz")
                isCase2 = tzKeyword is not None and not (
                    AstUtilities.isNameConstant(tzKeyword.value)
                    and AstUtilities.getValue(tzKeyword.value) is None
                )

                if not (isCase1 or isCase2):
                    self.violations.append((node, "M-305"))

            elif node.func.attr == "fromtimestamp":
                # datetime.fromtimestamp(1234, UTC)
                isCase1 = (
                    len(node.args) == 2
                    and len(node.keywords) == 0
                    and not (
                        AstUtilities.isNameConstant(node.args[1])
                        and AstUtilities.getValue(node.args[1]) is None
                    )
                )

                # datetime.fromtimestamp(1234, tz=UTC)
                tzKeyword = self.__getFromKeywords(node.keywords, "tz")
                isCase2 = tzKeyword is not None and not (
                    AstUtilities.isNameConstant(tzKeyword.value)
                    and AstUtilities.getValue(tzKeyword.value) is None
                )

                if not (isCase1 or isCase2):
                    self.violations.append((node, "M-306"))

            elif node.func.attr == "strptime":
                # datetime.strptime(...).replace(tzinfo=UTC)
                parent = getattr(node, "_dtCheckerParent", None)
                pparent = getattr(parent, "_dtCheckerParent", None)
                if not (
                    isinstance(parent, ast.Attribute) and parent.attr == "replace"
                ) or not isinstance(pparent, ast.Call):
                    isCase1 = False
                else:
                    tzinfoKeyword = self.__getFromKeywords(pparent.keywords, "tzinfo")
                    isCase1 = tzinfoKeyword is not None and not (
                        AstUtilities.isNameConstant(tzinfoKeyword.value)
                        and AstUtilities.getValue(tzinfoKeyword.value) is None
                    )

                if not isCase1:
                    self.violations.append((node, "M-307"))

            elif node.func.attr == "fromordinal":
                self.violations.append((node, "M-308"))

        # date.something()
        isDateClass = (
            isinstance(node.func, ast.Attribute)
            and isinstance(node.func.value, ast.Name)
            and node.func.value.id == "date"
        )

        # datetime.date.something()
        isDateModuleAndClass = (
            isinstance(node.func, ast.Attribute)
            and isinstance(node.func.value, ast.Attribute)
            and node.func.value.attr == "date"
            and isinstance(node.func.value.value, ast.Name)
            and node.func.value.value.id == "datetime"
        )

        if isDateClass or isDateModuleAndClass:
            if node.func.attr == "today":
                self.violations.append((node, "M-312"))

            elif node.func.attr == "fromtimestamp":
                self.violations.append((node, "M-313"))

            elif node.func.attr == "fromordinal":
                self.violations.append((node, "M-314"))

            elif node.func.attr == "fromisoformat":
                self.violations.append((node, "M-315"))

        self.generic_visit(node)


#
# eflag: noqa = M-891

eric ide

mercurial