Merged tpruvot's fork PHP-iCal (http://github.com/tpruvot/PHP-iCal). Great work tpruvot!

master
Morten Fangel 2010-11-01 19:56:01 +01:00
commit c4042c1628
25 zmienionych plików z 6359 dodań i 308 usunięć

2
.gitignore vendored 100644
Wyświetl plik

@ -0,0 +1,2 @@
/sgical.php

54
README 100644
Wyświetl plik

@ -0,0 +1,54 @@
A simple and fast iCal parser.
-------------------------------------------------------------------------------
http://github.com/fangel/SG-iCalendar
With massive help from http://github.com/tpruvot/PHP-iCal
and http://github.com/xonev/SG-iCalendar
-------------------------------------------------------------------------------
A simple example :
$ical = new SG_iCalReader( "./basic.ics" );
//or
$ical = new SG_iCalReader( "http://example.com/calendar.ics" );
foreach( $ical->getEvents() As $event ) {
// Do stuff with the event $event
}
To check unit tests with phpunit, goto tests/ directory and :
phpunit AllTests
phpunit helpers/FreqTest
-------------------------------------------------------------------------------
CHANGELOG :
-------------------------------------------------------------------------------
current (31 oct 2010)
+ ical RDATE support (added dates in a range)
+ RDATE and EXDATE arrays support
0.7.0 (30 oct 2010)
+ ical EXDATE support (excluded dates in a range)
+ $event->isWholeDay()
+ getAllOccurrences() for repeated events
+ implemented a cache for repeated events
0.6.0 (29 oct 2010)
+ Added demo based on fullcalendar
+ Added duration unit tests
+ Support of Recurrent events in query Between()
* various fixes on actual (5) issues
-------------------------------------------------------------------------------
TODO :
-------------------------------------------------------------------------------
These iCal keywords are not supported for the moment :
- RECURRENCE-ID : to move one event from a recurrence
- EXRULE : to exclude multiple days by a complex rule
Also, multiple RRULE could be specified for an event,
but that is not the case for most calendar applications
-------------------------------------------------------------------------------
To get more information about ical format and rules :
see http://www.ietf.org/rfc/rfc2445.txt

Wyświetl plik

