GithubHelp home page GithubHelp logo

thephpleague / period Goto Github PK

View Code? Open in Web Editor NEW
714.0 19.0 45.0 1.57 MB

PHP's time range API

Home Page: https://period.thephpleague.com

License: MIT License

PHP 100.00%
php interval value-object daterange sequence collection datetime date time timeline

period's Introduction

Period

Author Latest Version Software License Build Total Downloads

Period is PHP's missing time range API. This package covers all basic operations regarding time ranges.

Highlights

  • Represents Interval and Bounds as immutable value objects or enumeration
  • Exposes named constructors to ease instantiation
  • Covers all basic manipulations related to time range
  • Enables working with simple or complex time ranges logic
  • Fully documented
  • Framework-agnostic

Documentation

Full documentation can be found at period.thephpleague.com.

System Requirements

You need PHP >= 8.1.0 but the latest stable version of PHP is recommended.

Install

Install Period using Composer.

$ composer require league/period

or download the library and:

  • use any other PSR-4 compatible autoloader.
  • use the bundle autoloader script as shown below:
require 'path/to/period/repo/autoload.php';

use League\Period\Datepoint;

Datepoint::fromDateString('2012-05-23')->month()->toIso80000('Y-m-d');
//returns [2012-05-01, 2012-06-01)

where path/to/period/repo represents the path where the library was extracted.

Testing

Period has:

  • a PHPUnit test suite
  • a code analysis compliance test suite using PHPStan.
  • a coding style compliance test suite using PHP CS Fixer.

To run the tests, run the following command from the project folder.

$ composer test

Contributing

Contributions are welcome and will be fully credited. Please see CONTRIBUTING and CONDUCT for details.

Security

If you discover any security related issues, please email [email protected] instead of using the issue tracker.

Changelog

Please see CHANGELOG for more information on what has changed recently.

Credits

License

The MIT License (MIT). Please see LICENSE for more information.

period's People

Contributors

adamnicholson avatar bcrowe avatar ben-synapse avatar bibendus83 avatar bpolaszek avatar dritter avatar elnoro avatar grahamcampbell avatar irazasyed avatar jamesdb avatar jamesking56 avatar jeromegamez avatar kekenec avatar kraftner avatar maogou avatar notfloran avatar ntzm avatar nyamsprod avatar philsturgeon avatar pryazhnikov avatar rarst avatar rdohms avatar svenluijten avatar willtj avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

period's Issues

Period::split() return Sequence

Feature Request

Q A
New Feature yes
BC Break yes

Proposal

Would be nice if Period::split() returned a Sequence instead of an iterable.

With a Sequence, we have all the features from an iterable and more.

Splitting a day period by 10 minutes on daylight savings roll-back day throws LogicException

Bug Report

Information Description
Version 4.0.0
PHP version 7.1.16
OS Platform MacOS High Sierra 10.13.6

Summary

If you have a period that spans the day of November 4, 2018 in a timezone where daylight savings is in effect, if you split that period by 10 or 30 minutes, it throws a Logic Exception with the message: "The ending datepoint must be greater or equal to the starting datepoint".

Standalone code, or other way to reproduce the problem

I pulled the repo and wrote this test to reproduce the bug.

public function testSplitDaylightSavingsDayIntoHours()
{
    date_default_timezone_set('Canada/Central');
    $period = new Period(new DateTime('2018-11-04 00:00:00.000000'), new DateTime('2018-11-05 00:00:00.000000'));
    $splits = $period->split(new DateInterval('PT30M'));
    foreach ($splits as $inner_period) {
        self::assertNotNull($inner_period);
    }
}

Expected result

To not throw an exception when daylight savings is the cause $startDate > $endDate.

Actual result

Throws an exception.

Opposite to diff

Hey! This is a really cool package!

I was reading your API for comparison and found diff(). Diff it is awesome!! And with diff, I thought that there must be some opposite of diff? So diff() returns the period that was before, and the period that was after. Wouldnt it make sence to have a function for the opposite? So the function would return the (math description union) time of the period that is overlapped?

I might just be a bit dumb here but I couldn't find any method that gave me that value so I just wanted to suggest to add it in.

Or do you think it is too easy to accomplish by the user when you supply with diff()?

'Final' declaration prevents mocking.

Hi,
I really want to use this class in a new project. However, I am prevented from doing so, as you have declared the class as final, so I can't mock it for testing with PHPUnit. I'm curious as to why you have done this and whether you would be prepared to remove the final declaration?

