The Inbox: Chronology-Core-cmm.13.mcz

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

The Inbox: Chronology-Core-cmm.13.mcz

commits-2
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 ] ]
  .!


Reply | Threaded
Open this post in threaded view
|

Re: The Inbox: Chronology-Core-cmm.13.mcz

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
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 ] ]
>   .!
>
>

cbc
Reply | Threaded
Open this post in threaded view
|

Re: The Inbox: Chronology-Core-cmm.13.mcz

cbc
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 ] ]
>   .!
>
>



Reply | Threaded
Open this post in threaded view
|

Re: The Inbox: Chronology-Core-cmm.13.mcz

Chris Muller-3
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.

cbc
Reply | Threaded
Open this post in threaded view
|

Re: The Inbox: Chronology-Core-cmm.13.mcz

cbc
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:
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 ] ]
>   .!
>
>



Reply | Threaded
Open this post in threaded view
|

Why simplifying DateAndTime matters (was: The Inbox: Chronology-Core-cmm.13.mcz)

David T. Lewis
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 ] ]
> >> >   .!
> >> >
> >> >
> >>
> >>

>