@ -1,17 +1,18 @@
<?php
define('SG_ICALREADER_VERSION', '0.5');
define('SG_ICALREADER_VERSION', '0.7.0');
/**
* A simple iCal parser. Should take care of most stuff for ya
* http://github.com/fangel/SG-iCalendar
*
* Roadmap:
* * Finish FREQUENCY-parsing.
* * Add API for recurring events
*
*
* A simple example:
* <?php
* $ical = new SG_iCal("http://example.com/calendar.ics");
* $ical = new SG_iCalReader("http://example.com/calendar.ics");
* foreach( $ical->getEvents() As $event ) {
* // Do stuff with the event $event
* }
@ -19,26 +20,32 @@ define('SG_ICALREADER_VERSION', '0.5');
*
* @package SG_iCalReader
* @author Morten Fangel (C) 2008
* @author xonev (C) 2010
* @author Tanguy Pruvot (C) 2010
* @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK
*/
class SG_iCal {
private $information;
private $events;
private $timezones;
//objects
public $information; //SG_iCal_VCalendar
public $timezones; //SG_iCal_VTimeZone
protected $events; //SG_iCal_VEvent[]
/**
* Constructs a new iCalReader. You can supply the url now, or later using setUrl
* @param $url string
*/
public function __construct($url = false) {
require_once dirname(__FILE__) . '/helpers/SG_iCal_Factory.php'; // BUILD: Remove line
require_once dirname(__FILE__) . '/helpers/SG_iCal_Line.php'; // BUILD: Remove line
require_once dirname(__FILE__) . '/helpers/SG_iCal_Query.php'; // BUILD: Remove line
require_once dirname(__FILE__) . '/helpers/SG_iCal_Duration.php'; // BUILD: Remove line
require_once dirname(__FILE__) . '/helpers/SG_iCal_Freq.php'; // BUILD: Remove line
require_once dirname(__FILE__) . '/helpers/SG_iCal_Recurrence.php'; // BUILD: Remove line
require_once dirname(__FILE__) . '/helpers/SG_iCal_Parser.php'; // BUILD: Remove line
require_once dirname(__FILE__) . '/helpers/SG_iCal_Query.php'; // BUILD: Remove line
require_once dirname(__FILE__) . '/helpers/SG_iCal_Factory.php'; // BUILD: Remove line
if( $url !== false ) {
SG_iCal_Parser::Parse($url, $this);
}
$this->setUrl($url);
}
/**
@ -50,16 +57,16 @@ class SG_iCal {
SG_iCal_Parser::Parse($url, $this);
}
}
/**
* Returns the main calendar info. You can then query the returned
* object with ie getTitle().
* object with ie getTitle().
* @return SG_iCal_VCalendar
*/
public function getCalendarInfo() {
return $this->information;
}
/**
* Sets the calendar info for this calendar
* @param SG_iCal_VCalendar $info
@ -67,8 +74,8 @@ class SG_iCal {
public function setCalendarInfo( SG_iCal_VCalendar $info ) {
$this->information = $info;
}
/**
* Returns a given timezone for the calendar. This is mainly used
* by VEvents to adjust their date-times if they have specified a
@ -95,7 +102,7 @@ class SG_iCal {
return null;
}
}
/**
* Adds a new timezone to this calendar
* @param SG_iCal_VTimeZone $tz
@ -103,7 +110,7 @@ class SG_iCal {
public function addTimeZone( SG_iCal_VTimeZone $tz ) {
$this->timezones[] = $tz;
}
/**
* Returns the events found
* @return array

Wyświetl plik

@ -10,17 +10,17 @@
* @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK
*/
class SG_iCal_VCalendar implements IteratorAggregate {
private $data;
protected $data;
/**
* Creates a new SG_iCal_VCalendar.
*/
public function __construct($data) {
$this->data = $data;
}
/**
* Returns the title of the calendar. If no title is known, NULL
* Returns the title of the calendar. If no title is known, NULL
* will be returned
* @return string
*/
@ -31,7 +31,7 @@ class SG_iCal_VCalendar implements IteratorAggregate {
return null;
}
}
/**
* Returns the description of the calendar. If no description is
* known, NULL will be returned.
@ -44,7 +44,7 @@ class SG_iCal_VCalendar implements IteratorAggregate {
return null;
}
}
/**
* @see IteratorAggregate.getIterator()
*/

Wyświetl plik

@ -1,12 +1,12 @@
<?php // BUILD: Remove line
/**
* The wrapper for vevents. Will reveal a unified and simple api for
* The wrapper for vevents. Will reveal a unified and simple api for
* the events, which include always finding a start and end (except
* when no end or duration is given) and checking if the event is
* when no end or duration is given) and checking if the event is
* blocking or similar.
*
* Will apply the specified timezone to timestamps if a tzid is
* Will apply the specified timezone to timestamps if a tzid is
* specified
*
* @package SG_iCalReader
@ -15,22 +15,36 @@
*/
class SG_iCal_VEvent {
const DEFAULT_CONFIRMED = true;
private $uid;
private $start;
private $end;
private $recurrence;
private $summary;
private $description;
private $location;
private $data;
protected $uid;
protected $start;
protected $end;
protected $summary;
protected $description;
protected $location;
protected $laststart;
protected $lastend;
public $recurrence; //RRULE
public $recurex; //EXRULE
public $excluded; //EXDATE(s)
public $added; //RDATE(s)
public $freq; //getFrequency() SG_iCal_Freq
public $data;
/**
* Constructs a new SG_iCal_VEvent. Needs the SG_iCalReader
* Constructs a new SG_iCal_VEvent. Needs the SG_iCalReader
* supplied so it can query for timezones.
* @param SG_iCal_Line[] $data
* @param SG_iCalReader $ical
*/
public function __construct($data, SG_iCal $ical ) {
public function __construct($data, SG_iCal $ical) {
$this->uid = $data['uid']->getData();
unset($data['uid']);
@ -38,33 +52,66 @@ class SG_iCal_VEvent {
$this->recurrence = new SG_iCal_Recurrence($data['rrule']);
unset($data['rrule']);
}
if ( isset($data['exrule']) ) {
$this->recurex = new SG_iCal_Recurrence($data['exrule']);
unset($data['exrule']);
}
if( isset($data['dtstart']) ) {
$this->start = $this->getTimestamp( $data['dtstart'], $ical );
$this->start = $this->getTimestamp($data['dtstart'], $ical);
unset($data['dtstart']);
}
if( isset($data['dtend']) ) {
$this->end = $this->getTimestamp($data['dtend'], $ical);
unset($data['dtend']);
} elseif( isset($data['duration']) ) {
require_once dirname(__FILE__).'/../helpers/SG_iCal_Duration.php'; // BUILD: Remove line
$dur = new SG_iCal_Duration( $data['duration']->getData() );
$this->end = $this->start + $dur->getDuration();
unset($data['duration']);
} elseif ( isset($this->recurrence) ) {
}
//google cal set dtend as end of initial event (duration)
if ( isset($this->recurrence) ) {
//if there is a recurrence rule
//exclusions
if ( isset($data['exdate']) ) {
foreach ($data['exdate'] as $exdate) {
foreach ($exdate->getDataAsArray() as $ts) {
$this->excluded[] = strtotime($ts);
}
}
unset($data['exdate']);
}
//additions
if ( isset($data['rdate']) ) {
foreach ($data['rdate'] as $rdate) {
foreach ($rdate->getDataAsArray() as $ts) {
$this->added[] = strtotime($ts);
}
}
unset($data['rdate']);
}
$until = $this->recurrence->getUntil();
$count = $this->recurrence->getCount();
//check if there is either 'until' or 'count' set
if ( $this->recurrence->getUntil() or $this->recurrence->getCount() ) {
//if until is set, set that as the end date (using getTimeStamp)
if ( $until ) {
$this->end = strtotime( $until );
}
if ( $until ) {
//ok..
} elseif ($count) {
//if count is set, then figure out the last occurrence and set that as the end date
$this->getFrequency();
$until = $this->freq->lastOccurrence($this->start);
} else {
//forever... limit to 3 years
$this->recurrence->setUntil('+3 years');
$until = $this->recurrence->getUntil();
}
//date_default_timezone_set( xx ) needed ?;
$this->laststart = strtotime($until);
$this->lastend = $this->laststart + $this->getDuration();
}
$imports = array('summary','description','location');
@ -74,10 +121,28 @@ class SG_iCal_VEvent {
unset($data[$import]);
}
}
if( isset($this->previous_tz) ) {
date_default_timezone_set($this->previous_tz);
}
$this->data = SG_iCal_Line::Remove_Line($data);
}
/**
* Returns the Event Occurrences Iterator (if recurrence set)
* @return SG_iCal_Freq
*/
public function getFrequency() {
if (! isset($this->freq)) {
if ( isset($this->recurrence) ) {
$this->freq = new SG_iCal_Freq($this->recurrence->rrule, $this->start, $this->excluded, $this->added);
}
}
return $this->freq;
}
/**
* Returns the UID of the event
* @return string
@ -85,7 +150,7 @@ class SG_iCal_VEvent {
public function getUID() {
return $this->uid;
}
/**
* Returns the summary (or null if none is given) of the event
* @return string
@ -93,7 +158,7 @@ class SG_iCal_VEvent {
public function getSummary() {
return $this->summary;
}
/**
* Returns the description (or null if none is given) of the event
* @return string
@ -101,7 +166,7 @@ class SG_iCal_VEvent {
public function getDescription() {
return $this->description;
}
/**
* Returns the location (or null if none is given) of the event
* @return string
@ -109,7 +174,7 @@ class SG_iCal_VEvent {
public function getLocation() {
return $this->location;
}
/**
* Returns true if the event is blocking (ie not transparent)
* @return bool
@ -117,7 +182,7 @@ class SG_iCal_VEvent {
public function isBlocking() {
return !(isset($this->data['transp']) && $this->data['transp'] == 'TRANSPARENT');
}
/**
* Returns true if the event is confirmed
* @return bool
@ -129,7 +194,19 @@ class SG_iCal_VEvent {
return $this->data['status'] == 'CONFIRMED';
}
}
/**
* Returns true if duration is multiple of 86400
* @return bool
*/
public function isWholeDay() {
$dur = $this->getDuration();
if ($dur > 0 && ($dur % 86400) == 0) {
return true;
}
return false;
}
/**
* Returns the timestamp for the beginning of the event
* @return int
@ -137,7 +214,7 @@ class SG_iCal_VEvent {
public function getStart() {
return $this->start;
}
/**
* Returns the timestamp for the end of the event
* @return int
@ -145,7 +222,15 @@ class SG_iCal_VEvent {
public function getEnd() {
return $this->end;
}
/**
* Returns the timestamp for the end of the last event
* @return int
*/
public function getRangeEnd() {
return max($this->end,$this->lastend);
}
/**
* Returns the duration of this event in seconds
* @return int
@ -153,7 +238,7 @@ class SG_iCal_VEvent {
public function getDuration() {
return $this->end - $this->start;
}
/**
* Returns the given property of the event.
* @param string $prop
@ -168,19 +253,40 @@ class SG_iCal_VEvent {
return null;
}
}
/**
* Set default timezone (temporary) to get timestamps
* @return string
*/
protected function setLineTimeZone(SG_iCal_Line $line) {
if( isset($line['tzid']) ) {
if (!isset($this->previous_tz)) {
$this->previous_tz = @ date_default_timezone_get();
}
$this->tzid = $line['tzid'];
date_default_timezone_set($this->tzid);
return true;
}
return false;
}
/**
* Calculates the timestamp from a DT line.
* @param $line SG_iCal_Line
* @return int
*/
private function getTimestamp( SG_iCal_Line $line, SG_iCal $ical ) {
$ts = strtotime($line->getData());
protected function getTimestamp( SG_iCal_Line $line, SG_iCal $ical ) {
if( isset($line['tzid']) ) {
$tz = $ical->getTimeZoneInfo($line['tzid']);
$offset = $tz->getOffset($ts);
$ts = strtotime(date('D, d M Y H:i:s', $ts) . ' ' . $offset);
$this->setLineTimeZone($line);
//$tz = $ical->getTimeZoneInfo($line['tzid']);
//$offset = $tz->getOffset($ts);
//$ts = strtotime(date('D, d M Y H:i:s', $ts) . ' ' . $offset);
}
$ts = strtotime($line->getData());
return $ts;
}
}

Wyświetl plik

@ -9,31 +9,31 @@
* @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK
*/
class SG_iCal_VTimeZone {
private $tzid;
private $daylight;
private $standard;
private $cache = array();
protected $tzid;
protected $daylight;
protected $standard;
protected $cache = array();
/**
* Constructs a new SG_iCal_VTimeZone
*/
public function __construct( $data ) {
require_once dirname(__FILE__).'/../helpers/SG_iCal_Freq.php'; // BUILD: Remove line
$this->tzid = $data['tzid'];
$this->daylight = $data['daylight'];
$this->standard = $data['standard'];
}
/**
* Returns the timezone-id for this timezone. (Used to
* Returns the timezone-id for this timezone. (Used to
* differentiate between different tzs in a calendar)
* @return string
*/
public function getTimeZoneId() {
return $this->tzid;
}
/**
* Returns the given offset in this timezone for the given
* timestamp. (eg +0200)
@ -44,7 +44,7 @@ class SG_iCal_VTimeZone {
$act = $this->getActive($ts);
return $this->{$act}['tzoffsetto'];
}
/**
* Returns the timezone name for the given timestamp (eg CEST)
* @param int $ts
@ -54,7 +54,7 @@ class SG_iCal_VTimeZone {
$act = $this->getActive($ts);
return $this->{$act}['tzname'];
}
/**
* Determines which of the daylight or standard is the active
* setting.
@ -65,20 +65,31 @@ class SG_iCal_VTimeZone {
* @return string standard|daylight
*/
private function getActive( $ts ) {
if( isset($this->cache[$ts]) ) {
if (class_exists('DateTimeZone')) {
//PHP >= 5.2
$tz = new DateTimeZone( $this->tzid );
$date = new DateTime("@$ts", $tz);
return ($date->format('I') == 1) ? 'daylight' : 'standard';
} else {
if( isset($this->cache[$ts]) ) {
return $this->cache[$ts];
}
$daylight_freq = new SG_iCal_Freq($this->daylight['rrule'], strtotime($this->daylight['dtstart']));
$standard_freq = new SG_iCal_Freq($this->standard['rrule'], strtotime($this->standard['dtstart']));
$last_standard = $standard_freq->previousOccurrence($ts);
$last_dst = $daylight_freq->previousOccurrence($ts);
if( $last_dst > $last_standard ) {
$this->cache[$ts] = 'daylight';
} else {
$this->cache[$ts] = 'standard';
}
return $this->cache[$ts];
}
$daylight_freq = new SG_iCal_Freq($this->daylight['rrule'], strtotime($this->daylight['dtstart']));
$standard_freq = new SG_iCal_Freq($this->standard['rrule'], strtotime($this->standard['dtstart']));
$last_standard = $standard_freq->previousOccurrence($ts);
$last_dst = $daylight_freq->previousOccurrence($ts);
if( $last_dst > $last_standard ) {
$this->cache[$ts] = 'daylight';
} else {
$this->cache[$ts] = 'standard';
}
return $this->cache[$ts];
}
}

5
build.cmd 100644
Wyświetl plik

@ -0,0 +1,5 @@
@SET OUTPUT=.\sgical.php
cat SG_iCal.php | grep -v "BUILD: Remove line" > %OUTPUT%
DIR /B /S helpers | grep SG_iCal | grep -v svn | sed -e "s/\\/\//g" | xargs cat | grep -v "BUILD: Remove line" >> %OUTPUT%
DIR /B /S blocks | grep SG_iCal | grep -v svn | sed -e "s/\\/\//g" | xargs cat | grep -v "BUILD: Remove line" >> %OUTPUT%

107
demo/basic.ics 100644
Wyświetl plik

@ -0,0 +1,107 @@
BEGIN:VCALENDAR
PRODID:-//Google Inc//Google Calendar 70.9054//EN
VERSION:2.0
CALSCALE:GREGORIAN
METHOD:PUBLISH
X-WR-CALNAME:Particuliers - Grand Tour du Bassin
X-WR-TIMEZONE:Europe/Paris
X-WR-CALDESC:
BEGIN:VTIMEZONE
TZID:Europe/Paris
X-LIC-LOCATION:Europe/Paris
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
DTSTART;TZID=Europe/Paris:20100526T143000
DTEND;TZID=Europe/Paris:20100526T171500
DTSTAMP:20101028T215738Z
UID:ptb5jqomu2vu0sr2nm24qungag@google.com
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=Partic
uliers - Grand Tour du Bassin;X-NUM-GUESTS=0:mailto:l2n7ajiud0oaiua4qcdarg1
krg@group.calendar.google.com
RECURRENCE-ID;TZID=Europe/Paris:20100526T143000
CREATED:20100428T225030Z
DESCRIPTION:
LAST-MODIFIED:20100527T102538Z
LOCATION:Jetée Thiers\, Arcachon
SEQUENCE:0
STATUS:CONFIRMED
SUMMARY:Horaire
TRANSP:TRANSPARENT
END:VEVENT
BEGIN:VEVENT
DTSTART;TZID=Europe/Paris:20100403T143000
DTEND;TZID=Europe/Paris:20100403T171500
RRULE:FREQ=WEEKLY;WKST=MO;UNTIL=20100630T123000Z;BYDAY=SU,WE,SA
DTSTAMP:20101028T215738Z
UID:ptb5jqomu2vu0sr2nm24qungag@google.com
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=Partic
uliers - Grand Tour du Bassin;X-NUM-GUESTS=0:mailto:l2n7ajiud0oaiua4qcdarg1
krg@group.calendar.google.com
CREATED:20100428T225030Z
DESCRIPTION:
LAST-MODIFIED:20100518T052328Z
LOCATION:Jetée Thiers\, Arcachon
SEQUENCE:0
STATUS:CONFIRMED
SUMMARY:Horaire
TRANSP:TRANSPARENT
END:VEVENT
BEGIN:VEVENT
DTSTART;TZID=Europe/Paris:20101002T143000
DTEND;TZID=Europe/Paris:20101002T171500
RRULE:FREQ=WEEKLY;BYDAY=SU,SA;WKST=MO
DTSTAMP:20101028T215738Z
UID:41v060qrjvpvsv90n6ulljjeg4@google.com
CREATED:20100428T225427Z
DESCRIPTION:
LAST-MODIFIED:20100518T052328Z
LOCATION:Jetée Thiers\, Arcachon
SEQUENCE:0
STATUS:CONFIRMED
SUMMARY:Horaire
TRANSP:TRANSPARENT
END:VEVENT
BEGIN:VEVENT
DTSTART;TZID=Europe/Paris:20100703T143000
DTEND;TZID=Europe/Paris:20100703T171500
RRULE:FREQ=DAILY;UNTIL=20100930T123000Z;WKST=MO
DTSTAMP:20101028T215738Z
UID:92tmcm95ktr40ofn3okf0hhr2c@google.com
CREATED:20100428T225334Z
DESCRIPTION:
LAST-MODIFIED:20100518T052327Z
LOCATION:Jetée Thiers\, Arcachon
SEQUENCE:0
STATUS:CONFIRMED
SUMMARY:Horaire
TRANSP:TRANSPARENT
END:VEVENT
BEGIN:VEVENT
DTSTART:20100513T123000Z
DTEND:20100513T151500Z
DTSTAMP:20101028T215738Z
UID:254jd6v58v92k7bj69sgu3ulb0@google.com
CREATED:20100428T225221Z
DESCRIPTION:
LAST-MODIFIED:20100518T052327Z
LOCATION:Jetée Thiers\, Arcachon
SEQUENCE:0
STATUS:CONFIRMED
SUMMARY:Horaire
TRANSP:TRANSPARENT
END:VEVENT
END:VCALENDAR

43
demo/exdate.ics 100644
Wyświetl plik

@ -0,0 +1,43 @@
BEGIN:VCALENDAR
PRODID:-//Google Inc//Google Calendar 70.9054//EN
VERSION:2.0
CALSCALE:GREGORIAN
METHOD:PUBLISH
X-WR-CALNAME:uba.gestion@gmail.com
X-WR-TIMEZONE:Europe/Paris
BEGIN:VTIMEZONE
TZID:Europe/Paris
X-LIC-LOCATION:Europe/Paris
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
DTSTART;VALUE=DATE:20100907
DTEND;VALUE=DATE:20100908
RRULE:FREQ=DAILY;UNTIL=20100912
EXDATE;VALUE=DATE:20100911
EXDATE;VALUE=DATE:20100909,20100910
DTSTAMP:20101031T155459Z
UID:5oo2ridecth26kcavj8elhtd4s@google.com
CREATED:00001231T000000Z
DESCRIPTION:
LAST-MODIFIED:20101030T193954Z
LOCATION:
SEQUENCE:1
STATUS:CONFIRMED
SUMMARY:occur
TRANSP:TRANSPARENT
END:VEVENT
END:VCALENDAR

Wyświetl plik

@ -0,0 +1,615 @@
/*
* FullCalendar v1.4.8-IE9 Stylesheet
*
* Feel free to edit this file to customize the look of FullCalendar.
* When upgrading to newer versions, please upgrade this file as well,
* porting over any customizations afterwards.
*
* Date: Mon Oct 25 02:35:06 2010 +0200
*
*/
/* TODO: make font sizes look the same in all doctypes */
.fc,
.fc .fc-header,
.fc .fc-content {
font-size: 1em;
}
.fc {
direction: ltr;
text-align: left;
}
.fc table {
border-collapse: collapse;
border-spacing: 0;
}
.fc td, .fc th {
padding: 0;
vertical-align: top;
}
/* Header
------------------------------------------------------------------------*/
table.fc-header {
width: 100%;
}
.fc-header-left {
width: 25%;
}
.fc-header-left table {
float: left;
}
.fc-header-center {
width: 50%;
text-align: center;
}
.fc-header-center table {
margin: 0 auto;
}
.fc-header-right {
width: 25%;
}
.fc-header-right table {
float: right;
}
.fc-header-title {
margin-top: 0;
white-space: nowrap;
}
.fc-header-space {
padding-left: 10px;
}
/* right-to-left */
.fc-rtl .fc-header-title {
direction: rtl;
}
/* Buttons
------------------------------------------------------------------------*/
.fc-header .fc-state-default,
.fc-header .ui-state-default {
margin-bottom: 1em;
cursor: pointer;
}
.fc-header .fc-state-default {
border-width: 1px 0;
padding: 0 1px;
}
.fc-header .fc-state-default,
.fc-header .fc-state-default a {
border-style: solid;
}
.fc-header .fc-state-default a {
display: block;
border-width: 0 1px;
margin: 0 -1px;
width: 100%;
text-decoration: none;
}
.fc-header .fc-state-default span {
display: block;
border-style: solid;
border-width: 1px 0 1px 1px;
padding: 3px 5px;
}
.fc-header .ui-state-default {
padding: 4px 6px;
}
.fc-header .fc-state-default span,
.fc-header .ui-state-default span {
white-space: nowrap;
}
/* for adjacent buttons */
.fc-header .fc-no-right {
padding-right: 0;
}
.fc-header .fc-no-right a {
margin-right: 0;
border-right: 0;
}
.fc-header .ui-no-right {
border-right: 0;
}
/* for fake rounded corners */
.fc-header .fc-corner-left {
margin-left: 1px;
padding-left: 0;
}
.fc-header .fc-corner-right {
margin-right: 1px;
padding-right: 0;
}
/* DEFAULT button COLORS */
.fc-header .fc-state-default,
.fc-header .fc-state-default a {
border-color: #777; /* outer border */
color: #333;
}
.fc-header .fc-state-default span {
border-color: #fff #fff #d1d1d1; /* inner border */
background: #e8e8e8;
}
/* PRESSED button COLORS (down and active) */
.fc-header .fc-state-active a {
color: #fff;
}
.fc-header .fc-state-down span,
.fc-header .fc-state-active span {
background: #888;
border-color: #808080 #808080 #909090; /* inner border */
}
/* DISABLED button COLORS */
.fc-header .fc-state-disabled a {
color: #999;
}
.fc-header .fc-state-disabled,
.fc-header .fc-state-disabled a {
border-color: #ccc; /* outer border */
}
.fc-header .fc-state-disabled span {
border-color: #fff #fff #f0f0f0; /* inner border */
background: #f0f0f0;
}
/* Content Area & Global Cell Styles
------------------------------------------------------------------------*/
.fc-widget-content {
border: 1px solid #ccc; /* outer border color */
}
.fc-content {
clear: both;
}
.fc-content .fc-state-default {
border-style: solid;
border-color: #ccc; /* inner border color */
}
.fc-content .ui-state-highlight,
.fc-content .fc-state-highlight { /* today */
background: #ffd;
}
.fc-content .fc-not-today { /* override jq-ui highlight (TODO: ui-widget-content) */
background: none;
}
.fc-content .fc-before-today {
background: #fafafa;
}
.fc-cell-overlay { /* semi-transparent rectangle while dragging */
background: #9cf;
opacity: .2;
filter: alpha(opacity=20); /* for IE */
}
.fc-view { /* prevents dragging outside of widget */
width: 100%;
overflow: hidden;
}
/* Global Event Styles
------------------------------------------------------------------------*/
.fc-event,
.fc-agenda .fc-event-time,
.fc-event a {
border-style: solid;
border-color: #36c; /* default BORDER color (probably the same as background-color) */
background-color: #36c; /* default BACKGROUND color */
color: #fff; /* default TEXT color */
}
/* Use the 'className' CalEvent property and the following
* example CSS to change event color on a per-event basis:
*
* .myclass,
* .fc-agenda .myclass .fc-event-time,
* .myclass a {
* background-color: black;
* border-color: black;
* color: red;
* }
*/
.fc-event {
text-align: left;
}
.fc-event a {
overflow: hidden;
font-size: .85em;
text-decoration: none;
cursor: pointer;
}
.fc-event-editable {
cursor: pointer;
}
.fc-event-time,
.fc-event-title {
padding: 0 1px;
}
/* for fake rounded corners */
.fc-event a {
display: block;
position: relative;
width: 100%;
height: 100%;
}
/* right-to-left */
.fc-rtl .fc-event a {
text-align: right;
}
/* resizable */
.fc .ui-resizable-handle {
display: block;
position: absolute;
z-index: 99999;
border: 0 !important; /* important overrides pre jquery ui 1.7 styles */
background: url(data:image/gif;base64,AAAA) !important; /* hover fix for IE */
}
/* Horizontal Events
------------------------------------------------------------------------*/
.fc-event-hori {
border-width: 1px 0;
margin-bottom: 1px;
}
.fc-event-hori a {
border-width: 0;
}
/* for fake rounded corners */
.fc-content .fc-corner-left {
margin-left: 1px;
}
.fc-content .fc-corner-left a {
margin-left: -1px;
border-left-width: 1px;
}
.fc-content .fc-corner-right {
margin-right: 1px;
}
.fc-content .fc-corner-right a {
margin-right: -1px;
border-right-width: 1px;
}
/* resizable */
.fc-event-hori .ui-resizable-e {
top: 0 !important; /* importants override pre jquery ui 1.7 styles */
right: -3px !important;
width: 7px !important;
height: 100% !important;
cursor: e-resize;
}
.fc-event-hori .ui-resizable-w {
top: 0 !important;
left: -3px !important;
width: 7px !important;
height: 100% !important;
cursor: w-resize;
}
.fc-event-hori .ui-resizable-handle {
_padding-bottom: 14px; /* IE6 had 0 height */
}
/* Month View, Basic Week View, Basic Day View
------------------------------------------------------------------------*/
.fc-grid table {
width: 100%;
}
.fc .fc-grid th {
border-width: 0 0 0 1px;
text-align: center;
}
.fc .fc-grid td {
border-width: 1px 0 0 1px;
}
.fc-grid th.fc-leftmost,
.fc-grid td.fc-leftmost {
border-left: 0;
}
.fc-grid .fc-day-number {
float: right;
padding: 0 2px;
}
.fc-grid .fc-other-month .fc-day-number {
opacity: 0.3;
filter: alpha(opacity=30); /* for IE */
/* opacity with small font can sometimes look too faded
might want to set the 'color' property instead
making day-numbers bold also fixes the problem */
}
.fc-grid .fc-day-content {
clear: both;
padding: 2px 2px 0; /* distance between events and day edges */
}
/* event styles */
.fc-grid .fc-event-time {
font-weight: bold;
}
/* right-to-left */
.fc-rtl .fc-grid {
direction: rtl;
}
.fc-rtl .fc-grid .fc-day-number {
float: left;
}
.fc-rtl .fc-grid .fc-event-time {
float: right;
}
/* week numbers */
.fc .fc-grid th.fc-weeknumber {
border-top-width: 1px;
}
/* Agenda Week View, Agenda Day View
------------------------------------------------------------------------*/
.fc .fc-agenda th,
.fc .fc-agenda td {
border-width: 1px 0 0 1px;
}
.fc .fc-agenda .fc-leftmost {
border-left: 0;
}
.fc-agenda tr.fc-first th,
.fc-agenda tr.fc-first td {
border-top: 0;
}
.fc-agenda-head tr.fc-last th {
border-bottom-width: 1px;
}
.fc .fc-agenda-head td,
.fc .fc-agenda-body td {
background: none;
}
.fc-agenda-head th {
text-align: center;
}
/* the time axis running down the left side */
.fc-agenda .fc-axis {
width: 50px;
padding: 0 4px;
vertical-align: middle;
white-space: nowrap;
text-align: right;
font-weight: normal;
}
/* all-day event cells at top */
.fc-agenda-head tr.fc-all-day th {
height: 35px;
}
.fc-agenda-head td {
padding-bottom: 10px;
}
.fc .fc-divider div {
font-size: 1px; /* for IE6/7 */
height: 2px;
}
.fc .fc-divider .fc-state-default {
background: #eee; /* color for divider between all-day and time-slot events */
}
/* body styles */
.fc .fc-agenda-body td div {
height: 20px; /* slot height */
}
.fc .fc-agenda-body tr.fc-minor th,
.fc .fc-agenda-body tr.fc-minor td {
border-top-style: dotted;
}
.fc-agenda .fc-day-content {
padding: 2px 2px 0; /* distance between events and day edges */
}
/* vertical background columns */
.fc .fc-agenda-bg .ui-state-highlight {
background-image: none; /* tall column, don't want repeating background image */
}
/* Vertical Events
------------------------------------------------------------------------*/
.fc-event-vert {
border-width: 0 1px;
}
.fc-event-vert a {
border-width: 0;
}
/* for fake rounded corners */
.fc-content .fc-corner-top {
margin-top: 1px;
}
.fc-content .fc-corner-top a {
margin-top: -1px;
border-top-width: 1px;
}
.fc-content .fc-corner-bottom {
margin-bottom: 1px;
}
.fc-content .fc-corner-bottom a {
margin-bottom: -1px;
border-bottom-width: 1px;
}
/* event content */
.fc-event-vert span {
display: block;
position: relative;
z-index: 2;
}
.fc-event-vert span.fc-event-time {
white-space: nowrap;
_white-space: normal;
overflow: hidden;
border: 0;
font-size: 10px;
}
.fc-event-vert span.fc-event-title {
line-height: 13px;
}
.fc-event-vert span.fc-event-bg { /* makes the event lighter w/ a semi-transparent overlay */
position: absolute;
z-index: 1;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #fff;
opacity: .3;
filter: alpha(opacity=30); /* for IE */
}
/* resizable */
.fc-event-vert .ui-resizable-s {
bottom: 0 !important; /* importants override pre jquery ui 1.7 styles */
width: 100% !important;
height: 8px !important;
line-height: 8px !important;
font-size: 11px !important;
font-family: monospace;
text-align: center;
cursor: s-resize;
}
/* JOMRES */
.gcal-blackbooking-0,
.fc-agenda .gcal-blackbooking-0 .fc-event-time,
.gcal-blackbooking-0 a {
border-style: solid;
border-color: #400; /* default BORDER color (probably the same as background-color) */
background-color: #D60; /* default BACKGROUND color */
color: #fff; /* default TEXT color */
}
.gcal-blackbooking-1,
.fc-agenda .gcal-blackbooking-1 .fc-event-time,
.gcal-blackbooking-1 a {
border-style: solid;
border-color: #400; /* default BORDER color (probably the same as background-color) */
background-color: #800; /* default BACKGROUND color */
color: #fff; /* default TEXT color */
}

4765
demo/fullcalendar.js 100644

Plik diff jest za duży Load Diff

122
demo/index.php 100644
Wyświetl plik

@ -0,0 +1,122 @@
<?php
require_once('../SG_iCal.php');
function dump_t($x) {
echo "<pre>".print_r($x,true)."</pre>";
}
$ICS = "exdate.ics";
//echo dump_t(file_get_contents($ICS));
$ical = new SG_iCalReader($ICS);
$query = new SG_iCal_Query();
$evts = $ical->getEvents();
//$evts = $query->Between($ical,strtotime('20100901'),strtotime('20101131'));
$data = array();
foreach($evts as $id => $ev) {
$jsEvt = array(
"id" => ($id+1),
"title" => $ev->getProperty('summary'),
"start" => $ev->getStart(),
"end" => $ev->getEnd()-1,
"allDay" => $ev->isWholeDay()
);
if (isset($ev->recurrence)) {
$count = 0;
$start = $ev->getStart();
$freq = $ev->getFrequency();
if ($freq->firstOccurrence() == $start)
$data[] = $jsEvt;
while (($next = $freq->nextOccurrence($start)) > 0 ) {
if (!$next or $count >= 1000) break;
$count++;
$start = $next;
$jsEvt["start"] = $start;
$jsEvt["end"] = $start + $ev->getDuration()-1;
$data[] = $jsEvt;
}
} else
$data[] = $jsEvt;
}
//echo(date('Ymd\n',$data[0][start]));
//echo(date('Ymd\n',$data[1][start]));
//dump_t($data);
$events = "events:".json_encode($data).',';
?>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Fullcalendar iCal Loader</title>
<link rel="stylesheet" type="text/css" href="fullcalendar.css">
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script>
<script type="text/javascript" src="fullcalendar.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$('#calendar').fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,agendaDay'
},
year: 2010,
month: 9-1,
// US Holidays
//events: $.fullCalendar.gcalFeed('http://www.google.com/calendar/feeds/usa__en%40holiday.calendar.google.com/public/basic'),
<?=$events ?>
eventClick: function(event) {
// opens events in a popup window
window.open(event.url, 'gcalevent', 'width=700,height=600');
return false;
},
loading: function(bool) {
if (bool) {
$('#loading').show();
}else{
$('#loading').hide();
}
}
});
});
</script>
<style type='text/css'>
body div {
text-align: center;
}
body {
font-size: 14px;
font-family: "Lucida Grande",Helvetica,Arial,Verdana,sans-serif;
}
div#loading {
position: absolute;
top: 5px;
right: 5px;
}
div#calendar {
width: 900px;
margin: 0 auto;
}
</style>
</head>
<body>
<div id="loading" style="display:none;">loading...</div>
<div id="calendar"></div>
</body>
</html>