Otherwise, a very nice class! I am replacing something similar I had created myself, but yours is much better thought out. Well done!

Add feature for containsLeapDay()

Would be handy to be able to call containsLeapDay() on a Period object when dealing with periods that might contain leap days.

Specifically at the moment I want to determine if the period is a year long or not, there may be another easier way to do this which I've missed...

Named constructors with strings or date object ?

Hello and thanks for your nice project.

I have an array of dates like

$dates = array( '2012-04-01 08:30:00', '2015-12-28 10:25:00', '2015-11-17 03:17:00' ...);

Basically I would need to group all these dates by "regular" periods (say months).

Is there any way to do something like this :

Period::createFromMonth('2015-11');

Edit : I know I can parse the date string but just ask for sake of simplicity :)

Thanks !

Should we add a PeriodInterface or not ?

I'm currently developping the new major release of League\Period.
What's new in this new release apart from deprecations and removal of old PHP versions support is the addition of a League\Period\Collection class and a League\Period\PeriodInterface interface.

The main issue is do we really need a PeriodInterface ?

Pros

  • This interface is uses by the Period class and the Collection which let's people implements their own Period class if they want knowing the the Collection class will always works for them.
  • This makes making the Period class a final class easier as you can rely on a interface

Cons

  • The interface does not contains all the methods of the Period class because:
    • most of the Period modifier methods can be rebuilt using PeriodInterface::startingOn and PeriodInterface::endingOn. for instance PeriodInterface::withDurationBeforeEnd is built using PeriodInterface::startingOn so you don't need it in the interface;
    • all the comparisons methods uses PeriodInterface::compareDuration so their is no need IMHO to add a PeriodInterface::greaterThan method in the interface for instance.
  • Period most valuable methods are its named constructors which by definition won't be in an interface
  • While Period implements the interface, the class also uses the parameter widening concept to allow more flexibility on users input without having to add more methods. In other words, the interface only exposes a fraction of what Period can do. This means that the Collection object is restricted to a set of method for better interoperability.

TL;DR: do we really need the PeriodInterface at all ?

Thoughts @shadowhand @frankdejonge @bpolaszek ?

Missing Period in Sequence intersections.

Bug Report

Information Description
Version 4.9.0
PHP version 7.3
OS Platform Linux

Summary

Trying to get the intersections between a Period overlapping two other only return the first one.

Standalone code, or other way to reproduce the problem

The following test doesn't pass :

    /**
     * Intersections test 3.
     *
     *        [-------------------------)
     *             [-----)
     *                   [------)
     *
     *                 =
     *
     *             [-----)[-----)
     */
    public function testGetIntersections3(): void
    {
        $sequence = new Sequence(
            new Period('2020-01-09 22:00:00', '2020-01-10 00:00:00'),
            new Period('2020-01-10 00:00:00', '2020-01-10 06:00:00'),
            new Period('2020-01-09 18:25:00', '2020-01-10 07:10:05')
        );
        $intersections = $sequence->getIntersections();
        self::assertCount(2, $intersections);
        self::assertSame('[2020-01-09 22:00:00, 2020-01-10 00:00:00)', $intersections->get(0)->format('Y-m-d H:i:s'));
        self::assertSame('[2020-01-10 00:00:00, 2020-01-10 07:10:05)', $intersections->get(1)->format('Y-m-d H:i:s'));
    }

This one is OK :

    /**
     * Intersections test 4.
     *
     *        [--------------)
     *             [-----)
     *                   [------)
     *
     *                 =
     *
     *             [-----)[---)
     */
    public function testGetIntersections4(): void
    {
        $sequence = new Sequence(
            new Period('2020-01-09 22:00:00', '2020-01-10 00:00:00'),
            new Period('2020-01-10 00:00:00', '2020-01-10 06:00:00'),
            new Period('2020-01-09 18:25:00', '2020-01-10 01:10:05')
        );
        $intersections = $sequence->getIntersections();
        self::assertCount(2, $intersections);
        self::assertSame('[2020-01-09 22:00:00, 2020-01-10 00:00:00)', $intersections->get(0)->format('Y-m-d H:i:s'));
        self::assertSame('[2020-01-10 00:00:00, 2020-01-10 01:10:05)', $intersections->get(1)->format('Y-m-d H:i:s'));
    }

Expected result

In testGetIntersections3(), $sequence->getIntersections() should return a Sequence with two Period equals to the two smaller ones.

Actual result

