date-time icon indicating copy to clipboard operation
date-time copied to clipboard

Introduce a ZonedDateTimeRange

Open tigitz opened this issue 3 years ago • 10 comments

Hello,

Interval is a range of two instants and LocalDateRange is not Zoned nor include time.

However for cases where you need to schedule an event in the future (e.g. from 21/02/2022 10h00 Europe/Paris to 21/02/2022 12h00 Europe/Paris) you want to keep the timezone to prevent timezone shifts.

WDYT ?

tigitz avatar Apr 01 '22 08:04 tigitz

Should the end of this range be inclusive like in LoccalDateRange, or exclusive like in Interval?

solodkiy avatar Apr 10 '22 09:04 solodkiy

Related to https://github.com/brick/date-time/issues/34

solodkiy avatar Apr 10 '22 09:04 solodkiy

Should the end of this range be inclusive like in LoccalDateRange, or exclusive like in Interval?

I don't have a strong opinion about this. Ideally it would stay consistent with other range concepts in the lib. Maybe @BenMorel could explain its reasoning on why it's different in the first place.

tigitz avatar Apr 24 '22 19:04 tigitz

I think the reason is that concept of inclusive duration is quite complicated when units become small.

$a = ZonedDateTime::parse('2020-01-01T15:16:20');
$range = crateRange($a, $a);
$duration = $range->getDuration();
var_dump($duration);

How long $duration should be in this case?

solodkiy avatar Apr 24 '22 19:04 solodkiy

I think the reason is that concept of inclusive duration is quite complicated when units become small.

$a = ZonedDateTime::parse('2020-01-01T15:16:20');
$range = crateRange($a, $a);
$duration = $range->getDuration();
var_dump($duration);

How long $duration should be in this case?

Any reason it shouldn't be 0 ?

tigitz avatar Apr 24 '22 20:04 tigitz

Any reason it shouldn't be 0 ?

Inclusion :) For example same code but with LocalDateRange give you duration of one day. In this case difference between inclusive and exclusive is in inclusive version 2020-01-01T15:16:20 is part of the range (and duration cannot be zero) and in exclusive 2020-01-01T15:16:20 is not in range (duration is zero).

solodkiy avatar Apr 25 '22 05:04 solodkiy

After some thinking I consider that duration in case when start = end should be 1 nanosec.

$r = crateRange(
    '2020-01-01T00:00:00.000000000Z', // day min
    '2020-01-01T23:59:59.999999999Z' // day max
); 
$r->getDuration(); // = 86400.0 sec (86399.999999999 + 0.000000001)

But working in this type of scale is quite tricky when you put this dates in query like this: WHERE date >= :from AND date <= :to because of rounding in most databases starts after 6 sign.

Screen Shot 2022-04-25 at 09 59 46

Use exclusion type like Interval leads to different query WHERE date >= :from AND date < :to wich looks more safe in my opinion.

solodkiy avatar Apr 25 '22 06:04 solodkiy

I don't understand your point sorry, ZonedDateTime::parse('2020-01-01T15:16:20') is not even parsable. There's no inclusiveness or exclusiveness to think about actually, you just subtract both lowest unit which are nanos in this case and you get a Duration in nanos.

Since ZonedDateTime are basically Instant it would just rely on the Interval class:

class ZonedDateTimeRange {
    private function __construct(private ZonedDateTime $start, private ZonedDateTime $end)
    {
        
    }

    public static function of(ZonedDateTime $start, ZonedDateTime $end)
    {
        return new self($start, $end);
    }

    public function getDuration(): Duration
    {
        return (new Interval($this->start->getInstant(), $this->end->getInstant()))->getDuration();
    }
}
$zd = ZonedDateTime::parse('2020-01-01T15:16:20+01:00');
$zd2 = ZonedDateTime::parse('2020-01-01T15:16:20+01:00');

$duration = ZonedDateTimeRange::of($zd , $zd2);
echo $duration->getDuration()->getTotalNanos(); // 0

tigitz avatar Apr 25 '22 07:04 tigitz

We've developed a package on top of brick/date-time which includes a LocalDateTimeInterval object using an exclusive end. We use it quite extensively in our time-management project, and it works like a charm. We have not developed the Zoned* version, as we currently only deal with local dates/times. Maybe some implementation details can be of interest ?

Here's the latest version we have in our project, which has not yet been made available publicly (not yet had the time to update the public package...) : https://gist.github.com/gnutix/d730da3f5e3a9beedd62571418f62194 (class and tests, updated on 2024-03-23).

And here's the (outdated) version available in the public repository : Class: https://github.com/gammadia/date-time-extra/blob/develop/src/LocalDateTimeInterval.php Tests: https://github.com/gammadia/date-time-extra/blob/develop/tests/LocalDateTimeIntervalTest.php

Some methods worth having a look at (in the latest version) are contains, sees and intersects, which offer different ways of dealing with empty intervals (2022-07-03T15:50/2022-07-03T15:50). It also includes ALLEN-relationship methods and various helpers (like cast, containerOf, collapse, expand, ...).

gnutix avatar Jul 03 '22 13:07 gnutix

For those interested, I've updated the gist with the latest version of our code. A proper package will come, someday...

gnutix avatar Mar 23 '24 09:03 gnutix