brick / date-time

Date and time library for PHP
MIT License
323 stars 29 forks source link

Introduce a ZonedDateTimeRange #45

Open tigitz opened 2 years ago

tigitz commented 2 years ago

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 ?

solodkiy commented 2 years ago

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

solodkiy commented 2 years ago

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

tigitz commented 2 years ago

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.

solodkiy commented 2 years ago

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?

tigitz commented 2 years ago

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 ?

solodkiy commented 2 years ago

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 commented 2 years ago

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.

tigitz commented 2 years ago

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
gnutix commented 2 years ago

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 commented 4 months ago

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