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,9 +1,10 @@
<?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.
@ -11,7 +12,7 @@ define('SG_ICALREADER_VERSION', '0.5');
*
* 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);
}
/**

Wyświetl plik

@ -10,7 +10,7 @@
* @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.

Wyświetl plik

@ -15,14 +15,27 @@
*/
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
@ -30,7 +43,8 @@ class SG_iCal_VEvent {
* @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']);
@ -39,8 +53,13 @@ class SG_iCal_VEvent {
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']);
}
@ -48,23 +67,51 @@ class SG_iCal_VEvent {
$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');
@ -75,9 +122,27 @@ class SG_iCal_VEvent {
}
}
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
@ -130,6 +195,18 @@ class SG_iCal_VEvent {
}
}
/**
* 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
@ -146,6 +223,14 @@ class SG_iCal_VEvent {
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
@ -169,18 +254,39 @@ class SG_iCal_VEvent {
}
}
/**
* 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,10 +9,10 @@
* @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
@ -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,7 +9,7 @@
*/
class SG_iCal_Duration {
private $dur;
protected $dur;
/**
* Constructs a new SG_iCal_Duration from a duration-rule.
@ -20,28 +20,30 @@ 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;
} else {
// Invalid duration!
$this->dur = 0;
}
$dir = ($duration{0} == '-') ? -1 : 1;
$this->dur = $dir * $ts;
}
/**

Wyświetl plik

@ -23,21 +23,30 @@
* @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;
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;
private $rules = array('freq'=>'yearly', 'interval'=>1);
private $start = 0;
private $freq = '';
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 $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) {
@ -50,7 +59,6 @@ class SG_iCal_Freq {
}
$this->freq = strtolower($this->rules['freq']);
foreach( $this->knownRules AS $rule ) {
if( isset($this->rules['by' . $rule]) ) {
if( $this->isPrerule($rule, $this->freq) ) {
@ -65,13 +73,55 @@ class SG_iCal_Freq {
}
}
//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;
}
/**
@ -81,14 +131,23 @@ 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;
}
/**
@ -97,21 +156,32 @@ class SG_iCal_Freq {
* @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);
}
/**
@ -138,11 +208,18 @@ class SG_iCal_Freq {
* @return int
*/
public function findNext($offset) {
$echo = false;
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,25 +227,28 @@ 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]) ) {
@ -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,20 +274,24 @@ 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;
}
/**
@ -396,7 +480,11 @@ class SG_iCal_Freq {
}
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;
}

Wyświetl plik

@ -15,11 +15,11 @@
* @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();
protected $ident;
protected $data;
protected $params = array();
private $replacements = array('from'=>array('\\,', '\\n', '\\;', '\\:', '\\"'), 'to'=>array(',', "\n", ';', ':', '"'));
protected $replacements = array('from'=>array('\\,', '\\n', '\\;', '\\:', '\\"'), 'to'=>array(',', "\n", ';', ':', '"'));
/**
* Constructs a new line.
@ -75,6 +75,18 @@ class SG_iCal_Line implements ArrayAccess, Countable, IteratorAggregate {
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"..

Wyświetl plik

@ -1,4 +1,4 @@
<?php
<?php // BUILD: Remove line
class SG_iCal_Parser {
/**
@ -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,8 +120,13 @@ 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;

Wyświetl plik

@ -22,14 +22,14 @@ 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;
}
}
@ -53,7 +53,7 @@ class SG_iCal_Query {
$rtn = array();
foreach( $ical AS $e ) {
if( $start <= $e->getStart() ) {
if($e->getStart() >= $start || $e->getRangeEnd() >= $start) {
$rtn[] = $e;
}
}

Wyświetl plik

@ -14,6 +14,8 @@
*/
class SG_iCal_Recurrence {
public $rrule;
protected $freq;
protected $until;
@ -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

@ -605,21 +605,64 @@ 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\"; "