Only the first smaller Period is part of the returned Sequence : [2020-01-09 22:00:00, 2020-01-10 00:00:00)

Period without end

Feature Request

Any ideas or plans to implement compare periods without end ?

Q A
New Feature yes
BC Break no

Proposal

$period1 = Period::after('2014-01-01');
$period2 = Period::after('2014-10-03 08:00:00', 3600);

$period1->contains($period2); //return true

Merge Bakame/Period-Visualizer codebase to the Period repo

Feature Request

Q A
New Feature yes
BC Break yes

Proposal

An independent visualizer package was created to help visualize Periods and Sequences object. This package is heavily used on development to test the main package. As this package never hit the 1.0.0 milestone I think it's easier to just merge it's codebase to the Period package.

The only drawback is that it's inclusion requires bumping the min. PHP version supported to 7.2. Since 7.1 is already eol this should not be a problem if the library is loaded through composer.

Possible to determine the end of a duration in a sequence?

Q A
Version 4.11.0

Question

Is there a way for me to calculate the end of a duration applied against a sequence? Better still, calculate the end of a duration from a datepoint in a sequence?

For example:

$sequence = new Sequence(
    new Period('2020-10-30 16:39:12', '2020-10-30 17:00:00'),
    new Period('2020-11-02 09:00:00', '2020-11-02 17:00:00'),
    new Period('2020-11-03 09:00:00', '2020-11-03 17:00:00')
);

$duration = Duration::create(new DateInterval('PT40M'));

echo $sequence->magic($duration) // datepoint 2020-11-02 09:20:48

echo $sequence->betterMagicBit(
    Datepoint::create(new DateTimeImmutable('2020-11-02 16:39:12')),
    $duration
);  // returns datepoint('2020-11-03 09:20:12')

echo $sequence->betterMagicBit(
    Datepoint::create(new DateTimeImmutable('2020-11-02 18:30:00')),
    $duration
);  // returns datepoint('2020-11-03 09:40:00')

I could iterate through the periods in the sequence, subtracting the duration until it's exhausted, but having used the library a few times I can't help think that there's a simpler way that's eluding me.

Thanks.

Naming suggests mutability

setStart() returns a new Period, as it should, but the name setStart() suggests that it will in fact modify the Period. In my experience, this can be confusing to people who are used to mutable objects. I personally prefer to use a different naming that expresses that it makes a new object. For example:

$newPeriod = $period->startingOn(...)

$newPeriod = $period->withDuration(...)

Difference between sequences

Hi everyone.
I noticed there is a diff() method in the Period class but it's missing in Sequence.
It would be nice to be able to substract a sequence of periods from another sequence.

Here is a use case example.
I have a sequence containing all the standard time presences of a worker in a month (ex. mon-fri from 8:00 to 12:00 and from 13:00 to 17:00).
My worker decides to take a half day permit and, later the same month, a full week of holydays.
In that case it would be nice to create a sequence of the absence days so I can substract them from the presences sequence and obtain only the real presences for that month.

How to get dates for same day every week

Issue summary

If my start date for example is 2016-09-15 19:00, which is a Thursday and I want to figure out the date for every Thursday on wards for the next upcoming N weeks.

If I want every Thursday 4 weeks from now. The dates I want returned are:
2016-09-22
2016-09-29
2016-10-06
2016-10-13

How would this be accomplished?

Thanks.

Calling `Sequence->get()` with an index of -1 doesn't work in 4.8+

Bug Report

Information Description
Version 4.8.0
PHP version 7.2.19
OS Platform Linux (Ubuntu 18.04)

Summary

According to the documentation, the get() method supports passing in a negative index from version 4.8 and above. When I do this however, I get InvalidIndex exception thrown with the message:

-1 is an invalid index in the current sequence

Standalone code, or other way to reproduce the problem

Create any Sequence (empty or not) and call ->get(-1).

Expected result

I'm expecting that the return value is the latest Period item in the Sequence as shown in the documentation:

https://period.thephpleague.com/4.0/sequence/collection/#sequenceget

Actual result

InvalidIndex exception is thrown citing that -1 is an invalid index.

final or not final

Issue summary

Period is a value object. as such until version 3.0 the Period class was marked as final. The final keyword was removed as a requested from @shadowhand link to the relevant discussion

I am in the process of wrapping up the code for a v4 version which will be PHP7.1+ only. One of the last question I face is should I revert this change and make Period final again ?

