mirror of
https://github.com/rlanvin/php-rrule.git
synced 2025-02-20 09:54:16 +01:00
Better string generation & parsing
This commit is contained in:
parent
fba57c4ef2
commit
13626f66c5
15
README.md
15
README.md
@ -28,6 +28,9 @@ foreach ( $rrule as $occurrence ) {
|
||||
// Tue 01 Sep 2015
|
||||
// Thu 01 Oct 2015
|
||||
// Sun 01 Nov 2015
|
||||
|
||||
echo $rrule->humanReadable(),"\n";
|
||||
// monthly on the 1st of the month, starting from 01/06/2015, 6 times
|
||||
```
|
||||
|
||||
Complete doc is available in [the wiki](https://github.com/rlanvin/php-rrule/wiki).
|
||||
@ -35,6 +38,7 @@ Complete doc is available in [the wiki](https://github.com/rlanvin/php-rrule/wik
|
||||
## Requirements
|
||||
|
||||
- PHP >= 5.3
|
||||
- [intl extension](http://php.net/manual/en/book.intl.php) is recommended for `humanReadable()` but not strictly required
|
||||
|
||||
## Installation
|
||||
|
||||
@ -63,6 +67,14 @@ require 'vendor/autoload.php';
|
||||
|
||||
You can just download `src/RRule.php` and require it.
|
||||
|
||||
## Documentation
|
||||
|
||||
Complete doc is available in [the wiki](https://github.com/rlanvin/php-rrule/wiki).
|
||||
|
||||
## Contribution
|
||||
|
||||
Feel free to contribute! Just create a new issue or a new pull request.
|
||||
|
||||
## Note
|
||||
|
||||
I started this library because I wasn't happy with the existing implementations
|
||||
@ -79,9 +91,6 @@ respect of the RFC. This version is a bit strictier and will not accept many
|
||||
non-compliant combinations of rule parts, that the python version otherwise accepts.
|
||||
There are also some additional features in this version.
|
||||
|
||||
## Documentation
|
||||
|
||||
Complete doc is available in [the wiki](https://github.com/rlanvin/php-rrule/wiki).
|
||||
|
||||
## License
|
||||
|
||||
|
@ -8,6 +8,9 @@
|
||||
"require": {
|
||||
"php": ">=5.3.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-intl": "Intl extension is needed for humanReadable()"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": ["src/"]
|
||||
}
|
||||
|
341
src/RRule.php
341
src/RRule.php
@ -270,6 +270,7 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
|
||||
if ( $this->until && $this->count ) {
|
||||
throw new \InvalidArgumentException('The UNTIL or COUNT rule parts MUST NOT occur in the same rule');
|
||||
}
|
||||
|
||||
// infer necessary BYXXX rules from DTSTART, if not provided
|
||||
if ( ! (not_empty($parts['BYWEEKNO']) || not_empty($parts['BYYEARDAY']) || not_empty($parts['BYMONTHDAY']) || not_empty($parts['BYDAY'])) ) {
|
||||
switch ( $this->freq ) {
|
||||
@ -337,14 +338,15 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
|
||||
$this->bymonthday = array();
|
||||
$this->bymonthday_negative = array();
|
||||
foreach ( $parts['BYMONTHDAY'] as $value ) {
|
||||
$value = (int) $value;
|
||||
if ( ! $value || $value < -31 || $value > 31 ) {
|
||||
throw new \InvalidArgumentException('Invalid BYMONTHDAY value: '.$value.' (valid values are 1 to 31 or -31 to -1)');
|
||||
}
|
||||
if ( $value < 0 ) {
|
||||
$this->bymonthday_negative[] = (int) $value;
|
||||
$this->bymonthday_negative[] = $value;
|
||||
}
|
||||
else {
|
||||
$this->bymonthday[] = (int) $value;
|
||||
$this->bymonthday[] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -360,11 +362,12 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
|
||||
|
||||
$this->bysetpos = array();
|
||||
foreach ( $parts['BYYEARDAY'] as $value ) {
|
||||
$value = (int) $value;
|
||||
if ( ! $value || $value < -366 || $value > 366 ) {
|
||||
throw new \InvalidArgumentException('Invalid BYSETPOS value: '.$value.' (valid values are 1 to 366 or -366 to -1)');
|
||||
}
|
||||
|
||||
$this->byyearday[] = (int) $value;
|
||||
$this->byyearday[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
@ -380,10 +383,11 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
|
||||
|
||||
$this->byweekno = array();
|
||||
foreach ( $parts['BYWEEKNO'] as $value ) {
|
||||
$value = (int) $value;
|
||||
if ( ! $value || $value < -53 || $value > 53 ) {
|
||||
throw new \InvalidArgumentException('Invalid BYWEEKNO value: '.$value.' (valid values are 1 to 53 or -53 to -1)');
|
||||
}
|
||||
$this->byweekno[] = (int) $value;
|
||||
$this->byweekno[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
@ -396,10 +400,11 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
|
||||
|
||||
$this->bymonth = array();
|
||||
foreach ( $parts['BYMONTH'] as $value ) {
|
||||
$value = (int) $value;
|
||||
if ( $value < 1 || $value > 12 ) {
|
||||
throw new \InvalidArgumentException('Invalid BYMONTH value: '.$value);
|
||||
}
|
||||
$this->bymonth[] = (int) $value;
|
||||
$this->bymonth[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
@ -417,11 +422,12 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
|
||||
|
||||
$this->bysetpos = array();
|
||||
foreach ( $parts['BYSETPOS'] as $value ) {
|
||||
$value = (int) $value;
|
||||
if ( ! $value || $value < -366 || $value > 366 ) {
|
||||
throw new \InvalidArgumentException('Invalid BYSETPOS value: '.$value.' (valid values are 1 to 366 or -366 to -1)');
|
||||
}
|
||||
|
||||
$this->bysetpos[] = (int) $value;
|
||||
$this->bysetpos[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
@ -432,10 +438,11 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
|
||||
|
||||
$this->byhour = array();
|
||||
foreach ( $parts['BYHOUR'] as $value ) {
|
||||
$value = (int) $value;
|
||||
if ( $value < 0 || $value > 23 ) {
|
||||
throw new \InvalidArgumentException('Invalid BYHOUR value: '.$value);
|
||||
}
|
||||
$this->byhour[] = (int) $value;
|
||||
$this->byhour[] = $value;
|
||||
}
|
||||
|
||||
sort($this->byhour);
|
||||
@ -451,10 +458,11 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
|
||||
|
||||
$this->byminute = array();
|
||||
foreach ( $parts['BYMINUTE'] as $value ) {
|
||||
$value = (int) $value;
|
||||
if ( $value < 0 || $value > 59 ) {
|
||||
throw new \InvalidArgumentException('Invalid BYMINUTE value: '.$value);
|
||||
}
|
||||
$this->byminute[] = (int) $value;
|
||||
$this->byminute[] = $value;
|
||||
}
|
||||
sort($this->byminute);
|
||||
}
|
||||
@ -469,13 +477,14 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
|
||||
|
||||
$this->bysecond = array();
|
||||
foreach ( $parts['BYSECOND'] as $value ) {
|
||||
$value = (int) $value;
|
||||
// yes, "60" is a valid value, in (very rare) cases on leap seconds
|
||||
// December 31, 2005 23:59:60 UTC is a valid date...
|
||||
// so is 2012-06-30T23:59:60UTC
|
||||
if ( $value < 0 || $value > 60 ) {
|
||||
throw new \InvalidArgumentException('Invalid BYSECOND value: '.$value);
|
||||
}
|
||||
$this->bysecond[] = (int) $value;
|
||||
$this->bysecond[] = $value;
|
||||
}
|
||||
sort($this->bysecond);
|
||||
}
|
||||
@ -520,7 +529,7 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
|
||||
);
|
||||
}
|
||||
|
||||
$parts = [];
|
||||
$parts = array();
|
||||
foreach ( $this->rule as $key => $value ) {
|
||||
if ( $key === 'DTSTART' ) {
|
||||
continue;
|
||||
@ -1873,6 +1882,12 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
|
||||
|
||||
// i18n methods (could be moved into a separate class, since it's not always necessary)
|
||||
|
||||
/**
|
||||
* Stores translations once loaded (so we don't have to reload them all the time)
|
||||
*/
|
||||
static protected $i18n = array();
|
||||
static protected $intl_loaded = null;
|
||||
|
||||
/**
|
||||
* Select a translation in $array based on the value of $n
|
||||
* @return string
|
||||
@ -1917,165 +1932,95 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
|
||||
|
||||
/**
|
||||
* Load a translation file in memory.
|
||||
* Will load the basic first (e.g. "en") and then the region-specific (e.g. "en_GB")
|
||||
* Will load the basic first (e.g. "en") and then the region-specific if any
|
||||
* (e.g. "en_GB"), merging as necessary.
|
||||
* So region-specific translation files don't need to redefine every strings.
|
||||
*/
|
||||
static protected function i18nLoad($locale)
|
||||
{
|
||||
return array(
|
||||
'freq' => array(
|
||||
self::YEARLY => array(
|
||||
'1' => 'yearly',
|
||||
'else' => 'every %{interval} years'
|
||||
),
|
||||
self::MONTHLY => array(
|
||||
'1' => 'monthly',
|
||||
'else' => 'every %{interval} months'
|
||||
),
|
||||
self::WEEKLY => array(
|
||||
'1' => 'weekly',
|
||||
'2' => 'every other week',
|
||||
'else' => 'every %{interval} weeks'
|
||||
),
|
||||
self::DAILY => array(
|
||||
'1' => 'daily',
|
||||
'2' => 'every other day',
|
||||
'else' => 'every %{interval} days'
|
||||
),
|
||||
self::HOURLY => array(
|
||||
'1' => 'hourly',
|
||||
'else' => 'every %{interval} hours'
|
||||
),
|
||||
self::MINUTELY => array(
|
||||
'1' => 'minutely',
|
||||
'else' => 'every %{interval} minutes'
|
||||
),
|
||||
self::SECONDLY => array(
|
||||
'1' => 'secondly',
|
||||
'else' => 'every %{interval} seconds'
|
||||
),
|
||||
),
|
||||
'dtstart' => 'starting from %{date}',
|
||||
'infinite' => 'forever',
|
||||
'until' => 'until %{date}',
|
||||
'count' => array(
|
||||
'1' => 'one time',
|
||||
'else' => '%{count} times'
|
||||
),
|
||||
'and' => 'and',
|
||||
'bymonth' => array(
|
||||
'limit' => 'only in %{months}',
|
||||
'expand' => 'in %{months}',
|
||||
),
|
||||
'months' => array(
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
),
|
||||
'byweekday' => array(
|
||||
'limit' => 'only on %{weekdays}',
|
||||
'expand' => 'on %{weekdays}',
|
||||
),
|
||||
'weekdays' => array(
|
||||
1 => 'Monday',
|
||||
2 => 'Tuesday',
|
||||
3 => 'Wednesday',
|
||||
4 => 'Thursday',
|
||||
5 => 'Friday',
|
||||
6 => 'Saturday',
|
||||
7 => 'Sunday',
|
||||
),
|
||||
'nth_weekday' => array(
|
||||
'1' => 'the first %{weekday}', // e.g. the first Monday
|
||||
'2' => 'the second %{weekday}',
|
||||
'3' => 'the third %{weekday}',
|
||||
'else' => 'the %{n}th %{weekday}'
|
||||
),
|
||||
'-nth_weekday' => array(
|
||||
'-1' => 'the last %{weekday}',
|
||||
'-2' => 'the penultimate %{weekday}',
|
||||
'-3' => 'the antepenultimate %{weekday}',
|
||||
'else' => 'the %{n}th to the last %{weekday}'
|
||||
),
|
||||
'byweekno' => array(
|
||||
'1' => 'on week %{weeks}',
|
||||
'else' => 'on weeks number %{weeks}'
|
||||
),
|
||||
'nth_weekno' => '%{n}',
|
||||
'bymonthday' => array(
|
||||
'limit' => 'only on {%monthdays}',
|
||||
'expand' => 'on %{monthdays}'
|
||||
),
|
||||
'nth_monthday' => array(
|
||||
'1' => 'the 1st',
|
||||
'2' => 'the 2nd',
|
||||
'3' => 'the 3rd',
|
||||
'21' => 'the 21st',
|
||||
'22' => 'the 22nd',
|
||||
'23' => 'the 23rd',
|
||||
'31' => 'the 31st',
|
||||
'else' => 'the %{n}th'
|
||||
),
|
||||
'-nth_monthday' => array(
|
||||
'1' => 'the last day',
|
||||
'2' => 'the penultimate day',
|
||||
'3' => 'the antepenultimate day',
|
||||
'21' => 'the 21st to the last day',
|
||||
'22' => 'the 22nd to the last day',
|
||||
'23' => 'the 23rd to the last day',
|
||||
'31' => 'the 31st to the last day',
|
||||
'else' => 'the %{n}th to the last day'
|
||||
),
|
||||
'byyearday' => array(
|
||||
'limit' => 'only on {%yeardays}',
|
||||
'expand' => 'on %{yeardays}'
|
||||
),
|
||||
'nth_yearday' => array(
|
||||
'1' => 'the first day',
|
||||
'2' => 'the second day',
|
||||
'3' => 'the third day',
|
||||
'else' => 'the %{n}th day'
|
||||
),
|
||||
'-nth_yearday' => array(
|
||||
'-1' => 'the last day',
|
||||
'-2' => 'the penultimate day',
|
||||
'-3' => 'the antepenultimate day',
|
||||
'else' => 'the %{n}th to the last day'
|
||||
),
|
||||
'x_of_the_y' => array(
|
||||
self::YEARLY => '%{x} of the year',
|
||||
self::MONTHLY => '%{x} of the month',
|
||||
)
|
||||
);
|
||||
if ( ! preg_match('/^([a-z]{2})(_[A-Z]{2})?$/', $locale, $matches) ) {
|
||||
throw new \InvalidArgumentException('The locale option does not look like a valid locale: '.$opt['locale']);
|
||||
}
|
||||
|
||||
$files = array();
|
||||
if ( isset($matches[2]) ) {
|
||||
$files[] = $matches[1];
|
||||
}
|
||||
$files[] = $locale;
|
||||
|
||||
$result = array();
|
||||
foreach ( $files as $file ) {
|
||||
$path = __DIR__."/i18n/$file.php";
|
||||
if ( isset(self::$i18n[$file]) ) {
|
||||
$result = array_merge($result, self::$i18n[$file]);
|
||||
}
|
||||
elseif ( is_file($path) && is_readable($path) ) {
|
||||
self::$i18n[$file] = include $path;
|
||||
$result = array_merge($result, self::$i18n[$file]);
|
||||
}
|
||||
else {
|
||||
self::$i18n[$file] = array();
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty($result) ) {
|
||||
throw new \InvalidArgumentException("Failed to load $locale");
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a rule in a human readable string
|
||||
* intl extension is required.
|
||||
*/
|
||||
public function humanReadable(array $opt = array())
|
||||
{
|
||||
$opt = array_merge(array(
|
||||
if ( self::$intl_loaded === null ) {
|
||||
self::$intl_loaded = extension_loaded('intl');
|
||||
}
|
||||
|
||||
$default_opt = array(
|
||||
'locale' => \Locale::getDefault(),
|
||||
'date_format' => \IntlDateFormatter::SHORT,
|
||||
'time_format' => \IntlDateFormatter::SHORT,
|
||||
'date_formatter' => null
|
||||
), $opt);
|
||||
);
|
||||
|
||||
if ( self::$intl_loaded ) {
|
||||
$default_opt['date_format'] = \IntlDateFormatter::SHORT;
|
||||
if ( $this->freq >= self::SECONDLY || not_empty($this->rule['BYSECOND']) ) {
|
||||
$default_opt['time_format'] = \IntlDateFormatter::LONG;
|
||||
}
|
||||
elseif ( $this->freq >= self::HOURLY || not_empty($this->rule['BYHOUR']) || not_empty($this->rule['BYMINUTE']) ) {
|
||||
$default_opt['time_format'] = \IntlDateFormatter::SHORT;
|
||||
}
|
||||
else {
|
||||
$default_opt['time_format'] = \IntlDateFormatter::NONE;
|
||||
}
|
||||
}
|
||||
|
||||
$opt = array_merge($default_opt, $opt);
|
||||
|
||||
if ( $opt['date_formatter'] && ! is_callable($opt['date_formatter']) ) {
|
||||
throw new \InvalidArgumentException('The option date_formatter must callable');
|
||||
}
|
||||
|
||||
if ( ! $opt['date_formatter'] ) {
|
||||
$formatter = \IntlDateFormatter::create(
|
||||
$opt['locale'],
|
||||
$opt['date_format'],
|
||||
$opt['time_format'],
|
||||
date_default_timezone_get() // XXX should be $this->something?
|
||||
);
|
||||
if ( self::$intl_loaded ) {
|
||||
$formatter = \IntlDateFormatter::create(
|
||||
$opt['locale'],
|
||||
$opt['date_format'],
|
||||
$opt['time_format'],
|
||||
$this->dtstart->getTimezone()->getName()
|
||||
);
|
||||
$opt['date_formatter'] = function($date) use ($formatter) {
|
||||
return $formatter->format($date);
|
||||
};
|
||||
}
|
||||
else {
|
||||
$opt['date_formatter'] = function($date) {
|
||||
return $date->format('Y-m-d H:i:s');
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
$i18n = self::i18nLoad($opt['locale']);
|
||||
@ -2094,8 +2039,9 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
|
||||
);
|
||||
|
||||
// Every (INTERVAL) FREQ...
|
||||
$freq_str = strtolower(array_search($this->freq, self::$frequencies));
|
||||
$parts['freq'] = strtr(
|
||||
self::i18nSelect($i18n['freq'][$this->freq], $this->interval),
|
||||
self::i18nSelect($i18n[$freq_str], $this->interval),
|
||||
array(
|
||||
'%{interval}' => $this->interval
|
||||
)
|
||||
@ -2103,12 +2049,11 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
|
||||
|
||||
// BYXXX rules
|
||||
if ( $this->bymonth ) {
|
||||
$selector = $this->freq > self::YEARLY ? 'limit' : 'expand';
|
||||
$tmp = $this->bymonth;
|
||||
foreach ( $tmp as & $value) {
|
||||
$value = $i18n['months'][$value];
|
||||
}
|
||||
$parts['bymonth'] = strtr($i18n['bymonth'][$selector], array(
|
||||
$parts['bymonth'] = strtr(self::i18nSelect($i18n['bymonth'], count($tmp)), array(
|
||||
'%{months}' => self::i18nList($tmp, $i18n['and'])
|
||||
));
|
||||
}
|
||||
@ -2130,25 +2075,23 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
|
||||
}
|
||||
|
||||
if ( $this->byyearday ) {
|
||||
$selector = $this->freq > self::DAILY ? 'limit' : 'expand';
|
||||
$tmp = $this->byyearday;
|
||||
foreach ( $tmp as & $value ) {
|
||||
$value = strtr(self::i18nSelect($i18n[$value>0?'nth_yearday':'-nth_yearday'],$value), array(
|
||||
'%{n}' => abs($value)
|
||||
));
|
||||
}
|
||||
$tmp = strtr($i18n['byyearday'][$selector], array(
|
||||
$tmp = strtr(self::i18nSelect($i18n['byyearday'], count($tmp)), array(
|
||||
'%{yeardays}' => self::i18nList($tmp, $i18n['and'])
|
||||
));
|
||||
// ... of the month
|
||||
$tmp = strtr(self::i18nSelect($i18n['x_of_the_y'], self::YEARLY), array(
|
||||
$tmp = strtr(self::i18nSelect($i18n['x_of_the_y'], 'yearly'), array(
|
||||
'%{x}' => $tmp
|
||||
));
|
||||
$parts['byyearday'] = $tmp;
|
||||
}
|
||||
|
||||
if ( $this->bymonthday || $this->bymonthday_negative ) {
|
||||
$selector = $this->freq > self::DAILY ? 'limit' : 'expand';
|
||||
$parts['bymonthday'] = array();
|
||||
if ( $this->bymonthday ) {
|
||||
$tmp = $this->bymonthday;
|
||||
@ -2157,11 +2100,11 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
|
||||
'%{n}' => $value
|
||||
));
|
||||
}
|
||||
$tmp = strtr($i18n['bymonthday'][$selector], array(
|
||||
$tmp = strtr(self::i18nSelect($i18n['bymonthday'], count($tmp)), array(
|
||||
'%{monthdays}' => self::i18nList($tmp, $i18n['and'])
|
||||
));
|
||||
// ... of the month
|
||||
$tmp = strtr(self::i18nSelect($i18n['x_of_the_y'], self::MONTHLY), array(
|
||||
$tmp = strtr(self::i18nSelect($i18n['x_of_the_y'], 'monthly'), array(
|
||||
'%{x}' => $tmp
|
||||
));
|
||||
$parts['bymonthday'][] = $tmp;
|
||||
@ -2169,31 +2112,30 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
|
||||
if ( $this->bymonthday_negative ) {
|
||||
$tmp = $this->bymonthday_negative;
|
||||
foreach ( $tmp as & $value ) {
|
||||
$value = strtr(self::i18nSelect($i18n['-nth_monthday'],-$value), array(
|
||||
$value = strtr(self::i18nSelect($i18n['-nth_monthday'],$value), array(
|
||||
'%{n}' => -$value
|
||||
));
|
||||
}
|
||||
$tmp = strtr($i18n['bymonthday'][$selector], array(
|
||||
$tmp = strtr(self::i18nSelect($i18n['bymonthday'], count($tmp)), array(
|
||||
'%{monthdays}' => self::i18nList($tmp, $i18n['and'])
|
||||
));
|
||||
// ... of the month
|
||||
$tmp = strtr(self::i18nSelect($i18n['x_of_the_y'], self::MONTHLY), array(
|
||||
$tmp = strtr(self::i18nSelect($i18n['x_of_the_y'], 'monthly'), array(
|
||||
'%{x}' => $tmp
|
||||
));
|
||||
$parts['bymonthday'][] = $tmp;
|
||||
}
|
||||
$parts['bymonthday'] = implode(' '.$i18n['and'].' ',$parts['bymonthday']);
|
||||
$parts['bymonthday'] = implode(' '.$i18n['and'],$parts['bymonthday']);
|
||||
}
|
||||
|
||||
if ( $this->byweekday || $this->byweekday_nth ) {
|
||||
$parts['byweekday'] = array();
|
||||
$selector = $this->freq >= self::DAILY ? 'limit' : 'expand';
|
||||
if ( $this->byweekday ) {
|
||||
$tmp = $this->byweekday;
|
||||
foreach ( $tmp as & $value ) {
|
||||
$value = $i18n['weekdays'][$value];
|
||||
}
|
||||
$parts['byweekday'][] = strtr($i18n['byweekday'][$selector], array(
|
||||
$parts['byweekday'][] = strtr(self::i18nSelect($i18n['byweekday'], count($tmp)), array(
|
||||
'%{weekdays}' => self::i18nList($tmp, $i18n['and'])
|
||||
));
|
||||
}
|
||||
@ -2206,37 +2148,70 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
|
||||
'%{n}' => abs($n)
|
||||
));
|
||||
}
|
||||
$tmp = strtr($i18n['byweekday'][$selector], array(
|
||||
$tmp = strtr(self::i18nSelect($i18n['byweekday'], count($tmp)), array(
|
||||
'%{weekdays}' => self::i18nList($tmp, $i18n['and'])
|
||||
));
|
||||
// ... of the year|month
|
||||
$tmp = strtr(self::i18nSelect($i18n['x_of_the_y'], $this->freq), array(
|
||||
$tmp = strtr(self::i18nSelect($i18n['x_of_the_y'], $freq_str), array(
|
||||
'%{x}' => $tmp
|
||||
));
|
||||
$parts['byweekday'][] = $tmp;
|
||||
}
|
||||
$parts['byweekday'] = implode(' '.$i18n['and'].' ',$parts['byweekday']);
|
||||
$parts['byweekday'] = implode(' '.$i18n['and'],$parts['byweekday']);
|
||||
}
|
||||
|
||||
if ( $this->byhour ) {
|
||||
|
||||
if ( not_empty($this->rule['BYHOUR']) ) {
|
||||
$tmp = $this->byhour;
|
||||
foreach ( $tmp as &$value) {
|
||||
$value = strtr($i18n['nth_hour'], array(
|
||||
'%{n}' => $value
|
||||
));
|
||||
}
|
||||
$parts['byhour'] = strtr(self::i18nSelect($i18n['byhour'],count($tmp)), array(
|
||||
'%{hours}' => self::i18nList($tmp, $i18n['and'])
|
||||
));
|
||||
}
|
||||
|
||||
if ( $this->byminute ) {
|
||||
|
||||
if ( not_empty($this->rule['BYMINUTE']) ) {
|
||||
$tmp = $this->byminute;
|
||||
foreach ( $tmp as &$value) {
|
||||
$value = strtr($i18n['nth_minute'], array(
|
||||
'%{n}' => $value
|
||||
));
|
||||
}
|
||||
$parts['byminute'] = strtr(self::i18nSelect($i18n['byminute'],count($tmp)), array(
|
||||
'%{minutes}' => self::i18nList($tmp, $i18n['and'])
|
||||
));
|
||||
}
|
||||
|
||||
if ( $this->bysecond ) {
|
||||
|
||||
if ( not_empty($this->rule['BYSECOND']) ) {
|
||||
$tmp = $this->bysecond;
|
||||
foreach ( $tmp as &$value) {
|
||||
$value = strtr($i18n['nth_second'], array(
|
||||
'%{n}' => $value
|
||||
));
|
||||
}
|
||||
$parts['bysecond'] = strtr(self::i18nSelect($i18n['bysecond'],count($tmp)), array(
|
||||
'%{seconds}' => self::i18nList($tmp, $i18n['and'])
|
||||
));
|
||||
}
|
||||
|
||||
if ( $this->bysetpos ) {
|
||||
|
||||
$tmp = $this->bysetpos;
|
||||
foreach ( $tmp as & $value ) {
|
||||
$value = strtr(self::i18nSelect($i18n[$value>0?'nth_setpos':'-nth_setpos'],$value), array(
|
||||
'%{n}' => abs($value)
|
||||
));
|
||||
}
|
||||
$tmp = strtr(self::i18nSelect($i18n['bysetpos'], count($tmp)), array(
|
||||
'%{setpos}' => self::i18nList($tmp, $i18n['and'])
|
||||
));
|
||||
$parts['bysetpos'] = $tmp;
|
||||
}
|
||||
|
||||
// from X
|
||||
$parts['start'] = strtr($i18n['dtstart'], array(
|
||||
'%{date}' => $formatter->format($this->dtstart)
|
||||
'%{date}' => $opt['date_formatter']($this->dtstart)
|
||||
));
|
||||
|
||||
// to X, or N times, or indefinitely
|
||||
@ -2245,7 +2220,7 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
|
||||
}
|
||||
elseif ( $this->until ) {
|
||||
$parts['end'] = strtr($i18n['until'], array(
|
||||
'%{date}' => $formatter->format($this->until)
|
||||
'%{date}' => $opt['date_formatter']($this->until)
|
||||
));
|
||||
}
|
||||
elseif ( $this->count ) {
|
||||
@ -2264,7 +2239,7 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
|
||||
// '%{byday}' => $parts['byday'],
|
||||
// ));
|
||||
$parts = array_filter($parts);
|
||||
$str = implode(', ',$parts);
|
||||
$str = implode('',$parts);
|
||||
return $str;
|
||||
}
|
||||
|
||||
|
167
src/i18n/en.php
Executable file
167
src/i18n/en.php
Executable file
@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Translation file for English language.
|
||||
*
|
||||
* Most strings can be an array, with a value as the key. The system will
|
||||
* pick the translation corresponding to the key. The key "else" will be picked
|
||||
* if no matching value is found. This is useful for plurals.
|
||||
*
|
||||
* Licensed under the MIT license.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE file.
|
||||
*
|
||||
* @author Rémi Lanvin <remi@cloudconnected.fr>
|
||||
* @link https://github.com/rlanvin/php-rrule
|
||||
*/
|
||||
return array(
|
||||
'yearly' => array(
|
||||
'1' => 'yearly',
|
||||
'else' => 'every %{interval} years'
|
||||
),
|
||||
'monthly' => array(
|
||||
'1' => 'monthly',
|
||||
'else' => 'every %{interval} months'
|
||||
),
|
||||
'weekly' => array(
|
||||
'1' => 'weekly',
|
||||
'2' => 'every other week',
|
||||
'else' => 'every %{interval} weeks'
|
||||
),
|
||||
'daily' => array(
|
||||
'1' => 'daily',
|
||||
'2' => 'every other day',
|
||||
'else' => 'every %{interval} days'
|
||||
),
|
||||
'hourly' => array(
|
||||
'1' => 'hourly',
|
||||
'else' => 'every %{interval} hours'
|
||||
),
|
||||
'minutely' => array(
|
||||
'1' => 'minutely',
|
||||
'else' => 'every %{interval} minutes'
|
||||
),
|
||||
'secondly' => array(
|
||||
'1' => 'secondly',
|
||||
'else' => 'every %{interval} seconds'
|
||||
),
|
||||
'dtstart' => ', starting from %{date}',
|
||||
'infinite' => ', forever',
|
||||
'until' => ', until %{date}',
|
||||
'count' => array(
|
||||
'1' => ', one time',
|
||||
'else' => ', %{count} times'
|
||||
),
|
||||
'and' => 'and',
|
||||
'x_of_the_y' => array(
|
||||
'yearly' => '%{x} of the year', // e.g. the first Monday of the year, or the first day of the year
|
||||
'monthly' => '%{x} of the month',
|
||||
),
|
||||
'bymonth' => ' in %{months}',
|
||||
'months' => array(
|
||||
1 => 'January',
|
||||
2 => 'February',
|
||||
3 => 'March',
|
||||
4 => 'April',
|
||||
5 => 'May',
|
||||
6 => 'June',
|
||||
7 => 'July',
|
||||
8 => 'August',
|
||||
9 => 'September',
|
||||
10 => 'October',
|
||||
11 => 'November',
|
||||
12 => 'December',
|
||||
),
|
||||
'byweekday' => ' on %{weekdays}',
|
||||
'weekdays' => array(
|
||||
1 => 'Monday',
|
||||
2 => 'Tuesday',
|
||||
3 => 'Wednesday',
|
||||
4 => 'Thursday',
|
||||
5 => 'Friday',
|
||||
6 => 'Saturday',
|
||||
7 => 'Sunday',
|
||||
),
|
||||
'nth_weekday' => array(
|
||||
'1' => 'the first %{weekday}', // e.g. the first Monday
|
||||
'2' => 'the second %{weekday}',
|
||||
'3' => 'the third %{weekday}',
|
||||
'else' => 'the %{n}th %{weekday}'
|
||||
),
|
||||
'-nth_weekday' => array(
|
||||
'-1' => 'the last %{weekday}', // e.g. the last Monday
|
||||
'-2' => 'the penultimate %{weekday}',
|
||||
'-3' => 'the antepenultimate %{weekday}',
|
||||
'else' => 'the %{n}th to the last %{weekday}'
|
||||
),
|
||||
'byweekno' => array(
|
||||
'1' => ' on week %{weeks}',
|
||||
'else' => ' on weeks number %{weeks}'
|
||||
),
|
||||
'nth_weekno' => '%{n}',
|
||||
'bymonthday' => ' on %{monthdays}',
|
||||
'nth_monthday' => array(
|
||||
'1' => 'the 1st',
|
||||
'2' => 'the 2nd',
|
||||
'3' => 'the 3rd',
|
||||
'21' => 'the 21st',
|
||||
'22' => 'the 22nd',
|
||||
'23' => 'the 23rd',
|
||||
'31' => 'the 31st',
|
||||
'else' => 'the %{n}th'
|
||||
),
|
||||
'-nth_monthday' => array(
|
||||
'-1' => 'the last day',
|
||||
'-2' => 'the penultimate day',
|
||||
'-3' => 'the antepenultimate day',
|
||||
'-21' => 'the 21st to the last day',
|
||||
'-22' => 'the 22nd to the last day',
|
||||
'-23' => 'the 23rd to the last day',
|
||||
'-31' => 'the 31st to the last day',
|
||||
'else' => 'the %{n}th to the last day'
|
||||
),
|
||||
'byyearday' => array(
|
||||
'1' => ' on %{yeardays} day',
|
||||
'else' => ' on %{yeardays} days'
|
||||
),
|
||||
'nth_yearday' => array(
|
||||
'1' => 'the first',
|
||||
'2' => 'the second',
|
||||
'3' => 'the third',
|
||||
'else' => 'the %{n}th'
|
||||
),
|
||||
'-nth_yearday' => array(
|
||||
'-1' => 'the last',
|
||||
'-2' => 'the penultimate',
|
||||
'-3' => 'the antepenultimate',
|
||||
'else' => 'the %{n}th to the last'
|
||||
),
|
||||
'byhour' => array(
|
||||
'1' => ' at %{hours}',
|
||||
'else' => ' at %{hours}'
|
||||
),
|
||||
'nth_hour' => '%{n}h',
|
||||
'byminute' => array(
|
||||
'1' => ' at minute %{minutes}',
|
||||
'else' => ' at minutes %{minutes}'
|
||||
),
|
||||
'nth_minute' => '%{n}',
|
||||
'bysecond' => array(
|
||||
'1' => ' at second %{seconds}',
|
||||
'else' => ' at seconds %{seconds}'
|
||||
),
|
||||
'nth_second' => '%{n}',
|
||||
'bysetpos' => ', but only %{setpos} instance of this set',
|
||||
'nth_setpos' => array(
|
||||
'1' => 'the first',
|
||||
'2' => 'the second',
|
||||
'3' => 'the third',
|
||||
'else' => 'the %{n}th'
|
||||
),
|
||||
'-nth_setpos' => array(
|
||||
'-1' => 'the last',
|
||||
'-2' => 'the penultimate',
|
||||
'-3' => 'the antepenultimate',
|
||||
'else' => 'the %{n}th to the last'
|
||||
)
|
||||
);
|
155
src/i18n/fr.php
Executable file
155
src/i18n/fr.php
Executable file
@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Translation file for French language.
|
||||
*
|
||||
* Licensed under the MIT license.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE file.
|
||||
*
|
||||
* @author Rémi Lanvin <remi@cloudconnected.fr>
|
||||
* @link https://github.com/rlanvin/php-rrule
|
||||
*/
|
||||
return array(
|
||||
'yearly' => array(
|
||||
'1' => 'tous les ans',
|
||||
'2' => 'un an sur deux',
|
||||
'else' => 'tous les %{interval} ans'
|
||||
),
|
||||
'monthly' => array(
|
||||
'1' => 'tous les mois',
|
||||
'2' => 'un mois sur deux',
|
||||
'else' => 'tous les %{interval} mois'
|
||||
),
|
||||
'weekly' => array(
|
||||
'1' => 'toutes les semaines',
|
||||
'2' => 'une semaine sur deux',
|
||||
'else' => 'toutes les %{interval} semaines'
|
||||
),
|
||||
'daily' => array(
|
||||
'1' => 'tous les jours',
|
||||
'2' => 'un jour sur deux',
|
||||
'else' => 'tous les %{interval} jours'
|
||||
),
|
||||
'hourly' => array(
|
||||
'1' => 'toutes les heures',
|
||||
'else' => 'toutes les %{interval} heures'
|
||||
),
|
||||
'minutely' => array(
|
||||
'1' => 'toutes les minutes',
|
||||
'else' => 'toutes les %{interval} minutes'
|
||||
),
|
||||
'secondly' => array(
|
||||
'1' => 'toutes les secondes',
|
||||
'else' => 'toutes les %{interval} secondes'
|
||||
),
|
||||
'dtstart' => ', à partir du %{date}',
|
||||
'infinite' => ', indéfiniment',
|
||||
'until' => ', jusqu\'au %{date}',
|
||||
'count' => array(
|
||||
'1' => ', une fois',
|
||||
'else' => ', %{count} fois'
|
||||
),
|
||||
'and' => 'et',
|
||||
'x_of_the_y' => array(
|
||||
'yearly' => '%{x} de l\'année', // e.g. the first Monday of the year, or the first day of the year
|
||||
'monthly' => '%{x} du mois',
|
||||
),
|
||||
'bymonth' => ' en %{months}',
|
||||
'months' => array(
|
||||
1 => 'janvier',
|
||||
2 => 'février',
|
||||
3 => 'mars',
|
||||
4 => 'avril',
|
||||
5 => 'mai',
|
||||
6 => 'juin',
|
||||
7 => 'juillet',
|
||||
8 => 'août',
|
||||
9 => 'septembre',
|
||||
10 => 'octobre',
|
||||
11 => 'november',
|
||||
12 => 'décembre',
|
||||
),
|
||||
'byweekday' => ' le %{weekdays}',
|
||||
'weekdays' => array(
|
||||
1 => 'lundi',
|
||||
2 => 'mardi',
|
||||
3 => 'mercredi',
|
||||
4 => 'jeudi',
|
||||
5 => 'vendredi',
|
||||
6 => 'samedi',
|
||||
7 => 'dimanche',
|
||||
),
|
||||
'nth_weekday' => array(
|
||||
'1' => 'le 1er %{weekday}', // e.g. the first Monday
|
||||
'else' => 'le %{n}e %{weekday}'
|
||||
),
|
||||
'-nth_weekday' => array(
|
||||
'-1' => 'le dernier %{weekday}', // e.g. the last Monday
|
||||
'-2' => 'l\'avant-dernier %{weekday}',
|
||||
'-3' => 'l\'antépénultième %{weekday}',
|
||||
'else' => 'le %{n}e %{weekday} en partant de la fin'
|
||||
),
|
||||
'byweekno' => array(
|
||||
'1' => ' la semaine %{weeks}',
|
||||
'else' => ' les semaines %{weeks}'
|
||||
),
|
||||
'nth_weekno' => '%{n}',
|
||||
'bymonthday' => array(
|
||||
'1' => ' %{monthdays}',
|
||||
'else' => ' %{monthdays}'
|
||||
),
|
||||
'nth_monthday' => array(
|
||||
'1' => 'le 1er',
|
||||
'else' => 'le %{n}'
|
||||
),
|
||||
'-nth_monthday' => array(
|
||||
'-1' => 'le dernier jour',
|
||||
'-2' => 'l\'avant-dernier jour',
|
||||
'-3' => 'l\'antépénultième jour',
|
||||
'else' => 'le %{n}e jour en partant de la fin'
|
||||
),
|
||||
'byyearday' => array(
|
||||
'1' => ' le %{yeardays} jour',
|
||||
'else' => ' les %{yeardays} jours'
|
||||
),
|
||||
'nth_yearday' => array(
|
||||
'1' => '1er',
|
||||
'else' => '%{n}e'
|
||||
),
|
||||
'-nth_yearday' => array(
|
||||
'-1' => 'dernier',
|
||||
'-2' => 'avant-dernier',
|
||||
'-3' => 'antépénultième',
|
||||
'else' => '%{n}e en partant de la fin'
|
||||
),
|
||||
'byhour' => array(
|
||||
'1' => ' à %{hours}',
|
||||
'else' => ' à %{hours}'
|
||||
),
|
||||
'nth_hour' => '%{n}h',
|
||||
'byminute' => array(
|
||||
'1' => ' à %{minutes}',
|
||||
'else' => ' à %{minutes}'
|
||||
),
|
||||
'nth_minute' => '%{n}min',
|
||||
'bysecond' => array(
|
||||
'1' => ' à %{seconds}',
|
||||
'else' => ' à %{seconds}'
|
||||
),
|
||||
'nth_second' => '%{n}sec',
|
||||
'bysetpos' => array(
|
||||
'1' => ', mais seulement %{setpos} occurrence',
|
||||
'else' => ', mais seulement %{setpos} occurrence'
|
||||
),
|
||||
'nth_setpos' => array(
|
||||
'1' => 'la 1re',
|
||||
'else' => 'la %{n}e'
|
||||
),
|
||||
'-nth_setpos' => array(
|
||||
'-1' => 'la dernière',
|
||||
'-2' => 'l\'avant-dernière',
|
||||
'-3' => 'l\'antépénultième',
|
||||
'else' => 'la %{n}e en partant de la fin'
|
||||
)
|
||||
);
|
@ -17,6 +17,7 @@ class RRuleTest extends PHPUnit_Framework_TestCase
|
||||
array(array('FREQ' => 'DAILY', 'COUNT' => -1)),
|
||||
array(array('FREQ' => 'DAILY', 'UNTIL' => '2015-07-01', 'COUNT' => 1)),
|
||||
|
||||
array(array('FREQ' => 'YEARLY', 'BYDAY' => '1MO,X')),
|
||||
// The BYDAY rule part MUST NOT be specified with a numeric value
|
||||
// when the FREQ rule part is not set to MONTHLY or YEARLY.
|
||||
array(array('FREQ' => 'DAILY', 'BYDAY' => array('1MO'))),
|
||||
@ -28,12 +29,14 @@ class RRuleTest extends PHPUnit_Framework_TestCase
|
||||
array(array('FREQ' => 'DAILY', 'BYMONTHDAY' => 0)),
|
||||
array(array('FREQ' => 'DAILY', 'BYMONTHDAY' => 32)),
|
||||
array(array('FREQ' => 'DAILY', 'BYMONTHDAY' => -32)),
|
||||
array(array('FREQ' => 'DAILY', 'BYMONTHDAY' => '1,A')),
|
||||
// The BYMONTHDAY rule part MUST NOT be specified when the FREQ rule
|
||||
// part is set to WEEKLY.
|
||||
array(array('FREQ' => 'WEEKLY', 'BYMONTHDAY' => 1)),
|
||||
|
||||
array(array('FREQ' => 'YEARLY', 'BYYEARDAY' => 0)),
|
||||
array(array('FREQ' => 'YEARLY', 'BYYEARDAY' => 367)),
|
||||
array(array('FREQ' => 'YEARLY', 'BYYEARDAY' => '1,A')),
|
||||
// The BYYEARDAY rule part MUST NOT be specified when the FREQ
|
||||
// rule part is set to DAILY, WEEKLY, or MONTHLY.
|
||||
array(array('FREQ' => 'DAILY', 'BYYEARDAY' => 1)),
|
||||
@ -43,6 +46,7 @@ class RRuleTest extends PHPUnit_Framework_TestCase
|
||||
// BYSETPOS rule part MUST only be used in conjunction with another
|
||||
// BYxxx rule part.
|
||||
array(array('FREQ' => 'DAILY', 'BYSETPOS' => -1)),
|
||||
array(array('FREQ' => 'DAILY', 'BYDAY' => 'MO', 'BYSETPOS' => '1,A')),
|
||||
);
|
||||
}
|
||||
|
||||
@ -1574,7 +1578,6 @@ class RRuleTest extends PHPUnit_Framework_TestCase
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider notOccurrences
|
||||
*/
|
||||
@ -1586,6 +1589,47 @@ class RRuleTest extends PHPUnit_Framework_TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function rfcStrings()
|
||||
{
|
||||
return array(
|
||||
array('DTSTART;TZID=America/New_York:19970901T090000
|
||||
RRULE:FREQ=HOURLY;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR;BYMONTH=1;BYHOUR=1'),
|
||||
array('DTSTART;TZID=America/New_York:19970901T090000
|
||||
RRULE:FREQ=DAILY;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR;BYMONTH=1'),
|
||||
array('DTSTART;TZID=America/New_York:19970901T090000
|
||||
RRULE:FREQ=DAILY;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR;BYMONTH=1;BYHOUR=12;BYMINUTE=15,30'),
|
||||
array('DTSTART;TZID=America/New_York:19970901T090000
|
||||
RRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR'),
|
||||
array('DTSTART;TZID=America/New_York:19970901T090000
|
||||
RRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR'),
|
||||
array('DTSTART;TZID=America/New_York:19970901T090000
|
||||
RRULE:FREQ=MONTHLY;INTERVAL=1;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR;BYMONTH=1'),
|
||||
array('DTSTART;TZID=America/New_York:19970901T090000
|
||||
RRULE:FREQ=MONTHLY;INTERVAL=1;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR;BYMONTHDAY=1,2,5,31,-1,-3,-15'),
|
||||
array('DTSTART;TZID=America/New_York:19970901T090000
|
||||
RRULE:FREQ=MONTHLY;INTERVAL=1;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR;BYMONTHDAY=1,2,5,31,-1,-3,-15;BYSETPOS=-1'),
|
||||
array('DTSTART;TZID=America/New_York:19970901T090000
|
||||
RRULE:FREQ=MONTHLY;INTERVAL=1;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR;BYMONTHDAY=1,2,5,31,-1,-3,-15;BYSETPOS=-1,1'),
|
||||
array(' DTSTART;TZID=America/New_York:19970512T090000
|
||||
RRULE:FREQ=YEARLY;BYWEEKNO=20,30,40;BYDAY=MO'),
|
||||
array(' DTSTART;TZID=America/New_York:19970512T090000
|
||||
RRULE:FREQ=YEARLY;BYYEARDAY=1,-1,10,-50;BYDAY=MO')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider rfcStrings
|
||||
*/
|
||||
public function testRfcStrings($str)
|
||||
{
|
||||
$rule = new RRule($str);
|
||||
// test that parsing the string produces the same result
|
||||
// as generating the string from a rule
|
||||
$this->assertEquals($rule, new RRule($rule->rfcString()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function testIsLeapYear()
|
||||
{
|
||||
$this->assertFalse(\RRule\is_leap_year(1700));
|
||||
|
Loading…
x
Reference in New Issue
Block a user