php[world] 2018

The DatePeriod class

(PHP 5 >= 5.3.0, PHP 7)

Introduction

Represents a date period.

A date period allows iteration over a set of dates and times, recurring at regular intervals, over a given period.

Class synopsis

DatePeriod implements Traversable {
/* Constants */
const integer EXCLUDE_START_DATE = 1 ;
/* Properties */
public integer $recurrences ;
public boolean $include_start_date ;
/* Methods */
public __construct ( DateTimeInterface $start , DateInterval $interval , int $recurrences [, int $options ] )
public __construct ( DateTimeInterface $start , DateInterval $interval , DateTimeInterface $end [, int $options ] )
public __construct ( string $isostr [, int $options ] )
public DateInterval getDateInterval ( void )
public DateTimeInterface getEndDate ( void )
public DateTimeInterface getStartDate ( void )
}

Predefined Constants

DatePeriod::EXCLUDE_START_DATE

Exclude start date, used in DatePeriod::__construct().

Properties

recurrences

The number of recurrences.

include_start_date

Whether to include the start date in the set of recurring dates or not.

start

The start date of the period.

current

During iteration this will contain the current date within the period.

end

The end date of the period.

interval

An ISO 8601 repeating interval specification.

Changelog

Version Description
5.3.27, 5.4.17 The public properties recurrences, include_start_date, start, current, end and interval have been exposed.

Table of Contents

add a note add a note

User Contributed Notes 10 notes

up
112
josh dot love at verizon dot net
5 years ago
Just an example to include the end date using the DateTime method 'modify'

<?php

$begin
= new DateTime( '2012-08-01' );
$end = new DateTime( '2012-08-31' );
$end = $end->modify( '+1 day' );

$interval = new DateInterval('P1D');
$daterange = new DatePeriod($begin, $interval ,$end);

foreach(
$daterange as $date){
    echo
$date->format("Ymd") . "<br>";
}
?>
up
40
logos-php at kith dot org
6 years ago
Thanks much to those of you who supplied sample code; that helps a lot.

I wanted to mention another thing that helped me: when you do that foreach ( $period as $dt ), the $dt values are DateTime objects.

That may be obvious to those of you with more experience, but I wasn't sure until I looked it up on Stack Overflow. So I figured it was worth posting here to help others like me who might've been confused or uncertain.
up
11
jkaatz at gmx dot de
9 years ago
Nice example from PHP Spring Conference (thanks to Johannes Schlüter and David Zülke)

<?php
$begin
= new DateTime( '2007-12-31' );
$end = new DateTime( '2009-12-31 23:59:59' );

$interval = DateInterval::createFromDateString('last thursday of next month');
$period = new DatePeriod($begin, $interval, $end, DatePeriod::EXCLUDE_START_DATE);

foreach (
$period as $dt )
  echo
$dt->format( "l Y-m-d H:i:s\n" );
?>

DateInterval specs could be found at http://en.wikipedia.org/wiki/ISO_8601#Time_intervals
up
3
mail at pascalhofmann dot de
1 year ago
When looping over a DatePeriod object, the returned objects always implement DateTimeInterface. The exact type returned depends on how the DatePeriod was created. If $start was a DateTimeImmutable, the objects returned will be of type DateTimeImmutable. If a DateTime object was used, the objects returned will be of type DateTime.
up
3
php at karlsruler dot de
3 years ago
The iterator seems to check the time as well, it excludes the end element if its time is 00:00:00. So the slightly safer version (to compare it against joshs suggestion) is to use $date->setTime(23, 59, 59) instead of $date->modify("+1 day").
up
9
Memori
7 years ago
If you want to include the end-date, add one day to it:

<?php
$startDate
= new DateTime();
$endDate = new DateTime();

$startDateInt = new DateInterval( "P1Y" );
$endDateInt = new DateInterval( "P1D" );

$startDate->sub( $startDateInt );
$endDate->add( $endDateInt );

$periodInt = new DateInterval( "P1M" );
$period = new DatePeriod( $startDate, $periodInt, $endDate );

// At februari 2011:
// $period = (8,9,10,11,12,1,2)
?>
up
3
patrick at adrichem dot nu
4 years ago
DatePeriod is not compatible with negative intervals.