Any input from the community is welcomed @Ocramius , @mathiasverraes, @frankdejonge you are welcome to participate. I should note that I never felt the need to extend the Period class. But They are some use cases I may not be taking into account.

thanks in advance

Complexer period comparisons

Feature Request

Q A
New Feature yes
BC Break no

Proposal

I often find myself having to calculate the diffs or overlaps between multiple periods. This package only handles comparing two periods with each other though, so I made this: https://github.com/spatie/period

I wonder if there's interest in adding more complex date comparisons to this package? Here's an example of one of the things I want to do with it:

/*
 * A                   [====]
 * B                               [========]
 * C         [=====]
 * CURRENT      [========================]
 *
 * DIFF             [=]      [====]
 */

$a = Period::make('2018-01-05', '2018-01-10');
$b = Period::make('2018-01-15', '2018-03-01');
$c = Period::make('2017-01-01', '2018-01-02');

$current = Period::make('2018-01-01', '2018-01-31');

$diff = $current->diff($a, $b, $c);

To further clarify when these kind of comparison are useful: imagine an item that can be invoiced. The item has a price per day. Now you want to invoice this item to a customer for a given period: say January. If however, this customer was already invoiced for this item until the half of January, you only want 15 days to be invoiced instead of 31.

This is a simplified example of what we have to deal with in one of our projects, and I noticed other people also facing the same problems. I think it would be a good addition to this package.

Period objects overlaps only in one direction

Hello

In "overlaps" method you have such logic:

return $this->contains($period->start) || $this->contains($period->end);

I don't know if I understand "overlapping" correctly or the code is wrong.

For example:

  • if we have two dates in the same day - A: from 10 to 18 o'clock, and B: from 12 to 16, then A contains start (or end) of B and in result A overlaps B.
  • but if we check the same dates in reverse direction (if B overlaps A), then B doesn't contain start (or end) of A, and in result B doesn't overlap A.

Is it correct?

Number of (over)nights

How can the number of overnights inside an interval be found out?
%a returns 0 when the interval doesn't contain a whole day, but a night (00:00 AM) between.

Example code to reproduce the issue:

<?php
require 'vendor/autoload.php';
use League\Period\Period;

$period1 = new Period( new DateTime('2020-10-04 00:00', new DateTimeZone('UTC')), new DateTime('2020-12-18 00:00', new DateTimeZone('UTC')), Period::INCLUDE_ALL );
$period2 = new Period( new DateTime('2020-10-05 16:00', new DateTimeZone('UTC')), new DateTime('2020-10-06 12:00', new DateTimeZone('UTC')), Period::INCLUDE_ALL );
$diff    = $period2->intersect( $period1 );
$nights  = (int) $diff->getDateInterval()->format( '%a' );
var_dump( $nights ); // 0

sequence subtraction bug

Bug Report

Information Description
Version 4.9.0
PHP version 7.3

Summary

after substraction of sequences i have the following period:

object(League\Period\Sequence)#5214 (1) {
  ["intervals":"League\Period\Sequence":private]=>
  array(73) {
    [0]=>
    object(League\Period\Period)#5216 (3) {
      ["startDate":"League\Period\Period":private]=>
      object(League\Period\Datepoint)#5162 (3) {
        ["date"]=>
        string(26) "2020-01-21 00:00:00.000000"
        ["timezone_type"]=>
        int(3)
        ["timezone"]=>
        string(13) "Europe/Moscow"
      }
      ["endDate":"League\Period\Period":private]=>
      object(League\Period\Datepoint)#4092 (3) {
        ["date"]=>
        string(26) "2020-01-20 21:30:00.000000"
        ["timezone_type"]=>
        int(1)
        ["timezone"]=>
        string(6) "+00:00"
      }
      ["boundaryType":"League\Period\Period":private]=>
      string(2) "[]"
    }

It has the end date which is actually earlier than the start date.
Subtrahend sequence consists of overnight periods. And initial sequence is a single long period.

compare period with other period that only has a time

Hi,

I'm trying to figure out how the following problem might be solved with this library:
Assume I have a period like this:
$a =Period::createFromDuration('2014-11-30 03:00:00','2014-12-04 12:00:00');

now I have another period object, that would say $b = "every Monday to Friday between 09:00:00 and 18:00:00". I then want to intersect the two object to find out how much of $a is covered by $b.

I'm not sure how to approach this best. Thanks in advance for your help.

Period::gap()?

For cases where Period objects don't overlap, it may be useful to get the Period representing the gap between the two:

use League\Period\Period;

