Date/time programming is one of the tricky aspects of software development. Although inherently not complex in itself, coding date/time algorithms can be a subtle source of bugs. Especially in web development a feature such as payment subscription processing that ranges from days to weeks to months can get complex quickly. Also such kind of scenarios require additional features like auto renewal, scheduled email alerts to subscribers etc. Such kind of features require good date/time handling algorithms and libraries that handle such chores are always welcome.
One such library I encountered recently is Period. The library helps resolve many recurrent issues around time range selection and usage.
One such library I encountered recently is Period. The library helps resolve many recurrent issues around time range selection and usage.
Period lets you easily works with time ranges – creating a time range, comparing between ranges, checking if a range falls withing another range etc.
Installation
You need PHP >= 5.3.0 to use Period but the latest stable version of PHP is recommended. Period is available on Packagist and can be installed using Composer.
You can also install the library as a standalone by downloading the zip an including it in your project.
Creating a Period
To start using the Period class the first task you have to do is create a Period object. The default way to instantiate a new Period is with the Period constructor –
Period::__construct($start, $end)
Both $start and $end parameters represent the period endpoints as DateTime objects. The $start represents the starting endpoint. The $end value represents the ending endpoint. $end must be greater or equal to $start or the instantiation will throw a LogicException error. $start point is included in the period while the $end point is excluded from the period.
You can instantiate the Period object like the following.
OR
You can either pass a time string as shown in the example above or pass a DateTime object. When you pass a string they are automatically converted to a DateTime object before processing.
Once you have instantiated Period object you can access its properties using various getter methods.
Period::getStart();
This will return the following DateTime object.
Period::getEnd();
This will return the following DateTime object.
Period::getDuration($get_as_seconds = false);
This will return the following Period duration as a DateInterval object.
Passing a ‘true’ parameter to the above method returns the period in seconds.
This will return the following (in seconds).
Creating different period bocks
In the above examples we have created a period using 2 parameters, a start and end endpoints. However you can also quickly create various periods using some pre-built named constructors.
Period::createFromWeek($year, $week)
Returns a Period object with a duration of 1 week for a given year and week.
The $year parameter is a valid year;
The $week parameter is a selected week (between 1 and 53) according to the ISO8601 date and time standard;
The $year parameter is a valid year;
The $week parameter is a selected week (between 1 and 53) according to the ISO8601 date and time standard;
You can now use the various methods on this period as done in the previous section, a example using ‘getStart’ is given below.
This will return the following.
Period::createFromMonth($year, $month);
Returns a Period object with a duration of 1 month for a given year and month.
The $year parameter is a valid year;
The $month parameter is a selected month (between 1 and 12);
The $year parameter is a valid year;
The $month parameter is a selected month (between 1 and 12);
One important point should be mentioned here. When we use the ‘getEnd’ method on any period object, it returns the end point of the period, which as mentioned at the start of the post, is excluded from the period. The actual endpoint is ‘one less’ than the returned value. or example for the above ‘createFromMonth’ method the actual last day is ’31 Oct 2014′, but ‘getEnd’ returns ‘1st Nov 2014′. So keep this in mind while programming. This can lead to subtle bugs.
Period::createFromQuarter($year, $quarter);
Returns a Period object with a duration of 3 months for a given year and quarter.
The $year parameter is a valid year;
The $quarter parameter is a selected quarter (between 1 and 4);
The $year parameter is a valid year;
The $quarter parameter is a selected quarter (between 1 and 4);
Period::createFromSemester($year, $semester);
Returns a Period object with a duration of 6 months for a given year and semester.
The $year parameter is a valid year;
The $semester parameter is a selected semester (between 1 and 2);
The $year parameter is a valid year;
The $semester parameter is a selected semester (between 1 and 2);
Period::createFromYear($year);
Returns a Period object with a duration of 1 year for a given year.
The $year parameter is a valid year;
The $year parameter is a valid year;
Period::createFromDuration($start, $duration);
Returns a Period object which starts at $start with a duration equals to $duration. This a generic way to create periods.
The $start represents the starting included endpoint expressed as DateTime object.
The $duration parameter is a DateInterval object;
The $start represents the starting included endpoint expressed as DateTime object.
The $duration parameter is a DateInterval object;
Comparing time periods
An important element in creating time periods is the ability to compare them. The following methods help you compare different Period objects according to their endpoints or durations. Let us see how to compare using endpoints. The following tells whether two Period objects shares the same endpoints.
In the above example both $orig and $other have the same endpoint – 2014-03-01 00:00:00.
The following example calculates whether two Period objects overlap each other or not.
In the above example the $orig Period overlaps the $other Period, although the 3 week of $other spills in the 4th month. A graphical representation is given below. A Period does not have to 100% overlap or cover another Period, a partial overlap qualifies.
The following example tells if a Period contains a particular date.
Also the following code make it explicityle clear as mentioned above that a Period endpoint is not included the Period.
Another important method is to find differences between 2 Periods. The durationDiff method returns a difference between 2 Period objects, returning a DateInterval object.
You can also return the difference in seconds.
The above example returns a difference of ‘0’ seconds as the month of January and March contains the same number of days and hence the difference will be ‘0’. However with the next example the difference will be ‘86400’ (1 day) seconds as April has 1 day less than January.
The final few methods that aid in comparing duration are given below. The first is
compareDuration
. Compare two Period objects according to their duration.
Period::compareDuration(Period $period)
– Return 1 if the current object duration is greater than the submitted $period duration;
– Return -1 if the current object duration is less than the submitted $period duration;
– Return 0 if the current object duration is equal to the submitted $period duration;
– Return -1 if the current object duration is less than the submitted $period duration;
– Return 0 if the current object duration is equal to the submitted $period duration;
To ease the method usage you can rely on the following alias methods which return boolean values:
Period::durationGreaterThan(Period $period)
Period::durationLessThan(Period $period)
Period::sameDurationAs(Period $period)
Period::durationLessThan(Period $period)
Period::sameDurationAs(Period $period)
Modifying Periods
You can modify, add, merge Period objects. Note that Period object is an immutable value object so any change to its property returns a new Period object rather than changing the original.
Period::add($interval)
Returns a new Period object by adding an interval to the current ending excluded endpoint. The $interval parameter is expressed as a DateInterval object.
Period::sub($interval)
Returns a new Period object by substracting an interval to the current ending excluded endpoint. The $interval parameter is expressed as a DateInterval object.
Period::merge(Period $period[, Period $…])
Merges two or more Period objects by returning a new Period object which encloses all the submitted objects.
In the above example we have created 2 Periods, each of 1 month , March and May. When we merge the Periods the middle days are automatically added to the merged Period. In the above example the merged Period will also contain the month of April.
Period::intersect(Period $period)
Computes the intersection between two Period objects and returns a new Period object. Before getting the intersection, make sure the Period object at least overlaps.
The following figure give a visual idea of the process.
In summary
Rounding the discussion, the Period class can be used to easily develop subscription like solutions wherein we need to extensively process time ranges. http://www.codediesel.com/algorithms/period-time-range-api-for-php/