To do so you can simply use DateInterval and loop through it yourself like this: (not start should be ahead of end if you use a negative interval

    class DateRange extends ArrayIterator
    {

        protected $oDate = null;
        protected $oStartDate = null;
        protected $oEndDate = null;
        protected $oInterval = null;

        public function __construct( DateTime $oStartDate, DateTime $oEndDate, DateInterval $oInterval = null )
        {
            $this->oStartDate = $oStartDate;
            $this->oDate = clone $oStartDate;
            $this->oEndDate = $oEndDate;
            $this->oInterval = $oInterval;
        }

        public function next()
        {
            $this->oDate->add($this->oInterval);
            return $this->oDate;
        }

        public function current()
        {
            return $this->oDate;
        }

        public function valid()
        {
            if ($this->oStartDate > $this->oEndDate)
            {
                return $this->oDate >= $this->oEndDate;
            }
            return $this->oDate <= $this->oEndDate;
        }

    }

$oRange = new DateRange(new DateTime("2013-10-01"), new DateTime("2013-01-01"), DateInterval::createFromDateString("-1 month") );
    foreach ($oRange as $oDate)
    {
        echo $oDate->format("Y-m-d") . "<br />";
    }
up
5
Aurelien Marchand
7 years ago
Warning About DatePeriod for Some Versions of PHP
*****************************************

Some versions of PHP had a bug so that caused DatePeriod to act strangely. For instance, the following code:

<?php
$start
= DateTime::createFromFormat("Y-m-d H:i:s","2011-01-01 00:00:00",new DateTimeZone("America/Toronto"));
$interval = new DateInterval("P1M"); // 1 month
$occurrences = 3;
$period = new DatePeriod($start,$interval,$occurrences);
foreach(
$period as $dt){
  echo
$dt->format("Y-m-d H:i:s") . "\n";
}
foreach(
$period as $dt){
  echo
$dt->format("Y-m-d H:i:s") . "\n";
}
?>

Would produce the following text:
2011-01-01 00:00:00
2011-02-01 00:00:00
2011-03-01 00:00:00
2011-04-01 00:00:00
2011-05-01 00:00:00
2011-06-01 00:00:00
2011-07-01 00:00:00
2011-08-01 00:00:00

Instead of:
2011-01-01 00:00:00
2011-02-01 00:00:00
2011-03-01 00:00:00
2011-04-01 00:00:00
2011-01-01 00:00:00
2011-02-01 00:00:00
2011-03-01 00:00:00
2011-04-01 00:00:00

5.3.2 fails
5.3.3 fails
5.3.4 -??-
5.3.5 works
up
0
joseph dot cardwell at jbcwebservices dot com
3 months ago
As someone noted, at least in 7.2, dates with time 0 are excluded from the start and end.

To get a regular span of dates I ended up with:

$dates = new DatePeriod(
    ( new DateTime($date_start) )->setTime(0,0,1),
    new DateInterval('P1D'),
    ( new DateTime($date_end) )->setTime(0,0,1)
);
up
0
mike at saymikeo dot com
1 year ago
Calculating business days can be cumbersome. Here is an iterator for handling business days. Usage examples below for adding # of business days and calculating how many business days between two dates.

Here is the iterator class
https://gist.github.com/styks1987/29dd0f6a68e3b07ba70fec18f732eb86

Usage

Counting # of Business Days between two dates

<?php
function countBusinessDays($start, $stop)
    {
        if(
$start > $stop){
           
$tmpStart = clone $start;
           
$start = clone $stop;
           
$stop = clone $tmpStart;
        }

       
// Adding the time to the end date will include it
       
$period = new \DatePeriod($start->setTime(0,0,0), new \DateInterval('P1D'), $stop->setTime(23,59,59), \DatePeriod::EXCLUDE_START_DATE);
       
$periodIterator = new BusinessDayPeriodIterator($period);
       
$businessDays = 0;
        while(
$periodIterator->valid()){
           
// If we run into a weekend, don't count it
           
if(!$periodIterator->isWeekend()){
               
$businessDays++;
            }
           
$periodIterator->next();
        }

        return
$businessDays;
    }
?>

Add # of business days

<?php
function addBusinessDays(\DateTime $startDateTime, $daysToAdd)
    {
       
$endDateTime = clone $startDateTime;
       
$endDateTime->add(new \DateInterval('P' . $daysToAdd . 'D'))->setTime(23,59,59);
       
$period = new \DatePeriod($startDateTime, new \DateInterval('P1D'), $endDateTime);

       
$periodIterator = new BusinessDayPeriodIterator($period);
       
$adjustedEndingDate = clone $startDateTime;
        while(
$periodIterator->valid()){
           
$adjustedEndingDate = $periodIterator->current();
           
// If we run into a weekend, extend our days
           
if($periodIterator->isWeekend()){
               
$periodIterator->extend();
            }
           
$periodIterator->next();
        }

        return
$adjustedEndingDate;
    }
?>
To Top