-
Notifications
You must be signed in to change notification settings - Fork 84
RRule
A RRule object is an immutable value object. One instanciated you cannot change the recurrence rule.
The constructor accepts an associative array with all the keywords defined in the RFC ("rule parts") as key and their value as value. The keys and the values are case insensitive, so you can write FREQ
or freq
and MONTHLY
or monthly
.
Example: Every 2 weeks on Monday, starting now
$rrule = new RRule\RRule([
'freq' => 'weekly',
'byday' => 'MO',
'interval' => 2
]);
The only required key is FREQUENCY
, which must be one of the following:
-
"YEARLY"
(or the constantRRule::YEARLY
) -
"MONTHLY"
(or the constantRRule::MONTHLY
) -
"WEEKLY"
(or the constantRRule::WEEKLY
) -
"DAILY"
(or the constantRRule::DAILY
) -
"HOURLY"
(or the constantRRule::HOURLY
) -
"MINUTELY"
(or the constantRRule::MINUTELY
) -
"SECONDLY"
(or the constantRRule::SECONDLY
)
Here is a quick description the other parameters. For more details, check the iCalendar RFC.
Name | Description |
---|---|
DTSTART |
The recurrence start date and time. Can be given as a string understandable by PHP's DateTime constructor (for example 2015-07-01 14:46:30 ) a UNIX Timestamp (as int ) or a DateTime object. If no timezone is provided, the default PHP timezone will be used. Default is now. Unlike documented in the RFC, this is not necessarily the first recurrence instance, unless it does fit in the specified rule. Note that the microseconds component will be set to 0. |
INTERVAL |
The interval between each FREQUENCY iteration. For example, when using YEARLY , an interval of 2 means once every two years, but with HOURLY , it means once every two hours. Default is 1. |
WKST |
The week start day. Must be one of the following strings MO , TU , WE , TH , FR , SA , SU . This will affect recurrences based on weekly periods. Default is MO (Monday). |
COUNT |
How many occurrences will be generated. |
UNTIL |
The limit of the recurrence. Accepts the same formats as DTSTART . If no timezone is provided, the default PHP timezone will be used. It can be in a different timezone than DTSTART . If a recurrence instance happens to be the same the date given, this will be the last occurrence. |
BYMONTH |
The month(s) to apply the recurrence to, from 1 (January) to 12 (December). It can be a single value, or a comma-separated list or an array. |
BYWEEKNO |
The week number(s) to apply the recurrence to, from 1 to 53 or -53 to -1. Negative values mean that the counting start from the end of the year, so -1 means "the last week of the year". Week numbers have the meaning described in ISO8601, that is, the first week of the year is that containing at least four days of the new year. Week numbers are affected by the WKST setting. It can be a single value, or a comma-separated list or an array. Warning: negative week numbers are not fully tested yet.
|
BYYEARDAY |
The day(s) of the year to apply the recurrence to, from 1 to 366 or -366 to -1. Negative values mean that the count starts from the end of the year, so -1 means "the last day of the year". It can be a single value, or a comma-separated list or an array. |
BYMONTHDAY |
The day(s) of the month to apply the recurrence to, from 1 to 31 or -31 to -1. Negative values mean that the count starts from the end of the month, so -1 means "the last day the month". It can be a single value, or a comma-separated list or an array. |
BYDAY |
The day(s) of the week to apply the recurrence to from the following: MO , TU , WE , TH , FR , SA , SU . It can be a single value, a comma-separated list or an array. Each day can be preceded by a number, indicating a specific occurence within the interval. For example: 1MO (the first Monday of the interval), 3MO (the third Monday), -1MO (the last Monday), and so on. |
BYHOUR |
The hour(s) to apply the recurrence to, from 0 to 23. It can be a single value, or a comma-separated list or an array. |
BYMINUTE |
The minute(s) to apply the recurrence to, from 0 to 59. It can be a single value, or a comma-separated list or an array. |
BYSECOND |
The second(s) to apply the recurrence to, from 0 to 60. It can be a single value, or a comma-separated list or an array. Warning: leap second (i.e. second 60) support is not fully tested. |
BYSETPOS |
The Nth occurrence(s) within the valid occurrences inside a frequency period. It can be a single value, or a comma-separated list or an array. Negative values mean that the count starts from the set. For example, a bysetpos of -1 if combined with a MONTHLY frequency, and a byweekday of 'MO,TU,WE,TH FR' , will result in the last work day of every month. |
Example: The last workday of the month
$rrule = new RRule\RRule([
'freq' => 'monthly',
'byday' => 'MO,TU,WE,TH,FR',
'bysetpos' => -1
]);
It is also possible to create a RRule object from an RFC-like syntax by passing a string to the constructor. The string may be a multiple line string (DTSTART
and RRULE
), a single line string (only RRULE
), or just the RRULE
property value.
DTSTART
and RRULE
must be separated by \n
(LF), or \r\n
(CRLF).
Example:
new RRule('DTSTART;TZID=America/New_York:19970901T090000
RRULE:FREQ=DAILY;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR;BYMONTH=1');
new RRule('RRULE:FREQ=DAILY;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR;BYMONTH=1');
new RRule('FREQ=DAILY;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR;BYMONTH=1');
new RRule('FREQ=DAILY', new DateTime('1997-12-24')); // since version 1.5
Warning In the RFC, DTSTART
and RRULE
are two different properties, and therefore must be on a two separate lines. It is not valid to have DTSTART
inside RRULE
.
For versions >= 1.5 when creating from a string without DTSTART
you can optionally specify the date to be used in the second argument. By default, current date will be used.
When creating from a string, there is less flexibility on the date formats for DTSTART
and UNTIL
. Here is a quick recap of the RFC rules that applies:
DTSTART |
UNTIL |
Comment |
---|---|---|
Date (19970901 ) |
Date | |
Date with local time (19970901T090000 ) |
Date with local time | PHP's default timezone will be used |
Date with UTC time (19970901T090000Z ) |
Date with UTC time | |
Date with local time and time zone reference (TZID=America/New_York:19980119T020000 ) |
Date with UTC time (19970901T090000Z ) |
There is no way to specify a different timezone for UNTIL in the RFC syntax. |
Provided in the 2nd argument | Date with UTC time (19970901T090000Z ) |
Not recommended, if UNTIL is present in the string, so should DTSTART
|
For versions >= 1.3, the lib will throw an InvalidArgumentException
if the string is invalid.
For versions >= 1.5, the factory method RRule::createFromRfcString()
can be used to parse a string and create either a RRule
or a RSet
object, depending on the string.
An instance of RRule
can be used directly in a foreach
loop to obtain occurrences. This is most efficient way to use this class, as the occurrences are only computed one at the time, as you need them.
Occurrences are DateTime objects, of the same timezone as the original DTSTART
date.
Warning: if your rule doesn't have UNTIL
or COUNT
parts, it will be an infinite loop! You MUST take care of exiting the loop yourself.
Example:
use RRule\RRule;
// every 2 days, 5 times
$rrule = new RRule([
'freq' => RRule::DAILY,
'interval' => 2,
'count' => 5,
'dtstart' => '1997-09-02 09:00:00'
]);
foreach ( $rrule as $occurrence ) {
echo $occurrence->format('r'),"\n";
}
// output:
// Tue, 02 Sep 1997 09:00:00 +0000
// Thu, 04 Sep 1997 09:00:00 +0000
// Sat, 06 Sep 1997 09:00:00 +0000
// Mon, 08 Sep 1997 09:00:00 +0000
// Wed, 10 Sep 1997 09:00:00 +0000
A RRule
object can also be used as an array to access the Nth occurrence directly.
Example:
use RRule\RRule;
// every 2 days, 5 times
$rrule = new RRule([
'freq' => RRule::DAILY,
'interval' => 2,
'count' => 5,
'dtstart' => '1997-09-02 09:00:00'
]);
echo $rrule[0]->format('r'),"\n"; // Tue, 02 Sep 1997 09:00:00 +0000
echo $rrule[4]->format('r'),"\n"; // Wed, 10 Sep 1997 09:00:00 +0000
var_dump(isset($rrule[0])); // bool(true)
var_dump(isset($rrule[5])); // bool(false)
In addition to the Iterator
interface and the ArrayAccess
interface, the methods from the RRuleInterface are available.
Please check the corresponding wiki page for a list of all the methods.
(version >= 1.4)
Returns the internal array representing the rule. Useful if you want to populate a web form for example.
Will produce a RFC-compliant string representing the recurrence rule.
By default, the lib will produce a DTSTART
value with local time and timezone reference, even if only a date was passed originally (the time will be 00:00:00
and the timezone your default timezone). If the UNTIL
part is present, it will be in UTC
. For version >= 1.3, if the parameter $include_timezone
is false, DTSTART
and UNTIL
will be dates with local time (e.g. 19970901T120000
) without timezone information. UNTIL
will be converted to the same timezone as DTSTART
if they were different.
Will produce a (basic) human readable description of the recurrence rule. Optionally, you may pass an array of options as the first argument (see table below). Note: this option method will produce better results if PHP intl
extension is available.
Name | Description |
---|---|
locale |
A locale string to determine the language of the result as well as the date and time format. Default is the current locale, as defined in Locale::getDefault() (with intl loaded) or setlocale() (without intl loaded). Examples: 'en' , 'en_GB' , 'fr' , ... |
fallback |
A locale to fallback to, in case the main language is not found. Default is en . Set it to null to disable fallback. (version >= 1.2)
|
date_formatter |
A function that will be called to format DTSTART and UNTIL (if applicable). Default is to use the PHP intl extension if loaded, or format the date as Y-m-d H:i:s . If you wish to provide your own, the method takes a \DateTime object as first and only argument. |
date_format |
Only if intl extension is available. One of the IntlDateFormatter predefined constants. Default is IntlDateFormatter::SHORT
|
time_format |
Only if intl extension is available. One of the IntlDateFormatter predefined constants. Default is IntlDateFormatter::NONE , IntlDateFormatter::SHORT or IntlDateFormatter::LONG depending how much the time part is relevant to the rule. |
explicit_infinite |
Explicitely say if the rule is infinite (default true). Example: Every day, forever. (version >= 1.5) |
include_start |
Include the start date (default true). Example: Every day, starting from Mon, 01 Sep 1997. (version >= 1.5) |
include_until |
Include the until date or count (default true). (version >= 1.6) |
custom_path |
Filesystem path to look for custom translation. If the option is present, it'll first look for a file in this folder before looking into the default folder. (version >= 2.0) |
Examples:
$rule = new RRule('DTSTART;TZID=America/New_York:19970901T090000
RRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR');
echo $rule->humanReadable();
// every other week on Monday, Wednesday and Friday, starting from 9/1/97, until 12/23/97
echo $rule->humanReadable(['locale' => 'fr']);
// une semaine sur deux le lundi, mercredi et vendredi, à partir du 01/09/97, jusqu'au 23/12/97
echo $rrule->humanReadable([
'locale' => 'en_US',
'date_format' => IntlDateFormatter::MEDIUM
]),"\n\n";
// every other week on Monday, Wednesday and Friday, starting from Sep 1, 1997, until Dec 23, 1997
echo $rule->humanReadable(['date_formatter' => function($date) { return $date->format('r'); }]);
// every other week on Monday, Wednesday and Friday, starting from Mon, 01 Sep 1997 09:00:00 -0400, until Wed, 24 Dec 1997 00:00:00 +0000
Translations files are stored in src/i18n
folder. If your favorite language is missing, feel free to add it and submit a pull request!
Occurrences are cached the first time they are calculated. If you use the RRule
instance multiple times, caching will improve the performance considerably.
Calling this method will clear the internal cache of the instance, and force it to recalculate the occurrences next time you try to them.
These examples were converted from the RFC. More examples can be found in the unit tests.
Daily, for 10 occurrences.
$rrule = new RRule([
'freq' => 'daily',
'count' => 10,
'dtstart' => '1997-09-02 09:00:00'
]);
// 1997-09-02 09:00:00
// 1997-09-03 09:00:00
// 1997-09-04 09:00:00
// 1997-09-05 09:00:00
// 1997-09-06 09:00:00
// 1997-09-07 09:00:00
// 1997-09-08 09:00:00
// 1997-09-09 09:00:00
// 1997-09-10 09:00:00
// 1997-09-11 09:00:00
Daily until December 24, 1997
$rrule = new RRule([
'freq' => 'daily',
'dtstart' => '1997-09-02 09:00:00',
'until' => '1997-12-24 00:00:00'
]);
// 1997-09-02 09:00:00
// 1997-09-03 09:00:00
// 1997-09-04 09:00:00
// [...]
// 1997-12-22 09:00:00
// 1997-12-23 09:00:00
Everyday in January, for 3 years.
$rrule = new RRule([
'freq' => 'yearly',
'bymonth' => 1,
'byday' => 'MO,TU,WE,TH,FR,SA,SU',
'dtstart' => '1997-09-02 09:00:00',
'until' => '2000-01-31 09:00:00'
]);
// 1998-01-01 09:00:00
// 1998-01-02 09:00:00
// [...]
// 1998-01-31 09:00:00
// 1999-01-01 09:00:00
// 1999-01-02 09:00:00
// [...]
// 1999-01-31 09:00:00
// 2000-01-01 09:00:00
// [...]
// 2000-01-31 09:00:00
- Unlike documented in the RFC, and like in the Python version, the starting datetime (
DTSTART
) is not the first recurrence instance, unless it does fit in the specified rules. This behavior makes more sense than otherwise and is easier to work with. This will only change the results ifCOUNT
orBYSETPOS
are used. - The current algorithm to compute occurrences is faster at lower frequencies and slower at higher the frequencies. So
YEARLY
is faster thatMONTHLY
which is faster thanWEEKLY
and so on. So if you want to achieve the fastest calculation time, whenever possible, try to use the lowest possible frequency. For example, to get "every day in january" it is slightly faster to write['freq' => 'yearly','bymonth' => 1,'bymonthday' => range(1,31)]
rather than['freq' => 'daily','bymonth' => 1]
. - Computing all occurrences of rule might get noticeably slow with very high frequencies (
MINUTELY
orSECONDELY
) if the rule last a long period of time (such as many years). - Some perfectly RFC-compliant rules are actually impossible and will produce no result. For example "every year, on week 40, in February" (week 40 will never occur in February). In a cases like that, the library will still try to find occurrences, because the rule is technically valid, before returning an empty set once all the possibilities have been exhausted.
- To avoid issues with date comparison, this library ignores microseconds. Any microseconds provided as input, or created by default (starting PHP 7.1) are reset to 0.