<div dir="ltr"><br><br>On Mon, Aug 31, 2015 at 7:55 PM, Tim Peters <<a href="mailto:tim.peters@gmail.com">tim.peters@gmail.com</a>> wrote:<br>><br>> [Alex]<br>> >> After some thought, I believe the way to fix the implementation is what I<br>> >> suggested at first: reset fold to 0 before calling utcoffset() in __hash__.<br>> >> A rare hash collision is a small price to pay for having datetimes with<br>> >> different timezones in the same dictionary.<br>><br>> [Tim]<br>> > Ya, I can live with that.  In effect, we give up on converting to UTC<br>> > correctly for purposes of computing hash(), but only in rare cases.<br>> > hash() doesn't really care, and it remains true that datetime equality<br>> > (which does care) still implies hash equality.  The later and earlier<br>> > of ambiguous times will simply land on the same hash chain.<br>><br>> Nope, you wore me out prematurely ;-)<br>><br><br>It's getting late in my TZ, but what you are saying below sounds like a complaint that if you put t=second 01:30 as a key in the dictionary, you cannot later retrieve it by looking up t.astimezone(timezone.utc).  Sorry, but PEP 495 has never promised you that: "instances that differ only by the value of fold will compare as equal. Applications that need to differentiate between such instances should check the value of fold or convert them to a timezone that does not have ambiguous times."<div><br></div><div><<a href="https://www.python.org/dev/peps/pep-0495/#temporal-arithmetic">https://www.python.org/dev/peps/pep-0495/#temporal-arithmetic</a>></div><div><br></div><div>Maybe if we decide to do something with the arithmetic, we will be able to fix this wart as well.<br> <br>><br>> Consider datetimes dt1 and dt2 representing the earlier & later of an<br>> ambiguous time in their common zone (whatever it may be - doesn't<br>> matter).  Then all fields are identical except for `fold`.  Assume<br>> __hash__ forces `fold` to 0 before obtaining the UTC offset.  Then we<br>> have:<br>><br>>     dt1 == dt2<br>>     hash(dt1) == hash(dt2)<br>><br>> Fine so far as it goes.  Now do:<br>><br>>     u1 = dt1.astimezone(timezone.utc)<br>>     u2 = dt2.astimezone(timezone.utc)<br>><br>> At this point we have:<br>><br>>     u1 == dt1 == dt2 == u2 and u1 < u2<br>>     hash(dt1) == hash(dt2) == hash(u1)<br>><br>> (Parenthetically, note that despite the chain of equalities in the<br>> first of those lines, we do _not_ have u1 == u2 - transitivity fails,<br>> which is a bit of a wart by itself.)<br>><br>> Since u1 == dt1, and hash(u1) == hash(dt1), no problem there either.<br>><br>> But u1 isn't at all the same as u2, so hash(u2) can be the same as<br>> hash(u1) only by (unlikely) accident.  hash(u2) is off in a world of<br>> its own.  Therefore hash(dt2) can be the same as hash(u2) only by (the<br>> same unlikely) accident, despite that dt2 == u2.<br>><br>> So, in all, __hash__ forcing fold=0 at the start hides the problem for<br>> ambiguous times in the same zone, but doesn't really touch the problem<br>> for cross-zone equivalent spellings of such times (not even if one of<br>> the zones is UTC, which is likely the most important case).<br>><br>> One way to fix that is to have datetime.__hash__() _always_ return, say, 0 ;-)</div></div>