Discussion:
[squeak-dev] The Inbox: Chronology-Core-cmm.13.mcz
c***@source.squeak.org
0000-10-19 02:55:05 UTC
Permalink
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 durat
David T. Lewis
2018-10-18 00:43:32 UTC
Permalink
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
Post by c***@source.squeak.org
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 ===============
----- 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
Chris Cunningham
2018-10-18 01:26:12 UTC
Permalink
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).
Post by David T. Lewis
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
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
Post by c***@source.squeak.org
Chris Muller uploaded a new version of Chronology-Core to project The
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 ===============
----- 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 ]
Post by c***@source.squeak.org
ifFalse: [ self start = comparand start ])
and: [ self duration = comparand duration ] ]
.!
Chris Cunningham
2018-10-18 03:36:08 UTC
Permalink
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
Post by Chris Cunningham
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).
Post by David T. Lewis
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
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
Post by c***@source.squeak.org
Chris Muller uploaded a new version of Chronology-Core to project The
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 ===============
----- 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 ]
Post by c***@source.squeak.org
ifFalse: [ self start = comparand start ])
and: [ self duration = comparand duration ] ]
.!
David T. Lewis
2018-10-19 12:37:29 UTC
Permalink
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
Post by Chris Cunningham
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.
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.
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
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
Post by Chris Cunningham
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).
Post by David T. Lewis
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
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
Post by c***@source.squeak.org
Chris Muller uploaded a new version of Chronology-Core to project The
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 ===============
----- 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 ]
Post by c***@source.squeak.org
ifFalse: [ self start = comparand start ])
and: [ self duration = comparand duratio
Chris Muller
2018-10-18 03:12:22 UTC
Permalink
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
Loading...