$period    = Period::createFromDuration(2012-01-01, '2 MONTHS');
$altPeriod = Period::createFromDuration(2012-06-15, '3 MONTHS');
if ( ! $period->overlaps($altPeriod)) {
    $newPeriod = $period->gap($altPeriod);
    // $newPeriod is a Period object representing the gap between $period and $altPeriod
}

(above example adapted from documentation for Period->intersect(), since this is essentially the inverse operation)

Does this feature make sense for this project?

Parse date period

Issue summary

I think it would be interesting if this library would permite parse from string eg: Period::parse("last week").

Inspired by: Carbon

Invalid examples in documentation. Misleading exception message for intersect()

     */
    public function intersect(Period $period)
    {
        if ($this->abuts($period)) {
            throw new LogicException('Both object should not abuts');
        }

        return new self(
            (1 === self::compareDate($period->startDate, $this->startDate)) ? $period->startDate : $this->startDate,
            (-1 === self::compareDate($period->endDate, $this->endDate)) ? $period->endDate : $this->endDate
        );
    }

Its as if its meant to swap start/end date automatically, but it doesn't work correctly because whether the ternary is true or false, its doing the same thing, rendering the conditional pointless. I can't get it to work, even with this example from the documentation:

$period        = Period::createFromDuration(2012-01-01, '2 MONTHS');
    $anotherPeriod = Period::createFromDuration(2012-01-15, '3 MONTHS');
    $intersectPeriod = $period->intersect($anotherPeriod);

I always get this error:

PHP Fatal error:  Uncaught exception 'LogicException' with message 'The ending datepoint must be greater or equal to the starting datepoint

HHVM compliance

New tests were added after testing the period package against @padraic humbug. The package received bug fixes but tests no longer work on HHVM.

Documentation unclear on upgrade path for next and previous methods

Q A
Version 4.0

Question

3.x to 4.x upgrade guide says next and previous methods are removed as previously deprecated.

I found this a little sudden, since:

  1. Version 3.4.0 source doesn’t refer to them as deprecated.
  2. v3 documentation neither refers to them as deprecated or suggests replacements to use.

I am not sure if removal was intended (I am assuming so and that deprecation declarations in source were overlooked?) but the upgrade path seems to be missing from either v3 or v4 documentation and perhaps should be covered?

Datepoint::createFromFormat declaration issue with PHP 7.1

Bug Report

Information Description
Version 4.3.0
PHP version 7.1.25
OS Platform Ubuntu 16.04.5 LTS

Summary

After upgrade league/period (4.0.1 => 4.3.0) I've got this warning:

Declaration of League\Period\Datepoint::createFromFormat($format, $datetime, ?DateTimeZone $timezone = NULL) should be compatible with DateTimeImmutable::createFromFormat($format, $time, $object = NULL)

Standalone code, or other way to reproduce the problem

Here is a dummy DateTimeImmutable extender class simulating Datepoint class in a playground
https://www.tehplayground.com/nvl4opycmrAOJbIt

adding a named constructor with DatePeriod

in PHP 5.6 series DatePeriod was improved with the following getter methods

  • DatePeriod::getStartDate
  • DatePeriod::getEndDate
  • DatePeriod::getDateInterval

Should be interesting to have named constructors on Period that could return a new Period instance from a DatePeriod object

Add ability to associate additional custom data to a Period

Hi, it's me again!
I tought it would be nice to add an optional attribute to the Period class so you can associate any data you want to a Period.
I'll explain my use case but I think it may be useful to others.
I need to associate to each of my periods a userId or a roomId, then after I do some Sequence operations like substracts or merges I must be able to retrieve the data back from the resulting Periods in the Sequence.

Do you think it could be a useful feature?

Can the lib handle periods without/ignoring year?

Q A
Version 3.4 / 4.10

Question

for handling birthdays or different opening times in holidays i look for checking if a given date is inside a specific period. the year does not matter because it's the same period every year.

$holidayOpeningTimes = new Period(
    new DateTime('2014-06-01 00:00:00'),
    new DateTimeImmutable('2014-07-01 00:00:00'),
    Period::INCLUDE_START_EXCLUDE_END,
    Period::INGNORE_YEAR
);

$today = new DateTime() //2020-06-22

$holidayOpeningTimes->contains($today) // returns true or false in this case  -> true

do i miss something or is this not possible with period. just had a look in the docs...

related links:

https://stackoverflow.com/questions/37607512/compare-only-day-and-month-without-year-in-php-ex-birth-date?rq=1
https://stackoverflow.com/questions/35236103/php-compare-dates-without-year

Adding the boundaryType to Period named constructors ?

Feature Request

Q A
New Feature yes
BC Break no

Proposal

Currently most Period named constructors do not take into consideration the boundary type.
This proposal is to simplify and normalized Period object instantiation.

Instead of doing

$period = Period::fromMonth(2019, 3)->withBoundaryType(Period::EXCLUDE_ALL);

you will be able to do

$period = Period::fromMonth(2019, 3, Period::EXCLUDE_ALL);

This feature if accepted will land in the next feature release (ie 3.9.0 as the time of writing)

The affected named constructors are the following:

Period::fromYear(int $year): Period;
Period::fromIsoYear(int $year): Period;
Period::fromSemester(int $year, int $semester = 1): Period;
Period::fromQuarter(int $year, int $quarter = 1): Period;
Period::fromMonth(int $year, int $month = 1): Period;
Period::fromIsoWeek(int $year, int $week = 1): Period;
Period::fromDay(int $year, int $month = 1, int $day = 1): Period;

their new signature will be

Period::fromYear(int $year, string $boundaryType = self::INCLUDE_START_EXCLUDE_END): Period;
Period::fromIsoYear(int $year, string $boundaryType = self::INCLUDE_START_EXCLUDE_END): Period;
Period::fromSemester(int $year, int $semester = 1, string $boundaryType = self::INCLUDE_START_EXCLUDE_END): Period;
Period::fromQuarter(int $year, int $quarter = 1, string $boundaryType = self::INCLUDE_START_EXCLUDE_END): Period;
Period::fromMonth(int $year, int $month = 1, string $boundaryType = self::INCLUDE_START_EXCLUDE_END): Period;
Period::fromIsoWeek(int $year, int $week = 1, string $boundaryType = self::INCLUDE_START_EXCLUDE_END): Period;
Period::fromDay(int $year, int $month = 1, int $day = 1, string $boundaryType = self::INCLUDE_START_EXCLUDE_END): Period;

The default value is chosen to avoid BC break.

Of Note: the addition of the boundary Type has already be made for the Datepoint class.

Create from ISO8601 string

Feature Request

Q A
New Feature yes
BC Break no

Proposal

__toString() already exists but there's no (simple) way of converting that back to Period

Feature Request -- Find available from multiple periods of time

Issue summary

Given a collection of Periods where the internal startTime == the lowest value in the collection and the internal endTime == the highest value in the collection, The return of this method would be all of the gaps of unused time (and supporting intersecting / abutting periods). Effectively this would be an inversion of all of the periods in the given collection.

DateTimeImmutable construction

I see a lot of this:

new DateTimeImmutable($datetime->format('Y-m-d H:i:s.u'), $datetime->getTimeZone());

Wouldn't it be better to use createFromFormat because you always have the exact date already? For instance:

DateTimeImmutable::createFromFormat(self::ISO8601, $datetime->format(self::ISO8601), $datetime->getTimeZone());

This reuses the format you've already defined, instead repeating 'Y-m-d H:i:s.u' every time a new date object is made.

isBefore() and isAfter() comparisons do not take timezone into consideration

Issue summary

Given three Period's, with two in UTC and one in UTC -07:00, the one at -07:00 is noted as being earlier even though it should be sandwiched between the two UTC Periods.

array(3) {
  [0]=>
  object(League\Period\Period)#1668 (2) {
    ["startDate":protected]=>
    object(DateTimeImmutable)#994 (3) {
      ["date"]=>
      string(26) "2017-05-02 16:00:00.000000"
      ["timezone_type"]=>
      int(1)
      ["timezone"]=>
      string(6) "-07:00"
    }
    ["endDate":protected]=>
    object(DateTimeImmutable)#1637 (3) {
      ["date"]=>
      string(26) "2017-05-02 17:00:00.000000"
      ["timezone_type"]=>
      int(1)
      ["timezone"]=>
      string(6) "-07:00"
    }
  }
  [1]=>
  object(League\Period\Period)#1703 (2) {
    ["startDate":protected]=>
    object(DateTimeImmutable)#1696 (3) {
      ["date"]=>
      string(26) "2017-05-02 21:56:23.315010"
      ["timezone_type"]=>
      int(3)
      ["timezone"]=>
      string(3) "UTC"
    }
    ["endDate":protected]=>
    object(DateTimeImmutable)#1020 (3) {
      ["date"]=>
      string(26) "2017-05-03 21:57:23.000000"
      ["timezone_type"]=>
      int(3)
      ["timezone"]=>
      string(3) "UTC"
    }
  }
  [2]=>
  object(League\Period\Period)#1738 (2) {
    ["startDate":protected]=>
    object(DateTimeImmutable)#1031 (3) {
      ["date"]=>
      string(26) "2017-05-02 21:57:23.315010"
      ["timezone_type"]=>
      int(3)
      ["timezone"]=>
      string(3) "UTC"
    }
    ["endDate":protected]=>
    object(DateTimeImmutable)#1669 (3) {
      ["date"]=>
      string(26) "2017-05-03 21:58:23.000000"
      ["timezone_type"]=>
      int(3)
      ["timezone"]=>
      string(3) "UTC"
    }
  }
}

