Skip to content

equality not symmetric for subclasses of datetime.date and datetime.datetime #49766

Closed

Description

@jessaustin
BPO 5516
Nosy @ncoghlan, @abalkin, @jackdied, @ericsnowcurrently, @pganssle
Files
  • issue5516.diff: patch against trunk for datetimemodule.c and test_datetime.py
  • issue5516_trunk.diff: udiff against 2.7 trunk
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = 'https://github.com/abalkin'
    closed_at = None
    created_at = <Date 2009-03-19.00:51:23.421>
    labels = ['3.7', 'type-bug', 'library']
    title = 'equality not symmetric for subclasses of datetime.date and datetime.datetime'
    updated_at = <Date 2018-07-05.15:58:15.282>
    user = 'https://bugs.python.org/jessaustin'

    bugs.python.org fields:

    activity = <Date 2018-07-05.15:58:15.282>
    actor = 'p-ganssle'
    assignee = 'belopolsky'
    closed = False
    closed_date = None
    closer = None
    components = ['Library (Lib)']
    creation = <Date 2009-03-19.00:51:23.421>
    creator = 'jess.austin'
    dependencies = []
    files = ['13403', '13419']
    hgrepos = []
    issue_num = 5516
    keywords = []
    message_count = 19.0
    messages = ['83798', '84028', '84184', '103854', '103864', '103881', '103886', '104433', '106497', '106501', '106503', '106505', '106507', '106508', '108603', '108684', '195030', '195034', '195035']
    nosy_count = 7.0
    nosy_names = ['ncoghlan', 'belopolsky', 'jackdied', 'jess.austin', 'ysj.ray', 'eric.snow', 'p-ganssle']
    pr_nums = []
    priority = 'low'
    resolution = 'postponed'
    stage = 'needs patch'
    status = 'open'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue5516'
    versions = ['Python 3.7']

    Linked PRs

    Activity

    1. jessaustin commented on Mar 19, 2009

      jessaustinmannequin
      MannequinAuthor

      While the datetime.date and datetime.datetime classes consistently
      handle mixed-type comparison, their subclasses do not:

      >>> from datetime import date, datetime, time
      >>> d = date.today()
      >>> dt = datetime.combine(d, time())
      >>> d == dt
      False
      >>> dt == d
      False
      >>> class D(date):
      ...     pass
      ... 
      >>> class DT(datetime):
      ...     pass
      ... 
      >>> d = D.today()
      >>> dt = DT.combine(d, time())
      >>> d == dt
      True
      >>> dt == d
      False

      I think this is due to the premature "optimization" of using memcmp() in
      date_richcompare().

    2. added
      stdlibStandard Library Python modules in the Lib/ directory
      type-bugAn unexpected behavior, bug, or error
      on Mar 19, 2009
    3. changed the title [-]equality not reflixive for subclasses of datetime.date and datetime.datetime[/-] [+]equality not reflexive for subclasses of datetime.date and datetime.datetime[/+] on Mar 19, 2009
    4. changed the title [-]equality not reflexive for subclasses of datetime.date and datetime.datetime[/-] [+]equality not symmetric for subclasses of datetime.date and datetime.datetime[/+] on Mar 19, 2009
    5. jessaustin commented on Mar 23, 2009

      jessaustinmannequin
      MannequinAuthor

      The attached patch fixes this issue, and updates the tests. Contrary to
      my initial impression, it seems that a previous developer knew of this
      behavior and thought it correct; see the comment of the test I deleted.
      I left memcmp() in.

    6. jackdied commented on Mar 26, 2009

      @jackdied
      Contributor

      +1

      Patch and tests work for me. Uploaded a patch that is identical except
      the file paths are fixed.

      Was the old behavior stable across compilers anyway? It memcmpared two
      different structs and IIRC only the first item of each struct is
      guaranteed to be at the start of the memory location. No?

      With this patch only same-struct objects are memcmpared.

    7. self-assigned this
      on Jan 13, 2010
    8. AlexanderBelopolsky commented on Apr 21, 2010

      AlexanderBelopolskymannequin
      Mannequin

      There is another inconsistency that this patch does not seem to cure. With patch applied and D and DT defined as in OP,

      >>> D(1900,1,1) > DT(1900,1,1)
      True

      but

      >>> DT(1900,1,1) < D(1900,1,1)
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      TypeError: can't compare DT to D

      and

      >>> date(1900,1,1) < datetime(1900,1,1)
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      TypeError: can't compare datetime.datetime to datetime.date

      Note that without the patch,

      >>> D(1900,1,1) > DT(1900,1,1)
      False

      but both behaviors seem to be wrong.

    9. AlexanderBelopolsky commented on Apr 21, 2010

      AlexanderBelopolskymannequin
      Mannequin

      Upon further reflection I am -1 on this patch. First, as implemented this patch changes behavior of an explicit invocation of date.__eq__. The patch proposes to remove the following tests:

      •    # Neverthelss, comparison should work with the base-class (date)
        
      •    # projection if use of a date method is forced.
        
      •    self.assert_(as_date.\_\_eq__(as_datetime))
        
      •    different_day = (as_date.day + 1) % 20 + 1
        
      •    self.assert_(not as_date.\_\_eq__(as_datetime.replace(day=
        
      •                                                 different_day)))
        

      Second, the patch introduces dependence of the baseclass method (date_richcompare) on a particular subclass (PyDateTime_Check). This is against OOP principles.

      I am not sure how the "equality not symmetric" issue can be fixed. In my opinion current datetime implementation is fighting OOP and violating the substitution principle in an attempt to prevent date to datetime comparison. I would prefer seeing one of two things: either datetime not inheriting from date or making datetime to date comparison compare date part alone. Once you stop fighting OOP principles, symmetry of equality for subclasses will follow from that for the base class automatically.

      Given that either of these solutions means a major change, I think it is best to leave the things as they are now.

    10. jessaustin commented on Apr 21, 2010

      jessaustinmannequin
      MannequinAuthor

      To be systematic, without the patch:

      >>> D(1900, 1, 1) > DT(1900, 1, 1)
      False
      >>> D(1900, 1, 1) < DT(1900, 1, 1)
      False
      >>> DT(1900, 1, 1) > D(1900, 1, 1)
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      TypeError: can't compare DT to D
      >>> DT(1900, 1, 1) < D(1900, 1, 1)
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      TypeError: can't compare DT to D
      with the patch:
      >>> D(1900, 1, 1) > DT(1900, 1, 1)
      True
      >>> D(1900, 1, 1) < DT(1900, 1, 1)
      False
      >>> DT(1900, 1, 1) > D(1900, 1, 1)
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      TypeError: can't compare DT to D
      >>> DT(1900, 1, 1) < D(1900, 1, 1)
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      TypeError: can't compare DT to D

      It might seem like the latter behavior is marginally better, but really this is just a mess, since a date-datetime comparison TypeErrors in all directions. I appreciate Alexander's more experienced perspective, but it's not obvious to me that this problem is insoluble simply due to OOP algebra. I'm going to keep tinkering with this to see if there isn't a way to satisfy his concerns AND fix these bugs WITHOUT breaking the established (and admittedly anti-OOP) behavior that dates are not equal to datetimes.

      (Incidentally, the test I removed still seems to be an arbitrary ad-hoc requirement that subclasses of date behave differently than date itself. I don't see how one could rely on this given the other inconsistencies with date subclasses, and so violating this in order to fix more serious problems seems acceptable.)

      I'm reminded of the set and frozenset situation, which seems almost dual to this one. set and frozenset don't inherit from each other, but they do compare. This seems to bite you only when you try to redefine comparison in subclasses.

    11. AlexanderBelopolsky commented on Apr 21, 2010

      AlexanderBelopolskymannequin
      Mannequin

      On Wed, Apr 21, 2010 at 2:33 PM, Jess Austin <report@bugs.python.org> wrote:
      ..

      It might seem like the latter behavior is marginally better, but really this is just a mess, since a date-datetime comparison TypeErrors in all
      directions.  I appreciate Alexander's more experienced perspective, but it's not obvious to me that this problem is insoluble simply due to OOP
      algebra.  I'm going to keep tinkering with this to see if there isn't a way to satisfy his concerns AND fix these bugs WITHOUT breaking the
      established (and admittedly anti-OOP) behavior that dates are not equal to datetimes.

      I certainly don't have a proof that this is impossible, so best of
      luck. Note, however that the problematic behavior is due to D/DT
      classes implementor's choice not to derive DT from D. Whether
      resulting violation of the symmetry of equality is a bug in python or
      D/DT implementation is at least an open question.

    12. ncoghlan commented on Apr 28, 2010

      @ncoghlan
      Contributor

      I think I'll concur with the "this is a mess" assessment.

      Given that state of affairs, punting on this until 3.2 (at the earliest).

    13. removed their assignment
      on Apr 28, 2010
    14. abalkin commented on May 26, 2010

      @abalkin
      Member

      I am leaning towards "won't fix". Any objections?

    15. 13 remaining items

    16. iritkatriel commented on Jul 29, 2023

      @iritkatriel
      Member

      Reproduced on 3.13:

      Python 3.13.0a0 (heads/main:ecc05e23a1, Jul 28 2023, 20:21:40) [Clang 14.0.3 (clang-1403.0.22.14.1)] on darwin
      Type "help", "copyright", "credits" or "license" for more information.
      >>> from datetime import date, datetime, time
      >>> d = date.today()
      >>> dt = datetime.combine(d, time())
      >>> d == dt
      False
      >>> dt == d
      False
      >>> class D(date): pass
      ... 
      >>> class DT(datetime): pass
      ... 
      >>> d = D.today()
      >>> dt = DT.combine(d, time())
      >>> d == dt
      True
      >>> dt == d
      False
      >>> 
      
    17. added
      3.11only security fixes
      3.12only security fixes
      3.13only security fixes
      and removed on Jul 29, 2023
    18. added a commit that references this issue on Jan 30, 2024
    19. serhiy-storchaka commented on Feb 3, 2024

      @serhiy-storchaka
      Member

      @ncoghlan, @ericsnowcurrently, you participated in this discussion, can you take a look at #114760? It does not only fixes this issue, it makes the code simpler.

    20. added a commit that references this issue on Feb 11, 2024
    21. serhiy-storchaka commented on Feb 11, 2024

      @serhiy-storchaka
      Member

      It may be too serious change for backporting.

    22. added a commit that references this issue on Feb 14, 2024
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Assignees

    Labels

    3.13only security fixesstdlibStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or error

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions