luxon icon indicating copy to clipboard operation
luxon copied to clipboard

hasSame fastpath for DateTimes in the same zone

Open farnoy opened this issue 4 years ago • 3 comments

Hi,

I ran a quick benchmark (hopefully valid) after I noticed my app spending a lot of time in hasSame. If I understand it correctly, the only difference between ts_a[unit] === ts_b[unit] and ts_a.hasSame(ts_b, unit) is behavior with respect to timezones?

> a = luxon.DateTime.local()
DateTime {ts: 1578612479954, _zone: LocalZone, loc: Locale, invalid: null, weekData: null, …}
> a.toISO()
"2020-01-10T00:27:59.954+01:00"
> start = window.performance.now()
> for (let ix = 0; ix < 100000; ix++) {
>   a.day === a.day;
> }
> console.log(window.performance.now() - start)
VM1059:5 2.7500000142026693
undefined
> start = window.performance.now()
> for (let ix = 0; ix < 100000; ix++) {
>   a.hasSame(a, 'day');
> }
> console.log(window.performance.now() - start)
VM1099:5 1911.0750000108965
undefined

Is it possible to speed up hasSame in the case of equal timezones?

farnoy avatar Jan 09 '20 23:01 farnoy

yeah, that's probably right

icambron avatar Jan 10 '20 03:01 icambron

According to the documentation it's not fully correct: "Higher-order units must also be identical for this function to return true. Note that time zones are ignored in this comparison". Most of the work then supposedly lies in checking the higher order units.

mees- avatar May 05 '22 00:05 mees-

@mees- this a bit of a complicated one. For starters, the method's implementation has significantly changed since this was reported.

This is the current implementation of hasSame:

  hasSame(otherDateTime, unit) {
    if (!this.isValid) return false;

    const inputMs = otherDateTime.valueOf();
    const adjustedToZone = this.setZone(otherDateTime.zone, { keepLocalTime: true });
    return adjustedToZone.startOf(unit) <= inputMs && inputMs <= adjustedToZone.endOf(unit);
  }

I suspect this is now as fast for things in the same time zone as not, since the setZone will noop for that case.

The question for perf here is really whether we can do better than startOf and endOf and just compare the units directly, like,

adjustedToZone.get(unit) == this.get(unit)

I think we can but I don't remember quite why we didn't in the first place...

icambron avatar May 06 '22 19:05 icambron

The difference between a.hasSame(b, unit) and a.get(unit) == b.get(unit) is that hasSame also checks higher-order units implicitly. 2022-04-02 and 2021-04-09 have the same month when you just compare the month index (a.get('month') == b.get('month')), but they are not in the same calendar month (a.hasSame(b, 'month') would be false in this case).

One possible optimization might be to reuse startOf and do something like:

const adjustedToZoneStart = adjustedToZone.startOf(unit);
const adjustedToZoneEnd = adjustedToZoneStart.plus({[unit]: 1});
return adjustedToZoneStart <= inputMs && inputMs < adjustedToZoneEnd

But it would have to be investigated whether that yields any meaningful performance improvement.

diesieben07 avatar Feb 28 '23 21:02 diesieben07

@diesieben07 ah, right

icambron avatar Mar 02 '23 18:03 icambron