Proposed solution: when comparing isBefore() and isAfter(), the respective field should be converted into UTC.

Formatting dates

I'm not sure if this is possible within the scope of this project, or not wanted. But I didn't find it in the docs:

Is it possible to format the period 'nicely'? Something like this:

  • 2015-07-01 - 2015-07-03: 'July 1,2,3 2015'
  • 2015-07-01 - 2015-07-05: 'July 1-5 2015'
  • 2015-07-25 - 2015-08-05: 'July 25 - August 5 2015'
  • 2015-12-25 - 2016-01-05: 'December 25 2015 - January 5 2016'

Not sure about the syntax or localisation, but what do you think about something like this? Could be useful for showing dates in a event calendar etc.

Introduce high-level "timeline" abstraction

Hello!

Thank you for this great library! It looks very promising.
I was using my own custom solution for this exact purpose, but it looks like the better idea wood be to switch to this library instead.

However, it's lacking some extra functionality that I (and probably other developers) require. Please consider the following use case:

There are users in the system who can purchase subscription for the service. Each subscription can be represented as a Period instance with start and end dates. Subscription starts on the date of purchase or exacly when previous subscription ends (overlapping or "abuts" periods). Sometimes user can purchase subscription to use in the future (there is a gap between subscriptions). So in the end we have a Timeline entity that consists of overlapping and isolated periods and we have to answer this question: when the current continous period starts/ends?. What if we will need to merge all overlapping periods on a timeline to get only continous periods for better visualization? What if we need to sort all periods on a timeline?

I think we can introduce a Timeline class that will allow us to work with such abstraction.

timeline

The API could be the following:

$timeline = new Timeline([$periodA, $periodB, ..., $periodN]);

$timeline->addPeriod($periodC);

// Timeline will sort all periods added to it internally to always maintain a chronological order.

// Timeline instance can be easilly iterated in chronological order
foreach ($timeline as $period) {
    // output a period for visualization
}

// Will return a new Timeline with all overlapping periods merged together.
$mergedTimeline = $timeline->merge();

// Merge method will support a tolerance as a DateInerval instance
$mergedTimeline = $timeline->merge(new \DateInterval('P1D'));
// If two periods are close enough (the gap between them is less than tolerance) they will be also merged together

// Will return periods from the Timeline that includes the specified date.
$periods = $timeline->getPeriodsForDate(new \DateTime());

// Will merge the periods around the specified date (with respect to tolerance) and return a merged period.
// Actually this can be achieved by calling merge() and getPeriodsForDate() inernally.
// Or a better optimized method can be used.
$period = $timeline->getContinousPeriodForDate(new \DateTime, $tolerance);

Does it make sense?

Thank you!


mergePeriods

Here's the function that can be used to merge periods on the timeline with respect to tolerance. All periods MUST be chronologically ordered first. It implements a linear algorithm.

    /**
     * Merges intersecting time periods inside the collection.
     *
     * @param \DateInterval $tolerance
     *
     * @return TimePeriodCollection
     */
    public function mergePeriods(\DateInterval $tolerance = null)
    {
        // Making a shallow copy of the list.
        $list = $this->list;

        $count = count($list);

        for ($i = 0; $i < ($count - 1); $i++) {

            $timePeriodA = $list[$i];
            $timePeriodB = $list[$i + 1];

            $firstDateEnd = $timePeriodA->getDateEnd();

            // Adding tolerance to the right border of the first period
            // if it's specified.
            if ($tolerance) {
                // We have to clone the date in order to
                // preserve original instance's value.
                $firstDateEnd = clone $firstDateEnd;
                $firstDateEnd->add($tolerance);
            }

            // Periods are intersecting.
            if ($timePeriodB->getDateStart() <= $firstDateEnd) {

                // Finding an end date for a merged period.
                // Second period can be inside of the first one.
                $newDateEnd = max(
                    $timePeriodA->getDateEnd(),
                    $timePeriodB->getDateEnd()
                );

                $mergedPeriod = new TimePeriod(
                    $timePeriodA->getDateStart(),
                    $newDateEnd
                );

                // Removing first period from the list.
                unset($list[$i]);

                // Adding merged period as a second one.
                // It will become first period in the next iteration.
                $list[$i + 1] = $mergedPeriod;
            }
        }

        // Returning a new instance of the collection with the merged list.
        return new self($list);
    }

