Chris Muller uploaded a new version of Chronology-Core to project The Inbox:
http://source.squeak.org/inbox/Chronology-Core-cmm.13.mcz ==================== Summary ==================== Name: Chronology-Core-cmm.13 Author: cmm Time: 17 October 2018, 4:04:08.832696 pm UUID: 60718249-84a8-4dc2-aa94-4ba5c8e5addc Ancestors: Chronology-Core-tcj.12 Fix DateAndTime today asDate = Date today even when not in GMT. =============== Diff against Chronology-Core-tcj.12 =============== Item was changed: ----- Method: Timespan>>= (in category 'ansi protocol') ----- = comparand ^ self class = comparand class + and: [((self noTimezone and: [comparand noTimezone]) - and: [((self noTimezone or: [ comparand noTimezone ]) ifTrue: [ self start hasEqualTicks: comparand start ] ifFalse: [ self start = comparand start ]) and: [ self duration = comparand duration ] ] .! |
If I am reading this right, it says that we can test for
"self start = comparand start" if and only if both of the two Timespans have no timezone information, otherwise we need to use #hasEqualTicks: to compare the two start values for the two durations. I'm not sure if there is an optimization available for the case of the two Timespans both having timezone information, but aside from that the change looks right to me. Dave On Wed, Oct 17, 2018 at 09:04:29PM +0000, [hidden email] wrote: > Chris Muller uploaded a new version of Chronology-Core to project The Inbox: > http://source.squeak.org/inbox/Chronology-Core-cmm.13.mcz > > ==================== Summary ==================== > > Name: Chronology-Core-cmm.13 > Author: cmm > Time: 17 October 2018, 4:04:08.832696 pm > UUID: 60718249-84a8-4dc2-aa94-4ba5c8e5addc > Ancestors: Chronology-Core-tcj.12 > > Fix DateAndTime today asDate = Date today even when not in GMT. > > =============== Diff against Chronology-Core-tcj.12 =============== > > Item was changed: > ----- Method: Timespan>>= (in category 'ansi protocol') ----- > = comparand > ^ self class = comparand class > + and: [((self noTimezone and: [comparand noTimezone]) > - and: [((self noTimezone or: [ comparand noTimezone ]) > ifTrue: [ self start hasEqualTicks: comparand start ] > ifFalse: [ self start = comparand start ]) > and: [ self duration = comparand duration ] ] > .! > > |
I'm not so sure. at the very least you will need to change the comment because it specifically states this isn't what should be done. when I get back from the water polo game I'll give a better argument (or retract my statement). On Wed, Oct 17, 2018, 17:43 David T. Lewis <[hidden email]> wrote: If I am reading this right, it says that we can test for |
In reply to this post by David T. Lewis
> I'm not sure if there is an optimization available for the case of the
> two Timespans both having timezone information, but aside from that > the change looks right to me. Yes, we should do that since we can. Chronology-Core-cmm.14.mcz Chris, I checked out the class comment and even though I thought it seemed correct, it is probably not the best place to mention that particular optimization detail. |
In reply to this post by cbc
Ok, the comments that I remembered where in the Timespan>>defaultOffset that described the behaviour that #= was exhibiting. That is, it is intentional - wheither right or not, it is intentional, and this comment needs to change when we change #= or #hash. "Timespans created in the context of an offset will start in that offset. When no context is available, the defaultOffset for Timespans must be nil. For example, two ways to make a Date for today: Date today. 'start is midnight without offset. Will compare successfully to other Date today results.' DateAndTime now asDate. 'In this case, the start is midnight of the local time-zone. It can only compare equally to Dates of its time-zone or Dates without timezone.'" That last part - "[DateAndTime now asDate] can only compare equally to Date of its time-zone or Dates without timezone." This change makes the last part of that sentence incorrect. Summary of below - I agree with this change after thinking about it for a while. I do think that the comment above needs to change. Having read Richard's email (referenced by David), I would also like a Date to just be a Date, and you can compare any Date to another Date for the same day and they are the same. At the same time, I would like (and need to know) if a timestamp in one part of the world occurred on a specific date in another part of the world - and with just a Magnitude Date, this is hard (that is, I have to convert the timestamp to the other part of the world, stored somewhere separately from Date but linked to it, and then see if it is still the same Date. Much easier with Dates the way they are). My first inclination to fix this is that any date should compare to any other date if the date part of the start is the same - basically, ignore the offset and just check that the ticks are the same. (For Dates, we could also ignore the duration - it is a DAY, not a random duration, after all. But if we did this, we'd have to verify both are Date class - so skip that idea.) The problem with this is that two days starting in different parts of the world may not overlap a lot, and if we care about the actual day duration, that would not be nice. After validating a few more things: Date today start offset "0:00:00:00" '2018-10-07' asDate start offset "0:00:00:00" DateAndTime now asDate start offset "-0:07:00:00" '2018-10-17 00:00:00' asDate start offset "0:00:00:00" I think that the change is about right. I'll just have to live with Dates built off of times from different parts of the world are, in fact, different Dates. If I want a 'Date' that works like the older Smalltalks, I can craft a very similar one by nil'ing out the offset of any Date. This is also a better answer than just delegating the time comparison to start (DateAndTime), the offset gets weird in that case. For example: I do find it interesting that Timespan's with not offset, when asked the offset, return an offset that looks like UTC, though. DateAndTime now makeUTC asDate = Date today "true" DateAndTime now makeUTC asDate hash = Date today hash "true" ======== Once this (or an equivalent fix) is in, I'm going to take a shot at fixing DateAndTime>>= - it is doing up to 3 #isKindOf: comparisons (!) in that method. Crazy. Thanks, -cbc On Wed, Oct 17, 2018 at 6:26 PM Chris Cunningham <[hidden email]> wrote:
|
Apologies in advance for going off topic and having a bit of fun with this :-)
I have claimed without supporting evidence that UTCDateAndTime should make date and time easier to understand and test. This was my primary motivation for doing it. So let me now offer the current discussion as evidence in support of my claim. In order to understand and discuss this discussion, you need to be able to agree on what it means for two DateAndTime instances to compare as equal (and yes of course the #hash needs to align with #=). So let's look at DateAndTime>>= and see. DateAndTime>>= aDateAndTimeOrTimeStamp self == aDateAndTimeOrTimeStamp ifTrue: [ ^ true ]. ((aDateAndTimeOrTimeStamp isKindOf: self class) or: [aDateAndTimeOrTimeStamp isKindOf: DateAndTime orOf: TimeStamp]) ifFalse: [ ^ false ]. ^ self offset = aDateAndTimeOrTimeStamp offset ifTrue: [ self hasEqualTicks: aDateAndTimeOrTimeStamp ] ifFalse: [ self asUTC hasEqualTicks: aDateAndTimeOrTimeStamp asUTC ] In order to understand this, I need to know what #asUTC means. DateAndTime>>asUTC ^ self offset isZero ifTrue: [self] ifFalse: [self utcOffset: 0] It looks as though it answers a possibly modified version of itself, depending on whether the current timezone offset is zero. But let's check and see if that setter method is actually a setter. DateAndTime>>utcOffset: anOffset "Answer a <DateAndTime> equivalent to the receiver but offset from UTC by anOffset" | equiv | equiv := self + (anOffset asDuration - self offset). ^ equiv ticks: (equiv ticks) offset: anOffset asDuration; yourself OK, it is not a setter, it answers a new instance that is equivalent but has a different offset from UTC. And two equivalent instances would probably compare as equal, right? Wrong. So they are equivalent but not equal, whatever that might happen to mean: dt := DateAndTime now. dt = (dt deepCopy offset: 0). "==> false" So going back to the original question of whether the two DateAndTime instances were equal, we have now decided that under certain conditions we need to compare two equivalent copies of the original two objects, and find out if they have equal ticks. That should be pretty simple at this point. Since we don't really know what an equivalent copy is, it probably does not matter if we understand what equal ticks means, just as long as the tests are green. But what the heck, we're into it this deep, so we might as well see what it means for two instances to have equal ticks. DateAndTime>>asEqualTicks: aDateAndTime ^ (jdn = aDateAndTime julianDayNumber) and: [ (seconds = aDateAndTime secondsSinceMidnight) and: [ nanos = aDateAndTime nanoSecond ] ] Well that is actually quite straightforward. It says that if the instance variables other than #offset are the same, then the instances have equal ticks. We still may not be entirely sure if two instances with equal ticks and different offsets refer to the same actual instant in time, or if maybe they refer to the same time as it appear in a local calendar. But given that the comparison seems to be excluding the #offset, we might be inclined to think that the ticks must be representing the same actual point in time. dt1 := DateAndTime now. dt2:= dt1 copy offset: 0. dt1 ticks = dt2 ticks. "==> true" dt1 = dt2. "==> false" OK, so I guess that was not so obvious after all. The ticks do not directly represent the point in time. And the only other thing that could possibly make sense is if they represent the local time representation, so that must be it. This might seem crazy, but it actually makes sense if you remember that early Squeak implementations did everything (both in the VM and the image) in local time with no awareness of timezones. So now we can understand the comparison. If we want to compare two instances of DateAndTime that were created in different timezones, we make equivalent copies of both of them, then compare their ticks as if they had been created in the same time zone. From this we should be able to work our way back to a definition of "equivalent" DateAndTime instances. We'll leave that as an exercise for the reader, but to return briefly to the original topic of this message, consider for a moment the definition of equal DateAndTime instances in UTCDateAndTime: DateAndTime>>= aDateAndTimeOrTimeStamp "Equal if the absolute time values match, regardless of local time transform" self == aDateAndTimeOrTimeStamp ifTrue: [ ^ true ]. ^aDateAndTimeOrTimeStamp species == DateAndTime and: [ utcMicroseconds = aDateAndTimeOrTimeStamp utcMicroseconds ] You may or may not agree with how this is defined, but at least you can read it and understand the meaning. Two instances are equal if their magnitudes are equal, regardless of local timezone. I cannot claim that this implementation is any more or less correct than what was discussed above, but I am quite confident in claiming that you less likely to get a headache from trying to read it. <EOM> ;-) Dave On Wed, Oct 17, 2018 at 08:36:08PM -0700, Chris Cunningham wrote: > Ok, the comments that I remembered where in the Timespan>>defaultOffset > that described the behaviour that #= was exhibiting. That is, it is > intentional - wheither right or not, it is intentional, and this comment > needs to change when we change #= or #hash. > > "Timespans created in the context of an offset will start in that offset. > When no context is available, the defaultOffset for Timespans must be nil. > For example, two ways to make a Date for today: > Date today. 'start is midnight without offset. Will compare successfully > to other Date today results.' > DateAndTime now asDate. 'In this case, the start is midnight of the local > time-zone. It can only compare equally to Dates of its time-zone or Dates > without timezone.'" > > That last part - "[DateAndTime now asDate] can only compare equally to Date > of its time-zone or Dates without timezone." This change makes the last > part of that sentence incorrect. > > Summary of below - I agree with this change after thinking about it for a > while. I do think that the comment above needs to change. > > Having read Richard's email (referenced by David), I would also like a Date > to just be a Date, and you can compare any Date to another Date for the > same day and they are the same. At the same time, I would like (and need > to know) if a timestamp in one part of the world occurred on a specific > date in another part of the world - and with just a Magnitude Date, this is > hard (that is, I have to convert the timestamp to the other part of the > world, stored somewhere separately from Date but linked to it, and then see > if it is still the same Date. Much easier with Dates the way they are). > > My first inclination to fix this is that any date should compare to any > other date if the date part of the start is the same - basically, ignore > the offset and just check that the ticks are the same. (For Dates, we > could also ignore the duration - it is a DAY, not a random duration, after > all. But if we did this, we'd have to verify both are Date class - so skip > that idea.) > > The problem with this is that two days starting in different parts of the > world may not overlap a lot, and if we care about the actual day duration, > that would not be nice. > > After validating a few more things: > Date today start offset "0:00:00:00" > '2018-10-07' asDate start offset "0:00:00:00" > DateAndTime now asDate start offset "-0:07:00:00" > '2018-10-17 00:00:00' asDate start offset "0:00:00:00" > I think that the change is about right. I'll just have to live with Dates > built off of times from different parts of the world are, in fact, > different Dates. If I want a 'Date' that works like the older Smalltalks, > I can craft a very similar one by nil'ing out the offset of any Date. > > This is also a better answer than just delegating the time comparison to > start (DateAndTime), the offset gets weird in that case. For example: > > I do find it interesting that Timespan's with not offset, when asked the > offset, return an offset that looks like UTC, though. > DateAndTime now makeUTC asDate = Date today "true" > DateAndTime now makeUTC asDate hash = Date today hash "true" > > ======== > Once this (or an equivalent fix) is in, I'm going to take a shot at fixing > DateAndTime>>= - it is doing up to 3 #isKindOf: comparisons (!) in that > method. Crazy. > > Thanks, > -cbc > > On Wed, Oct 17, 2018 at 6:26 PM Chris Cunningham <[hidden email]> > wrote: > > > I'm not so sure. at the very least you will need to change the comment > > because it specifically states this isn't what should be done. > > > > when I get back from the water polo game I'll give a better argument (or > > retract my statement). > > > > On Wed, Oct 17, 2018, 17:43 David T. Lewis <[hidden email]> wrote: > > > >> If I am reading this right, it says that we can test for > >> "self start = comparand start" if and only if both of the two Timespans > >> have no timezone information, otherwise we need to use #hasEqualTicks: > >> to compare the two start values for the two durations. > >> > >> I'm not sure if there is an optimization available for the case of the > >> two Timespans both having timezone information, but aside from that > >> the change looks right to me. > >> > >> Dave > >> > >> On Wed, Oct 17, 2018 at 09:04:29PM +0000, [hidden email] > >> wrote: > >> > Chris Muller uploaded a new version of Chronology-Core to project The > >> Inbox: > >> > http://source.squeak.org/inbox/Chronology-Core-cmm.13.mcz > >> > > >> > ==================== Summary ==================== > >> > > >> > Name: Chronology-Core-cmm.13 > >> > Author: cmm > >> > Time: 17 October 2018, 4:04:08.832696 pm > >> > UUID: 60718249-84a8-4dc2-aa94-4ba5c8e5addc > >> > Ancestors: Chronology-Core-tcj.12 > >> > > >> > Fix DateAndTime today asDate = Date today even when not in GMT. > >> > > >> > =============== Diff against Chronology-Core-tcj.12 =============== > >> > > >> > Item was changed: > >> > ----- Method: Timespan>>= (in category 'ansi protocol') ----- > >> > = comparand > >> > ^ self class = comparand class > >> > + and: [((self noTimezone and: [comparand noTimezone]) > >> > - and: [((self noTimezone or: [ comparand noTimezone ]) > >> > ifTrue: [ self start hasEqualTicks: comparand > >> start ] > >> > ifFalse: [ self start = comparand start ]) > >> > and: [ self duration = comparand duration ] ] > >> > .! > >> > > >> > > >> > >> > |
Free forum by Nabble | Edit this page |