Wyświetl plik

@ -9,8 +9,8 @@
*/
class SG_iCal_Duration {
private $dur;
protected $dur;
/**
* Constructs a new SG_iCal_Duration from a duration-rule.
* The basic build-up of DURATIONs are:
@ -20,30 +20,32 @@ class SG_iCal_Duration {
* @param $duration string
*/
public function __construct( $duration ) {
if( $duration{0} == 'P' || (($duration{0} == '+' || $duration{0} == '-') && $duration{1} == 'P') ) {
preg_match('/P((\d+)W)?((\d+)D)?(T)?((\d+)H)?((\d+)M)?((\d+)S)?/', $duration, $matches);
$results = array('weeks'=>(int)$matches[2],
'days'=>(int)$matches[4],
'hours'=>(int)$matches[7],
'minutes'=>(int)$matches[9],
'seconds'=>(int)$matches[11]);
$ts = 0;
$ts = 0;
if (preg_match('/[\\+\\-]{0,1}P((\d+)W)?((\d+)D)?(T)?((\d+)H)?((\d+)M)?((\d+)S)?/', $duration, $matches) === 1) {
$results = array(
'weeks'=> (int)@ $matches[2],
'days'=> (int)@ $matches[4],
'hours'=> (int)@ $matches[7],
'minutes'=>(int)@ $matches[9],
'seconds'=>(int)@ $matches[11]
);
$ts += $results['seconds'];
$ts += 60 * $results['minutes'];
$ts += 60 * 60 * $results['hours'];
$ts += 24 * 60 * 60 * $results['days'];
$ts += 7 * 24 * 60 * 60 * $results['weeks'];
$dir = ($duration{0} == '-') ? -1 : 1;
$this->dur = $dir * $ts;
$ts += 7 * 24 * 60 * 60 * $results['weeks'];
} else {
// Invalid duration!
$this->dur = 0;
}
$dir = ($duration{0} == '-') ? -1 : 1;
$this->dur = $dir * $ts;
}
/**
* Returns the duration in seconds
* @return int

Wyświetl plik

@ -13,7 +13,7 @@ class SG_iCal_Factory {
/**
* Returns a new block-object for the section/data-pair. The list
* of returned objects is:
*
*
* vcalendar => SG_iCal_VCalendar
* vtimezone => SG_iCal_VTimeZone
* vevent => SG_iCal_VEvent
@ -34,7 +34,7 @@ class SG_iCal_Factory {
case "vevent":
require_once dirname(__FILE__).'/../blocks/SG_iCal_VEvent.php'; // BUILD: Remove line
return new SG_iCal_VEvent($data, $ical );
default:
return new ArrayObject(SG_iCal_Line::Remove_Line((array) $data) );
}

Wyświetl plik

@ -1,10 +1,10 @@
<?php // BUILD: Remove line
/**
* A class to store Frequency-rules in. Will allow a easy way to find the
* A class to store Frequency-rules in. Will allow a easy way to find the
* last and next occurrence of the rule.
*
* No - this is so not pretty. But.. ehh.. You do it better, and I will
* No - this is so not pretty. But.. ehh.. You do it better, and I will
* gladly accept patches.
*
* Created by trail-and-error on the examples given in the RFC.
@ -12,10 +12,10 @@
* TODO: Update to a better way of doing calculating the different options.
* Instead of only keeping track of the best of the current dates found
* it should instead keep a array of all the calculated dates within the
* period.
* This should fix the issues with multi-rule + multi-rule interference,
* period.
* This should fix the issues with multi-rule + multi-rule interference,
* and make it possible to implement the SETPOS rule.
* By pushing the next period onto the stack as the last option will
* By pushing the next period onto the stack as the last option will
* (hopefully) remove the need for the awful simpleMode
*
* @package SG_iCalReader
@ -23,22 +23,31 @@
* @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK
*/
class SG_iCal_Freq {
private $weekdays = array('MO'=>'monday', 'TU'=>'tuesday', 'WE'=>'wednesday', 'TH'=>'thursday', 'FR'=>'friday', 'SA'=>'saturday', 'SU'=>'sunday');
private $knownRules = array('month', 'weekno', 'day', 'monthday', 'yearday', 'hour', 'minute');
private $simpleMode = true;
private $rules = array('freq'=>'yearly', 'interval'=>1);
private $start = 0;
private $freq = '';
protected $weekdays = array('MO'=>'monday', 'TU'=>'tuesday', 'WE'=>'wednesday', 'TH'=>'thursday', 'FR'=>'friday', 'SA'=>'saturday', 'SU'=>'sunday');
protected $knownRules = array('month', 'weekno', 'day', 'monthday', 'yearday', 'hour', 'minute'); //others : 'setpos', 'second'
protected $ruleModifiers = array('wkst');
protected $simpleMode = true;
protected $rules = array('freq'=>'yearly', 'interval'=>1);
protected $start = 0;
protected $freq = '';
protected $excluded; //EXDATE
protected $added; //RDATE
protected $cache; // getAllOccurrences()
/**
* Constructs a new Freqency-rule
* @param $rule string
* @param $start int Unix-timestamp (important!)
* @param $rule string
* @param $start int Unix-timestamp (important : Need to be the start of Event)
* @param $excluded array of int (timestamps), see EXDATE documentation
* @param $added array of int (timestamps), see RDATE documentation
*/
public function __construct( $rule, $start ) {
public function __construct( $rule, $start, $excluded=array(), $added=array()) {
$this->start = $start;
$this->excluded = array();
$rules = array();
foreach( explode(';', $rule) AS $v) {
list($k, $v) = explode('=', $v);
@ -49,7 +58,6 @@ class SG_iCal_Freq {
$this->rules['until'] = strtotime($this->rules['until']);
}
$this->freq = strtolower($this->rules['freq']);
foreach( $this->knownRules AS $rule ) {
if( isset($this->rules['by' . $rule]) ) {
@ -58,22 +66,64 @@ class SG_iCal_Freq {
}
}
}
if(!$this->simpleMode) {
if(! (isset($this->rules['byday']) || isset($this->rules['bymonthday']) || isset($this->rules['byyearday']))) {
$this->rules['bymonthday'] = date('d', $this->start);
}
}
//set until, and cache
if( isset($this->rules['count']) ) {
$n = $start;
for($i=0;$i<$this->rules['count'];$i++) {
$n = $this->findNext($n);
$cache[$ts] = $ts = $this->start;
for($n=1; $n < $this->rules['count']; $n++) {
$ts = $this->findNext($ts);
$cache[$ts] = $ts;
}
$this->rules['until'] = $n;
$this->rules['until'] = $ts;
//EXDATE
if (!empty($excluded)) {
foreach($excluded as $ts) {
unset($cache[$ts]);
}
}
//RDATE
if (!empty($added)) {
$cache = $cache + $added;
asort($cache);
}
$this->cache = array_values($cache);
}
$this->excluded = $excluded;
$this->added = $added;
}
/**
* Returns all timestamps array(), build the cache if not made before
* @return array
*/
public function getAllOccurrences() {
if (empty($this->cache)) {
//build cache
$next = $this->firstOccurrence();
while ($next) {
$cache[] = $next;
$next = $this->findNext($next);
}
if (!empty($this->added)) {
$cache = $cache + $this->added;
asort($cache);
}
$this->cache = $cache;
}
return $this->cache;
}
/**
* Returns the previous (most recent) occurrence of the rule from the
* given offset
@ -81,68 +131,95 @@ class SG_iCal_Freq {
* @return int
*/
public function previousOccurrence( $offset ) {
$t1 = $this->start;
while( ($t2 = $this->findNext($t1)) < $offset) {
if( $t2 == false ){
break;
if (!empty($this->cache)) {
$t2=$this->start;
foreach($this->cache as $ts) {
if ($ts >= $offset)
return $t2;
$t2 = $ts;
}
} else {
$ts = $this->start;
while( ($t2 = $this->findNext($ts)) < $offset) {
if( $t2 == false ){
break;
}
$ts = $t2;
}
$t1 = $t2;
}
return $t1;
return $ts;
}
/**
* Returns the next occurrence of this rule after the given offset
* @param int $offset
* @return int
*/
public function nextOccurrence( $offset ) {
return $this->findNext( $this->previousOccurrence( $offset) );
if ($offset < $this->start)
return $this->firstOccurrence();
return $this->findNext($offset);
}
/**
* Finds the first occurrence of the rule.
* @return int timestamp
*/
public function firstOccurrence() {
$t = $this->start;
if (in_array($t, $this->excluded))
$t = $this->findNext($t);
return $t;
}
/**
* Finds the absolute last occurrence of the rule from the given offset.
* Builds also the cache, if not set before...
* @return int timestamp
*/
public function lastOccurrence() {
$temp_timestamp = $this->findNext($this->start);
$timestamp = 0;
while ($temp_timestamp) {
$timestamp = $temp_timestamp;
$temp_timestamp = $this->findNext($temp_timestamp);
}
return $timestamp;
//build cache if not done
$this->getAllOccurrences();
//return last timestamp in cache
return end($this->cache);
}
/**
* Calculates the next time after the given offset that the rule
* Calculates the next time after the given offset that the rule
* will apply.
*
* The approach to finding the next is as follows:
* First we establish a timeframe to find timestamps in. This is
* between $offset and the end of the period that $offset is in.
*
* We then loop though all the rules (that is a Prerule in the
*
* We then loop though all the rules (that is a Prerule in the
* current freq.), and finds the smallest timestamp inside the
* timeframe.
*
* If we find something, we check if the date is a valid recurrence
* (with validDate). If it is, we return it. Otherwise we try to
* (with validDate). If it is, we return it. Otherwise we try to
* find a new date inside the same timeframe (but using the new-
* found date as offset)
*
* If no new timestamps were found in the period, we try in the
* If no new timestamps were found in the period, we try in the
* next period
*
* @param int $offset
* @return int
*/
public function findNext($offset) {
$echo = false;
public function findNext($offset) {
if (!empty($this->cache)) {
foreach($this->cache as $ts) {
if ($ts > $offset)
return $ts;
}
}
$debug = false;
//make sure the offset is valid
if( $offset === false || (isset($this->rules['until']) && $this->rules['until'] <= $offset) ) {
if($echo) echo 'STOP: ' . date('r', $offset) . "\n";
if( $offset === false || (isset($this->rules['until']) && $offset > $this->rules['until']) ) {
if($debug) echo 'STOP: ' . date('r', $offset) . "\n";
return false;
}
@ -150,26 +227,29 @@ class SG_iCal_Freq {
//set the timestamp of the offset (ignoring hours and minutes unless we want them to be
//part of the calculations.
if($echo) echo 'O: ' . date('r', $offset) . "\n";
if($debug) echo 'O: ' . date('r', $offset) . "\n";
$hour = (in_array($this->freq, array('hourly','minutely')) && $offset > $this->start) ? date('H', $offset) : date('H', $this->start);
$minute = (($this->freq == 'minutely' || isset($this->rules['byminute'])) && $offset > $this->start) ? date('i', $offset) : date('i', $this->start);
$t = mktime($hour, $minute, date('s', $this->start), date('m', $offset), date('d', $offset), date('Y',$offset));
if($echo) echo 'START: ' . date('r', $t) . "\n";
if($debug) echo 'START: ' . date('r', $t) . "\n";
if( $this->simpleMode ) {
if( $offset < $t ) {
return $t;
$ts = $t;
if ($ts && in_array($ts, $this->excluded))
$ts = $this->findNext($ts);
} else {
$ts = $this->findStartingPoint( $t, $this->rules['interval'], false );
if( !$this->validDate( $ts ) ) {
$ts = $this->findNext($ts);
}
}
$next = $this->findStartingPoint( $t, $this->rules['interval'], false );
if( !$this->validDate( $next ) ) {
return $this->findNext($next);
}
return $next;
return $ts;
}
$eop = $this->findEndOfPeriod($offset);
if($echo) echo 'EOP: ' . date('r', $eop) . "\n";
if($debug) echo 'EOP: ' . date('r', $eop) . "\n";
foreach( $this->knownRules AS $rule ) {
if( $found && isset($this->rules['by' . $rule]) ) {
if( $this->isPrerule($rule, $this->freq) ) {
@ -180,7 +260,7 @@ class SG_iCal_Freq {
if( $imm === false ) {
break;
}
if($echo) echo strtoupper($rule) . ': ' . date('r', $imm) . ' A: ' . ((int) ($imm > $offset && $imm < $eop)) . "\n";
if($debug) echo strtoupper($rule) . ': ' . date('r', $imm) . ' A: ' . ((int) ($imm > $offset && $imm < $eop)) . "\n";
if( $imm > $offset && $imm < $eop && ($_t == null || $imm < $_t) ) {
$_t = $imm;
}
@ -194,22 +274,26 @@ class SG_iCal_Freq {
}
}
if( $this->start > $offset && $this->start < $t ) {
return $this->start;
if( $offset < $this->start && $this->start < $t ) {
$ts = $this->start;
} else if( $found && ($t != $offset)) {
if( $this->validDate( $t ) ) {
if($echo) echo 'OK' . "\n";
return $t;
if($debug) echo 'OK' . "\n";
$ts = $t;
} else {
if($echo) echo 'Invalid' . "\n";
return $this->findNext($t);
if($debug) echo 'Invalid' . "\n";
$ts = $this->findNext($t);
}
} else {
if($echo) echo 'Not found' . "\n";
return $this->findNext( $this->findStartingPoint( $offset, $this->rules['interval'] ) );
}
if($debug) echo 'Not found' . "\n";
$ts = $this->findNext( $this->findStartingPoint( $offset, $this->rules['interval'] ) );
}
if ($ts && in_array($ts, $this->excluded))
return $this->findNext($ts);
return $ts;
}
/**
* Finds the starting point for the next rule. It goes $interval
* 'freq' forward in time since the given offset
@ -229,14 +313,14 @@ class SG_iCal_Freq {
}
$sp = strtotime($t, $offset);
if( $truncate ) {
$sp = $this->truncateToPeriod($sp, $this->freq);
}
return $sp;
}
/**
* Finds the earliest timestamp posible outside this perioid
* @param int $offset
@ -245,11 +329,11 @@ class SG_iCal_Freq {
public function findEndOfPeriod($offset) {
return $this->findStartingPoint($offset, 1);
}
/**
* Resets the timestamp to the beginning of the
* period specified by freq
*
*
* Yes - the fall-through is on purpose!
*
* @param int $time
@ -283,7 +367,7 @@ class SG_iCal_Freq {
$d = mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], $date['year']);
return $d;
}
/**
* Applies the BYDAY rule to the given timestamp
* @param string $rule
@ -293,24 +377,24 @@ class SG_iCal_Freq {
private function ruleByday($rule, $t) {
$dir = ($rule{0} == '-') ? -1 : 1;
$dir_t = ($dir == 1) ? 'next' : 'last';
$d = $this->weekdays[substr($rule,-2)];
$s = $dir_t . ' ' . $d . ' ' . date('H:i:s',$t);
if( $rule == substr($rule, -2) ) {
if( date('l', $t) == ucfirst($d) ) {
$s = 'today ' . date('H:i:s',$t);
}
$_t = strtotime($s, $t);
if( $_t == $t && in_array($this->freq, array('monthly', 'yearly')) ) {
// Yes. This is not a great idea.. but hey, it works.. for now
$s = 'next ' . $d . ' ' . date('H:i:s',$t);
$_t = strtotime($s, $_t);
}
return $_t;
} else {
$_f = $this->freq;
@ -323,10 +407,10 @@ class SG_iCal_Freq {
$_t = $this->truncateToPeriod($t, $this->freq);
}
$this->freq = $_f;
$c = preg_replace('/[^0-9]/','',$rule);
$c = ($c == '') ? 1 : $c;
$n = $_t;
while($c > 0 ) {
if( $dir == 1 && $c == 1 && date('l', $t) == ucfirst($d) ) {
@ -335,11 +419,11 @@ class SG_iCal_Freq {
$n = strtotime($s, $n);
$c--;
}
return $n;
}
}
private function ruleBymonth($rule, $t) {
$_t = mktime(date('H',$t), date('i',$t), date('s',$t), $rule, date('d', $t), date('Y', $t));
if( $t == $_t && isset($this->rules['byday']) ) {
@ -349,14 +433,14 @@ class SG_iCal_Freq {
return $_t;
}
}
private function ruleBymonthday($rule, $t) {
if( $rule < 0 ) {
$rule = date('t', $t) + $rule + 1;
}
return mktime(date('H',$t), date('i',$t), date('s',$t), date('m', $t), $rule, date('Y', $t));
}
private function ruleByyearday($rule, $t) {
if( $rule < 0 ) {
$_t = $this->findEndOfPeriod();
@ -376,12 +460,12 @@ class SG_iCal_Freq {
} else {
$_t = $this->truncateToPeriod($t, $this->freq);
$d = '+';
}
}
$sub = (date('W', $_t) == 1) ? 2 : 1;
$s = $d . abs($rule - $sub) . ' weeks ' . date('H:i:s',$t);
$_t = strtotime($s, $_t);
return $_t;
}
@ -389,17 +473,21 @@ class SG_iCal_Freq {
$_t = mktime($rule, date('i',$t), date('s',$t), date('m',$t), date('d', $t), date('Y', $t));
return $_t;
}
private function ruleByminute($rule, $t) {
$_t = mktime(date('h',$t), $rule, date('s',$t), date('m',$t), date('d', $t), date('Y', $t));
return $_t;
}
private function validDate( $t ) {
if( isset($this->rules['until']) && $this->rules['until'] <= $t ) {
if( isset($this->rules['until']) && $t > $this->rules['until'] ) {
return false;
}
if (in_array($t, $this->excluded)) {
return false;
}
if( isset($this->rules['bymonth']) ) {
$months = explode(',', $this->rules['bymonth']);
if( !in_array(date('m', $t), $months)) {
@ -438,10 +526,10 @@ class SG_iCal_Freq {
return false;
}
}
return true;
}
private function isPrerule($rule, $freq) {
if( $rule == 'year')
return false;
@ -456,11 +544,11 @@ class SG_iCal_Freq {
return true;
if( $rule == 'day' && in_array($freq, array('yearly', 'monthly', 'weekly')))
return true;
if( $rule == 'hour' && in_array($freq, array('yearly', 'monthly', 'weekly', 'daily')))
if( $rule == 'hour' && in_array($freq, array('yearly', 'monthly', 'weekly', 'daily')))
return true;
if( $rule == 'minute' )
if( $rule == 'minute' )
return true;
return false;
}
}

Wyświetl plik

@ -4,7 +4,7 @@
* A class for storing a single (complete) line of the iCal file.
* Will find the line-type, the arguments and the data of the file and
* store them.
*
*
* The line-type can be found by querying getIdent(), data via either
* getData() or typecasting to a string.
* Params can be access via the ArrayAccess. A iterator is also avilable
@ -15,12 +15,12 @@
* @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK
*/
class SG_iCal_Line implements ArrayAccess, Countable, IteratorAggregate {
private $ident;
private $data;
private $params = array();
private $replacements = array('from'=>array('\\,', '\\n', '\\;', '\\:', '\\"'), 'to'=>array(',', "\n", ';', ':', '"'));
protected $ident;
protected $data;
protected $params = array();
protected $replacements = array('from'=>array('\\,', '\\n', '\\;', '\\:', '\\"'), 'to'=>array(',', "\n", ';', ':', '"'));
/**
* Constructs a new line.
*/
@ -28,21 +28,21 @@ class SG_iCal_Line implements ArrayAccess, Countable, IteratorAggregate {
$split = strpos($line, ':');
$idents = explode(';', substr($line, 0, $split));
$ident = strtolower(array_shift($idents));
$data = trim(substr($line, $split+1));
$data = str_replace($this->replacements['from'], $this->replacements['to'], $data);
$params = array();
foreach( $idents AS $v) {
list($k, $v) = explode('=', $v);
$params[ strtolower($k) ] = $v;
}
$this->ident = $ident;
$this->params = $params;
$this->params = $params;
$this->data = $data;
}
/**
* Is this line the begining of a new block?
* @return bool
@ -50,7 +50,7 @@ class SG_iCal_Line implements ArrayAccess, Countable, IteratorAggregate {
public function isBegin() {
return $this->ident == 'begin';
}
/**
* Is this line the end of a block?
* @return bool
@ -58,7 +58,7 @@ class SG_iCal_Line implements ArrayAccess, Countable, IteratorAggregate {
public function isEnd() {
return $this->ident == 'end';
}
/**
* Returns the line-type (ident) of the line
* @return string
@ -66,7 +66,7 @@ class SG_iCal_Line implements ArrayAccess, Countable, IteratorAggregate {
public function getIdent() {
return $this->ident;
}
/**
* Returns the content of the line
* @return string
@ -74,7 +74,19 @@ class SG_iCal_Line implements ArrayAccess, Countable, IteratorAggregate {
public function getData() {
return $this->data;
}
/**
* Returns the content of the line
* @return string
*/
public function getDataAsArray() {
if (strpos($this->data,",") !== false) {
return explode(",",$this->data);
}
else
return array($this->data);
}
/**
* A static helper to get a array of SG_iCal_Line's, and calls
* getData() on each of them to lay the data "bare"..
@ -95,14 +107,14 @@ class SG_iCal_Line implements ArrayAccess, Countable, IteratorAggregate {
}
return $rtn;
}
/**
* @see ArrayAccess.offsetExists
*/
public function offsetExists( $param ) {
return isset($this->params[ strtolower($param) ]);
}
/**
* @see ArrayAccess.offsetGet
*/
@ -112,7 +124,7 @@ class SG_iCal_Line implements ArrayAccess, Countable, IteratorAggregate {
return $this->params[ $index ];
}
}
/**
* Disabled ArrayAccess requirement
* @see ArrayAccess.offsetSet
@ -120,7 +132,7 @@ class SG_iCal_Line implements ArrayAccess, Countable, IteratorAggregate {
public function offsetSet( $param, $val ) {
return false;
}
/**
* Disabled ArrayAccess requirement
* @see ArrayAccess.offsetUnset
@ -128,7 +140,7 @@ class SG_iCal_Line implements ArrayAccess, Countable, IteratorAggregate {
public function offsetUnset( $param ) {
return false;
}
/**
* toString method.
* @see getData()
@ -136,14 +148,14 @@ class SG_iCal_Line implements ArrayAccess, Countable, IteratorAggregate {
public function __toString() {
return $this->getData();
}
/**
* @see Countable.count
*/
public function count() {
return count($this->params);
}
/**
* @see IteratorAggregate.getIterator
*/

Wyświetl plik

@ -1,4 +1,4 @@
<?php
<?php // BUILD: Remove line
class SG_iCal_Parser {
/**
@ -11,7 +11,7 @@ class SG_iCal_Parser {
$content = self::UnfoldLines($content);
self::_Parse( $content, $ical );
}
/**
* Passes a text string on to be parsed
* @param string $content
@ -21,7 +21,7 @@ class SG_iCal_Parser {
$content = self::UnfoldLines($content);
self::_Parse( $content, $ical );
}
/**
* Fetches a resource and tries to make sure it's UTF8
* encoded
@ -29,7 +29,7 @@ class SG_iCal_Parser {
*/
protected static function Fetch( $resource ) {
$is_utf8 = true;
if( is_file( $resource ) ) {
// The resource is a local file
$content = file_get_contents($resource);
@ -59,16 +59,16 @@ class SG_iCal_Parser {
$is_utf8 = false;
}
}
if( !$is_utf8 ) {
$content = utf8_encode($content);
}
return $content;
}
/**
* Takes the string $content, and creates a array of iCal lines.
* Takes the string $content, and creates a array of iCal lines.
* This includes unfolding multi-line entries into a single line.
* @param $content string
*/
@ -93,6 +93,7 @@ class SG_iCal_Parser {
*/
private static function _Parse( $content, SG_iCal $ical ) {
$main_sections = array('vevent', 'vjournal', 'vtodo', 'vtimezone', 'vcalendar');
$array_idents = array('exdate','rdate');
$sections = array();
$section = '';
$current_data = array();
@ -119,11 +120,16 @@ class SG_iCal_Parser {
if( array_search($s, $sections) !== false ) {
// This section is in the main section
if( $section == $s ) {
// It _is_ the main section
$current_data[$s][$line->getIdent()] = $line;
// It _is_ the main section else
if (in_array($line->getIdent(), $array_idents))
//exdate could appears more that once
$current_data[$s][$line->getIdent()][] = $line;
else {
$current_data[$s][$line->getIdent()] = $line;
}
} else {
// Sub section
$current_data[$s][$section][$line->getIdent()] = $line;
$current_data[$s][$section][$line->getIdent()] = $line;
}
break;
}
@ -155,10 +161,10 @@ class SG_iCal_Parser {
}
/**
* This functions does some regexp checking to see if the value is
* This functions does some regexp checking to see if the value is
* valid UTF-8.
*
* The function is from the book "Building Scalable Web Sites" by
* The function is from the book "Building Scalable Web Sites" by
* Cal Henderson.
*
* @param string $data
@ -181,7 +187,7 @@ class SG_iCal_Parser {
$rx .= '|^[\x80-\xBF]';
return ( ! (bool) preg_match('!'.$rx.'!', $data) );
}
}
}

Wyświetl plik

@ -22,23 +22,23 @@ class SG_iCal_Query {
if( $ical instanceof SG_iCalReader ) {
$ical = $ical->getEvents();
}
if( !is_array($evs) ) {
if( !is_array($ical) ) {
throw new Exception('SG_iCal_Query::Between called with invalid input!');
}
$rtn = array();
foreach( $evs AS $e ) {
foreach( $ical AS $e ) {
if( ($start <= $e->getStart() && $e->getStart() < $end)
|| ($start < $e->getEnd() && $e->getEnd() <= $end) ) {
|| ($start < $e->getRangeEnd() && $e->getRangeEnd() <= $end) ) {
$rtn[] = $e;
}
}
return $rtn;
}
/**
* Returns all events from the calendar after a given timestamp
*
*
* @param SG_iCalReader|array $ical The calendar to query
* @param int $start
* @return SG_iCal_VEvent[]
@ -50,19 +50,19 @@ class SG_iCal_Query {
if( !is_array($ical) ) {
throw new Exception('SG_iCal_Query::After called with invalid input!');
}
$rtn = array();
foreach( $ical AS $e ) {
if( $start <= $e->getStart() ) {
if($e->getStart() >= $start || $e->getRangeEnd() >= $start) {
$rtn[] = $e;
}
}
return $rtn;
}
/**
* Sorts the events from the calendar after the specified column.
* Column can be all valid entires that getProperty can return.
* Column can be all valid entires that getProperty can return.
* So stuff like uid, start, end, summary etc.
* @param SG_iCalReader|array $ical The calendar to query
* @param string $column
@ -75,7 +75,7 @@ class SG_iCal_Query {
if( !is_array($ical) ) {
throw new Exception('SG_iCal_Query::Sort called with invalid input!');
}
$cmp = create_function('$a, $b', 'return strcmp($a->getProperty("' . $column . '"), $b->getProperty("' . $column . '"));');
usort($ical, $cmp);
return $ical;

Wyświetl plik

@ -14,11 +14,13 @@
*/
class SG_iCal_Recurrence {
public $rrule;
protected $freq;
protected $until;
protected $count;
protected $interval;
protected $bysecond;
protected $byminute;
@ -29,6 +31,7 @@ class SG_iCal_Recurrence {
protected $byyearno;
protected $bymonth;
protected $bysetpos;
protected $wkst;
/**
@ -40,7 +43,6 @@ class SG_iCal_Recurrence {
'byyearday', 'byyearno', 'bymonth', 'bysetpos'
);
/**
* Creates an recurrence object with a passed in line. Parses the line.
* @param object $line an SG_iCal_Line object which will be parsed to get the
@ -56,6 +58,8 @@ class SG_iCal_Recurrence {
* @param string $line the line to be parsed
*/
protected function parseLine($line) {
$this->rrule = $line;
//split up the properties
$recurProperties = explode(';', $line);
$recur = array();
@ -77,6 +81,18 @@ class SG_iCal_Recurrence {
}
}
/**
* Set the $until member
* @param mixed timestamp (int) / Valid DateTime format (string)
*/
public function setUntil($ts) {
if ( is_int($ts) )
$dt = new DateTime('@'.$ts);
else
$dt = new DateTime($ts);
$this->until = $dt->format('Ymd\THisO');
}
/**
* Retrieves the desired member variable and returns it (if it's set)
* @param string $member name of the member variable
@ -103,7 +119,6 @@ class SG_iCal_Recurrence {
* @return mixed string if the member has been set, false otherwise
*/
public function getUntil() {
return $this->getMember('until');
}

Wyświetl plik

@ -116,6 +116,6 @@ class VEventTest extends PHPUnit_Framework_TestCase {
date_default_timezone_set('America/New_York');
$event = new SG_iCal_VEvent($data, $ical);
$this->assertEquals(strtotime('20091030T090000'), $event->getEnd());
$this->assertEquals(strtotime('20091030T090000'), $event->getProperty('laststart'));
}
}

Wyświetl plik

@ -2,6 +2,7 @@
require_once 'PHPUnit/Framework.php';
require_once dirname(__FILE__).'/FreqTest.php';
require_once dirname(__FILE__).'/RecurrenceTest.php';
require_once dirname(__FILE__).'/DurationTest.php';
class Helpers_AllTests {
@ -9,6 +10,7 @@ class Helpers_AllTests {
$suite = new PHPUnit_Framework_TestSuite('Helpers');
$suite->addTestSuite('FreqTest');
$suite->addTestSuite('RecurrenceTest');
$suite->addTestSuite('DurationTest');
return $suite;
}

Wyświetl plik

@ -0,0 +1,35 @@
<?php
require_once dirname(__FILE__) . '/../common.php';
require_once 'PHPUnit/Framework.php';
class DurationTest extends PHPUnit_Framework_TestCase {
public function setUp() {
$this->secsMin = 60;
$this->secsHour = 60 * 60;
$this->secsDay = 24 * 60 * 60;
$this->secsWeek = 7 * 24 * 60 * 60;
}
public function testDurationDateTime() {
//A duration of 10 days, 6 hours and 20 seconds
$dur = new SG_iCal_Duration('P10DT6H0M20S');
$this->assertEquals($this->secsDay*10 + $this->secsHour*6 + 20, $dur->getDuration() );
}
public function testDurationWeek() {
//A duration of 2 weeks
$dur = new SG_iCal_Duration('P2W');
$this->assertEquals($this->secsWeek * 2, $dur->getDuration() );
}
public function testDurationNegative() {
//A duration of -1 day
$dur = new SG_iCal_Duration('-P1D');
$this->assertEquals(-1 * $this->secsDay, $dur->getDuration() );
}
}

Wyświetl plik

@ -23,12 +23,12 @@ class FreqTest extends PHPUnit_Framework_TestCase {
873961200,
-1
);
$rule = 'FREQ=DAILY;COUNT=10';
$start = strtotime('19970902T090000');
$this->assertRule( $rule, $start, $dateset);
}
public function testDailyUntil() {
$dateset = array(
873183600,
@ -43,15 +43,15 @@ class FreqTest extends PHPUnit_Framework_TestCase {
873961200,
874047600
);
$rule = 'FREQ=DAILY;UNTIL=19971224T000000Z';
$start = strtotime('19970902T090000');
$this->assertRule( $rule, $start, $dateset);
$freq = new SG_iCal_Freq($rule, $start);
$this->assertEquals(882864000, $freq->previousOccurrence(time()));
}
public function testDailyInterval() {
$dateset = array(
873183600,
@ -66,7 +66,7 @@ class FreqTest extends PHPUnit_Framework_TestCase {
$start = strtotime('19970902T090000');
$this->assertRule( $rule, $start, $dateset);
}
public function testDailyIntervalCount() {
$dateset = array(
873183600,
@ -80,7 +80,7 @@ class FreqTest extends PHPUnit_Framework_TestCase {
$start = strtotime('19970902T090000');
$this->assertRule( $rule, $start, $dateset);
}
public function testDailyBydayBymonthUntil() {
$rules = array(
'FREQ=YEARLY;UNTIL=20000131T090000Z;BYMONTH=1;BYDAY=SU,MO,TU,WE,TH,FR,SA',
@ -106,22 +106,22 @@ class FreqTest extends PHPUnit_Framework_TestCase {
946972800
)
);
foreach( $rules As $rule ) {
$start = strtotime('19980101T090000');
$this->assertRule( $rule, $start, $datesets[0]);
$start = strtotime('+1 year', $start);
$this->assertRule( $rule, $start, $datesets[1]);
$start = strtotime('+1 year', $start);
$this->assertRule( $rule, $start, $datesets[2]);
$freq = new SG_iCal_Freq($rule, $start);
$this->assertEquals(949305600, $freq->previousOccurrence(time()));
}
}
public function testWeeklyCount() {
$dateset = array(
873183600,
@ -140,7 +140,7 @@ class FreqTest extends PHPUnit_Framework_TestCase {
$start = strtotime('19970902T090000');
$this->assertRule( $rule, $start, $dateset);
}
public function testWeeklyUntil() {
$dateset = array(
873183600,
@ -158,11 +158,11 @@ class FreqTest extends PHPUnit_Framework_TestCase {
$rule = 'FREQ=WEEKLY;UNTIL=19971224T000000Z';
$start = strtotime('19970902T090000');
$this->assertRule( $rule, $start, $dateset);
$freq = new SG_iCal_Freq($rule, $start);
$this->assertEquals(882864000, $freq->previousOccurrence(time()), 'Failed getting correct end date');
}
public function testWeeklyBydayLimit() {
$rules = array(
'FREQ=WEEKLY;UNTIL=19971007T000000Z;WKST=SU;BYDAY=TU,TH',
@ -186,7 +186,7 @@ class FreqTest extends PHPUnit_Framework_TestCase {
$this->assertRule( $rule, $start, $dateset);
}
}
public function testWeeklyIntervalUntilByday() {
$dateset = array(
873183600,
@ -204,11 +204,11 @@ class FreqTest extends PHPUnit_Framework_TestCase {
$rule = 'FREQ=WEEKLY;INTERVAL=2;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR';
$start = strtotime('19970902T090000');
$this->assertRule( $rule, $start, $dateset);
$freq = new SG_iCal_Freq($rule, $start);
$this->assertEquals(882777600, $freq->previousOccurrence(time()), 'Failed getting correct end date');
}
public function testWeeklyIntervalBydayCount() {
$dateset = array(
873183600,
@ -225,7 +225,7 @@ class FreqTest extends PHPUnit_Framework_TestCase {
$start = strtotime('19970902T090000');
$this->assertRule( $rule, $start, $dateset);
}
public function testMonthlyBydayCount() {
$dateset = array(
873442800,
@ -244,7 +244,7 @@ class FreqTest extends PHPUnit_Framework_TestCase {
$start = strtotime('19970905T090000');
$this->assertRule( $rule, $start, $dateset);
}
public function testMonthlyBydayUntil() {
$dateset = array(
873442800,
@ -257,7 +257,7 @@ class FreqTest extends PHPUnit_Framework_TestCase {
$start = strtotime('19970905T090000');
$this->assertRule( $rule, $start, $dateset);
}
public function testMonthlyIntervalBydayCount2() {
$dateset = array(
873615600,
@ -291,7 +291,7 @@ class FreqTest extends PHPUnit_Framework_TestCase {
$start = strtotime('19970922T090000');
$this->assertRule( $rule, $start, $dateset);
}
public function testMonthlyBymonthday() {
$dateset = array(
875430000,
@ -305,7 +305,7 @@ class FreqTest extends PHPUnit_Framework_TestCase {
$start = strtotime('19970928T090000');
$this->assertRule( $rule, $start, $dateset);
}
public function testMonthlyBymonthdayCount() {
$dateset = array(
873183600,
@ -324,7 +324,7 @@ class FreqTest extends PHPUnit_Framework_TestCase {
$start = strtotime('19970902T090000');
$this->assertRule( $rule, $start, $dateset);
}
public function testMonthlyBymonthdayCount2() {
$dateset = array(
875602800,
@ -343,7 +343,7 @@ class FreqTest extends PHPUnit_Framework_TestCase {
$start = strtotime('19970930T090000');
$this->assertRule( $rule, $start, $dateset);
}
public function testMonthlyIntervalBymonthdayCount() {
$dateset = array(
873874800,
@ -362,7 +362,7 @@ class FreqTest extends PHPUnit_Framework_TestCase {
$start = strtotime('19970910T090000');
$this->assertRule( $rule, $start, $dateset);
}
public function testMonthlyIntervalByday() {
$dateset = array(
873183600,
@ -380,7 +380,7 @@ class FreqTest extends PHPUnit_Framework_TestCase {
$start = strtotime('19970902T090000');
$this->assertRule( $rule, $start, $dateset);
}
public function testYearlyCountBymonth() {
$dateset = array(
865926000,
@ -399,7 +399,7 @@ class FreqTest extends PHPUnit_Framework_TestCase {
$start = strtotime('19970610T090000');
$this->assertRule( $rule, $start, $dateset);
}
public function testYearlyIntervalCountBymonth() {
$dateset = array(
857980800,
@ -418,7 +418,7 @@ class FreqTest extends PHPUnit_Framework_TestCase {
$start = strtotime('19970310T090000');
$this->assertRule( $rule, $start, $dateset);
}
public function testYearlyIntervalCountByyearday() {
$dateset = array(
852105600,
@ -437,7 +437,7 @@ class FreqTest extends PHPUnit_Framework_TestCase {
$start = strtotime('19970101T090000');
$this->assertRule( $rule, $start, $dateset);
}
public function testYearlyByday() {
$dateset = array(
864025200,
@ -448,7 +448,7 @@ class FreqTest extends PHPUnit_Framework_TestCase {
$start = strtotime('19970519T090000');
$this->assertRule( $rule, $start, $dateset);
}
public function testYearlyByweeknoByday() {
$dateset = array(
863420400,
@ -509,7 +509,7 @@ class FreqTest extends PHPUnit_Framework_TestCase {
$start = strtotime('19970902T090000');
$this->assertRule( $rule, $start, $dateset);
}
public function testYearlyBydayBymonthday2() {
$dateset = array(
874134000,
@ -527,7 +527,7 @@ class FreqTest extends PHPUnit_Framework_TestCase {
$start = strtotime('19970913T090000');
$this->assertRule( $rule, $start, $dateset);
}
public function testYearlyIntervalBymonthBydayBymonthday() {
$dateset = array(
847180800,
@ -538,9 +538,9 @@ class FreqTest extends PHPUnit_Framework_TestCase {
$start = strtotime('19961105T090000');
$this->assertRule( $rule, $start, $dateset);
}
// TODO: SETPOS rules
public function testHourlyIntervalUntil() {
$dateset = array(
873183600,
@ -552,7 +552,7 @@ class FreqTest extends PHPUnit_Framework_TestCase {
$start = strtotime('19970902T090000');
$this->assertRule( $rule, $start, $dateset);
}
public function testMinutelyIntervalCount() {
$dateset = array(
873183600,
@ -567,7 +567,7 @@ class FreqTest extends PHPUnit_Framework_TestCase {
$start = strtotime('19970902T090000');
$this->assertRule( $rule, $start, $dateset);
}
public function testMinutelyIntervalCount2() {
$dateset = array(
873183600,
@ -580,11 +580,11 @@ class FreqTest extends PHPUnit_Framework_TestCase {
$start = strtotime('19970902T090000');
$this->assertRule( $rule, $start, $dateset);
}
public function testMinutelyIntervalByhour() {
$rules = array(
'FREQ=MINUTELY;INTERVAL=20;BYHOUR=9,10,11,12,13,14,15,16'/*,
'FREQ=DAILY;BYHOUR=9,10,11,12,13,14,15,16;BYMINUTE=0,20,40'*/
'FREQ=DAILY;BYHOUR=9,10,11,12,13,14,15,16;BYMINUTE=0,20,40'*/
);
// TODO: Fix it so multi byhour and byminute will work
$dateset = array(
@ -605,25 +605,68 @@ class FreqTest extends PHPUnit_Framework_TestCase {
}
}
public function testLastOccurrence() {
/*
weird : in this test $start is not a matched occurrence but...
to do something like that, we need EXDATE :
DTSTART;TZID=US-Eastern:19970902T090000
EXDATE;TZID=US-Eastern:19970902T090000
RRULE:FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13
*/
public function testFirstOccurrencesByYearDay() {
$rule = 'FREQ=YEARLY;INTERVAL=2;BYYEARDAY=1;COUNT=5';
$start = strtotime('2009-10-27T090000');
$freq = new SG_iCal_Freq($rule, $start);
$this->assertEquals(strtotime('2018-01-01T09:00:00'), $freq->lastOccurrence());
$this->assertEquals(strtotime('2009-10-27T09:00:00'), $freq->firstOccurrence());
$this->assertEquals(strtotime('2011-01-01T09:00:00'), $freq->nextOccurrence($start));
}
// TODO: WKST rule
public function testFirstOccurrencesByYearDayWithoutFirstDate() {
$rule = 'FREQ=YEARLY;INTERVAL=2;BYYEARDAY=1;COUNT=5';
$start = strtotime('2009-10-27T090000');
$freq = new SG_iCal_Freq($rule, $start, array($start));
$this->assertEquals(strtotime('2011-01-01T09:00:00'), $freq->firstOccurrence());
}
public function testLastOccurrenceByYearDay() {
$rule = 'FREQ=YEARLY;INTERVAL=2;BYYEARDAY=1;COUNT=5';
$start = strtotime('2011-01-01T090000');
$freq = new SG_iCal_Freq($rule, $start);
$this->assertEquals(strtotime('2019-01-01T09:00:00'), $freq->lastOccurrence());
}
public function testCacheCount() {
$rule = 'FREQ=YEARLY;INTERVAL=2;BYYEARDAY=1;COUNT=5';
$start = strtotime('2011-01-01T090000');
$freq = new SG_iCal_Freq($rule, $start);
$this->assertEquals(5, count($freq->getAllOccurrences()));
$this->assertEquals(strtotime('2019-01-01T09:00:00'), $freq->lastOccurrence());
}
/* TODO: BYSETPOS rule :
The 3rd instance into the month of one of Tuesday, Wednesday or
Thursday, for the next 3 months:
DTSTART;TZID=US-Eastern:19970904T090000
RRULE:FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,TH;BYSETPOS=3
*/
/* TODO: WKST rule
*/
//check a serie of dates
private function assertRule( $rule, $start, $dateset ) {
$freq = new SG_iCal_Freq($rule, $start);
reset($dateset);
$n = $start - 1;
do {
$n = $freq->findNext($n);
//echo date('Y-m-d H:i:sO ',$n);
$e = (current($dateset) != -1) ? current($dateset) : false;
$this->assertEquals($e, $n);
} while( next($dateset) !== false );
}
}
?>
?>

5
tests/test.sh 100755
Wyświetl plik

@ -0,0 +1,5 @@
#!/bin/sh
# aptitude install phpunit
phpunit AllTests.php

Wyświetl plik

@ -0,0 +1,6 @@
#!/bin/sh
TIMESTAMP=$1
php -r "echo date('Y-m-d H:i:s O',$TIMESTAMP); echo \"\n\"; "