I've tried to consider all possible use cases and make it as ellegant as possible.

A period must contain at least its starting datepoint

Hi - first of all thanks for the small but sweet package, will be useful for overlapping periods validation.

While going through the code I noticed that the constructor is accepting the same $startDate and $endDate, with a notable exception message 'the ending endpoint must be greater or equal to the starting endpoint'.

On the other hand the contains method expects the input $datetime to be: stricktly smaller than the endDate. This suggests to me that the ending endPoint is considered to be outside the period. This last bit doesn't seem to be in line with the check in the constructor?

Period class next and previous methods when applied to quarter interval return different interval than expected

Issue summary

Period class next and previous methods when applied to quarter interval return different interval than expected.

System informations

Information Description
League\Period version 3.3.0
PHP/HHVM version 7.0.22
OS Platform Linux

Standalone code, or other way to reproduce the problem

<?php

require_once 'vendor/autoload.php';

use League\Period\Period;

$qPrevious = Period::createFromQuarter(2014, 4);
$qCurrent  = Period::createFromQuarter(2015, 1);
$qNext     = Period::createFromQuarter(2015, 2);

var_dump($qCurrent->sameValueAs($qPrevious->next()));
var_dump($qCurrent->sameValueAs($qNext->previous()));

Expected result

boolean true
boolean true

Actual result

boolean false
boolean false

Instantiate from timestamp

It would be a nice feature to also be able to instantiate a period using unix timestamps. Or is that problematic regarding which timezone that is present?

Merge sequences. (Logical OR similar to subtract which is logical NOT)

Feature Request

Q A
New Feature yes
BC Break no

Proposal

We have subtract method for sequences. It represents logical operator NOT.
I need the same logical operator OR. It can be called merge.

example 1

2 sequences:
== == = ==
==========
output:
==========

example 2

2 sequences:
= = =
 = =
output
=====

example 3

3 sequences:
= = =
=
==
output:
=== =

We have a car rental with 10 cars. All cars are similar. Desired rent duration is provided. We want to show a time windows available for rent while combining 10 cars into one. Available periods for each car should cover the same interval and should paint general availability. I doubt that there is an existing feature that would allows this.

Correct me if i'm wrong. Please provide some advice. Also I hope to discuss it further. Thank you in advance!

Add helper itterators

It would be really handy if this lib could do things like:

foreach ($period->weeks() as $period)
    // ....

This'd work for days(), weeks(), months(), years().

Or perhaps just a $period->each(\DateInterval $interval) which would give us scope to add anything?

I'm more than happy to write the code for this, but wanted to put feelers out and see if it was something you'd consider (or have already considered) before investing the time.

DST issue in the unit tests

Issue summary

The seem to be a DST issue with how the split() function is tested, because it assumes that a day always has 24 hours, which will be true except for two days during the year.

System informations

Information Description
League\Period version master (3.3.0)
PHP/HHVM version PHP 7.0.0
OS Platform OSX 10.12

Standalone code, or other way to reproduce the problem

Run the test suite on a day where we mess around with DST, like today (Nov 5th).

Expected result

Tests should pass.

Actual result

$ phpunit --stop-on-failure
PHPUnit 4.8.27 by Sebastian Bergmann and contributors.

Runtime:	PHP 7.0.0 with Xdebug 2.4.0RC2
Configuration:	/Users/marc/Projects/period/phpunit.xml

................F

Time: 428 ms, Memory: 8.00MB

There was 1 failure:

1) League\Period\Test\PeriodTest::testSplitWithInconsistentInterval
Failed asserting that 18000 matches expected 14400.

/Users/marc/Projects/period/test/PeriodTest.php:154

FAILURES!
Tests: 17, Assertions: 52, Failures: 1.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.