-
-
Save edewaal97/344ec108931ac4c9e8ca7874b2db83cb to your computer and use it in GitHub Desktop.
<?php | |
// SOURCE: https://gist.github.com/seebz/c00a38d9520e035a6a8c | |
class iCal | |
{ | |
/** | |
* @var string | |
*/ | |
public $title; | |
/** | |
* @var string | |
*/ | |
public $description; | |
/** | |
* @var array | |
*/ | |
public $events = array(); | |
/** | |
* @var array | |
*/ | |
protected $_eventsByDate; | |
public function __construct($content = null) | |
{ | |
if ($content) { | |
$isUrl = strpos($content, 'http') === 0 && filter_var($content, FILTER_VALIDATE_URL); | |
$isFile = strpos($content, "\n") === false && file_exists($content); | |
if ($isUrl || $isFile) { | |
$content = file_get_contents($content); | |
} | |
$this->parse($content); | |
} | |
} | |
public function title() | |
{ | |
return $this->summary; | |
} | |
public function description() | |
{ | |
return $this->description; | |
} | |
public function events() | |
{ | |
return $this->events; | |
} | |
public function eventsByDate() | |
{ | |
if (! $this->_eventsByDate) { | |
$this->_eventsByDate = array(); | |
$tmpEventsByDate = array(); | |
foreach ($this->events() as $event) { | |
foreach ($event->occurrences() as $occurrence) { | |
$date = date('Y-m-d', $occurrence); | |
$newevent = clone $event; | |
$newevent->fixOccurringDate($occurrence); | |
// generate key for sorting | |
$key = strtotime($newevent->dateStart); | |
while(isset($tmpEventsByDate[$date][$key])) $key++; | |
$tmpEventsByDate[$date][$key] = $newevent; | |
} | |
} | |
// sort array | |
ksort($tmpEventsByDate); | |
foreach ($tmpEventsByDate as $date => $value) { | |
ksort($value); | |
$this->_eventsByDate[$date] = $value; | |
} | |
// prevent duplicates for edited dates in recurring events | |
foreach ($this->_eventsByDate as $dateKey => $date) { | |
foreach ($date as $event) { | |
if(!empty($event->recurrenceId)) { | |
$uid = $event->uid; | |
foreach ($date as $eventKey => $eventValue) { | |
if($eventValue->uid == $uid && (empty($eventValue->recurrenceId))) { | |
unset($this->_eventsByDate[$dateKey][$eventKey]); | |
} | |
} | |
} | |
} | |
} | |
} | |
return $this->_eventsByDate; | |
} | |
public function eventsByDateBetween($start, $end, int $limit=NULL) | |
{ | |
if ((string) (int) $start !== (string) $start) { | |
$start = strtotime($start); | |
} | |
$start = date('Y-m-d', $start); | |
if ((string) (int) $end !== (string) $end) { | |
$end = strtotime($end); | |
} | |
$end = date('Y-m-d', $end); | |
$return = array(); | |
foreach ($this->eventsByDate() as $date => $events) { | |
if ($start <= $date && $date < $end) { | |
if(empty($limit) || count($return) <= $limit) { | |
$return[$date] = $events; | |
} | |
} | |
if(!empty($limit) && count($return) >= $limit){ | |
break; | |
} | |
} | |
return $return; | |
} | |
public function eventsByDateSince($start, int $limit=NULL) | |
{ | |
if ((string) (int) $start !== (string) $start) { | |
$start = strtotime($start); | |
} | |
$start = date('Y-m-d', $start); | |
$return = array(); | |
foreach ($this->eventsByDate() as $date => $events) { | |
if ($start <= $date) { | |
if(empty($limit) || count($return) <= $limit) { | |
$return[$date] = $events; | |
} | |
} | |
if(!empty($limit) && count($return) >= $limit){ | |
break; | |
} | |
} | |
return $return; | |
} | |
public function eventsByDateUntil($end, int $limit=NULL) | |
{ | |
if ((string) (int) $end !== (string) $end) { | |
$end = strtotime($end); | |
} | |
$start = date('Y-m-d'); | |
$end = date('Y-m-d', $end); | |
$return = array(); | |
foreach ($this->eventsByDate() as $date => $events) { | |
if ($start <= $date && $end >= $date) { | |
if(empty($limit) || count($return) <= $limit) { | |
$return[$date] = $events; | |
} | |
} | |
if(!empty($limit) && count($return) >= $limit){ | |
break; | |
} | |
} | |
return $return; | |
} | |
public function parse($content) | |
{ | |
$content = str_replace("\r\n ", '', $content); | |
// Title | |
preg_match('`^X-WR-CALNAME:(.*)$`m', $content, $m); | |
$this->title = $m ? trim($m[1]) : null; | |
// Description | |
preg_match('`^X-WR-CALDESC:(.*)$`m', $content, $m); | |
$this->description = $m ? trim($m[1]) : null; | |
// Events | |
preg_match_all('`BEGIN:VEVENT(.+)END:VEVENT`Us', $content, $m); | |
foreach ($m[0] as $c) { | |
$this->events[] = new iCal_Event($c); | |
} | |
return $this; | |
} | |
} | |
class iCal_Event | |
{ | |
/** | |
* @var string | |
*/ | |
public $uid; | |
/** | |
* @var string | |
*/ | |
public $summary; | |
/** | |
* @var string | |
*/ | |
public $description; | |
/** | |
* @var string | |
*/ | |
public $dateStart; | |
/** | |
* @var string | |
*/ | |
public $dateEnd; | |
/** | |
* @var string | |
*/ | |
public $recurrenceId; | |
/** | |
* @var array | |
*/ | |
public $exdate = array(); | |
/** | |
* @var stdClass | |
*/ | |
public $recurrence; | |
/** | |
* @var string | |
*/ | |
public $location; | |
/** | |
* @var string | |
*/ | |
public $status; | |
/** | |
* @var string | |
*/ | |
public $created; | |
/** | |
* @var string | |
*/ | |
public $updated; | |
/** | |
* @var integer | |
*/ | |
protected $_timeStart; | |
/** | |
* @var integer | |
*/ | |
protected $_timeEnd; | |
/** | |
* @var integer | |
*/ | |
protected $_recurrenceId; | |
/** | |
* @var array | |
*/ | |
protected $_occurrences; | |
public function __construct($content = null) | |
{ | |
if ($content) { | |
$this->parse($content); | |
} | |
} | |
public function summary() | |
{ | |
return $this->summary; | |
} | |
public function title() | |
{ | |
return $this->summary; | |
} | |
public function description() | |
{ | |
return $this->description; | |
} | |
public function occurrences() | |
{ | |
if (empty($this->_occurrences)) { | |
$this->_occurrences = $this->_calculateOccurrences(); | |
} | |
return $this->_occurrences; | |
} | |
public function duration() | |
{ | |
// if ($this->_timeEnd) { | |
return $this->_timeEnd - $this->_timeStart; | |
// } | |
} | |
public function parse($content) | |
{ | |
$content = str_replace("\r\n ", '', $content); | |
// UID | |
if (preg_match('`^UID:(.*)$`m', $content, $m)) | |
$this->uid = trim($m[1]); | |
// Summary | |
if (preg_match('`^SUMMARY:(.*)$`m', $content, $m)) | |
$this->summary = trim($m[1]); | |
// Description | |
if (preg_match('`^DESCRIPTION:(.*)$`m', $content, $m)) | |
$this->description = trim($m[1]); | |
// Date start | |
if (preg_match('`^DTSTART(?:;.+)?:([0-9]+(T[0-9]+Z?)?)`m', $content, $m)) { | |
$this->_timeStart = strtotime($m[1]); | |
$this->dateStart = date('Y-m-d H:i:s', $this->_timeStart); | |
} | |
// Date end | |
if (preg_match('`^DTEND(?:;.+)?:([0-9]+(T[0-9]+Z?)?)`m', $content, $m)) { | |
$this->_timeEnd = strtotime($m[1]); | |
$this->dateEnd = date('Y-m-d H:i:s', $this->_timeEnd); | |
} | |
// Recurrence-Id | |
if (preg_match('`^RECURRENCE-ID(?:;.+)?:([0-9]+(T[0-9]+Z?)?)`m', $content, $m)) { | |
$this->_recurrenceId = strtotime($m[1]); | |
$this->recurrenceId = date('Y-m-d H:i:s', $this->_recurrenceId); | |
} | |
// Exdate | |
if (preg_match_all('`^EXDATE(;.+)?:([0-9]+(T[0-9]+Z?)?)`m', $content, $m)) { | |
foreach ($m[2] as $dates) { | |
$dates = explode(',', $dates); | |
foreach ($dates as $d) { | |
$this->exdate[] = date('Y-m-d', strtotime($d)); | |
} | |
} | |
} | |
// Recurrence | |
if (preg_match('`^RRULE:(.*)`m', $content, $m)) { | |
$rules = (object) array(); | |
$rule = trim($m[1]); | |
$rule = explode(';', $rule); | |
foreach ($rule as $r) { | |
list($key, $value) = explode('=', $r); | |
$rules->{ strtolower($key) } = $value; | |
} | |
if (isset($rules->until)) { | |
$rules->until = date('Y-m-d H:i:s', strtotime($rules->until)); | |
} | |
if (isset($rules->count)) { | |
$rules->count = intval($rules->count); | |
} | |
if (isset($rules->interval)) { | |
$rules->interval = intval($rules->interval); | |
} | |
if (isset($rules->byday)) { | |
$rules->byday = explode(',', $rules->byday); | |
} | |
// Avoid infinite recurrences | |
if (! isset($rules->until) && ! isset($rules->count)) { | |
$rules->count = 500; | |
} | |
$this->recurrence = $rules; | |
} | |
// Location | |
if (preg_match('`^LOCATION:(.*)$`m', $content, $m)) | |
$this->location = trim($m[1]); | |
// Status | |
if (preg_match('`^STATUS:(.*)$`m', $content, $m)) | |
$this->status = trim($m[1]); | |
// Created | |
if (preg_match('`^CREATED:(.*)`m', $content, $m)) | |
$this->created = date('Y-m-d H:i:s', strtotime(trim($m[1]))); | |
// Updated | |
if (preg_match('`^LAST-MODIFIED:(.*)`m', $content, $m)) | |
$this->updated = date('Y-m-d H:i:s', strtotime(trim($m[1]))); | |
return $this; | |
} | |
public function isRecurrent() | |
{ | |
return ! empty($this->recurrence); | |
} | |
public function fixOccurringDate($timestamp) | |
{ | |
if($timestamp != $this->_timeStart) { | |
// calculate correct start & end date if not a repeating event | |
$duration = $this->duration(); | |
// get date from occurrences | |
$timestampCalc = new DateTime(); | |
$timestampCalc->setTimestamp($timestamp); | |
// make new startdate and start timestamp | |
$startCalc = new DateTime(); | |
$startCalc->setTimestamp($this->_timeStart); | |
$startCalc->setDate($timestampCalc->format('Y'), $timestampCalc->format('m'), $timestampCalc->format('d')); | |
$this->_timeStart = $startCalc->getTimestamp(); | |
$this->dateStart = date('Y-m-d H:i:s', $this->_timeStart); | |
// calculate end date and time with duration of original event. | |
$this->_timeEnd += - $this->_timeStart + $duration; | |
$this->dateEnd = date('Y-m-d H:i:s', $this->_timeEnd); | |
} | |
} | |
protected function _isExdate($date) | |
{ | |
if ((string) (int) $date != $date) { | |
$date = strtotime($date); | |
} | |
$date = date('Y-m-d', $date); | |
return in_array($date, $this->exdate); | |
} | |
protected function _calculateOccurrences() | |
{ | |
$occurrences = array($this->_timeStart); | |
if ($this->isRecurrent()) | |
{ | |
$freq = $this->recurrence->freq; | |
$count = isset($this->recurrence->count) ? $this->recurrence->count : null; | |
$until = isset($this->recurrence->until) ? strtotime($this->recurrence->until) : null; | |
$callbacks = array( | |
'YEARLY' => '_nextYearlyOccurrence', | |
'MONTHLY' => '_nextMonthlyOccurrence', | |
'WEEKLY' => '_nextWeeklyOccurrence', | |
'DAILY' => '_nextDailyOccurrence' | |
); | |
$callback = $callbacks[$freq]; | |
$offset = $this->_timeStart; | |
$continue = $until ? ($offset < $until) : ($count > 1); | |
while ($continue) { | |
if(isset($occurrence)) { | |
if (! $this->_isExdate($occurrence)) { | |
$occurrences[] = $occurrence; | |
$count--; | |
} | |
} | |
$occurrence = $this->{$callback}($offset); | |
$offset = $occurrence; | |
$continue = $until ? ($offset < $until) : ($count > 1); | |
} | |
} | |
if ($this->_isExdate($occurrences[0])) { | |
unset($occurrences[0]); | |
$occurrences = array_values($occurrences); | |
} | |
return $occurrences; | |
} | |
protected function _nextYearlyOccurrence($offset) | |
{ | |
$interval = isset($this->recurrence->interval) | |
? $this->recurrence->interval | |
: 1; | |
return strtotime("+{$interval} year", $offset); | |
} | |
protected function _nextMonthlyOccurrence($offset) | |
{ | |
$dayname = array( | |
'MO' => 'monday', | |
'TU' => 'tuesday', | |
'WE' => 'wednesday', | |
'TH' => 'thursday', | |
'FR' => 'friday', | |
'SA' => 'saturday', | |
'SU' => 'sunday' | |
); | |
$interval = isset($this->recurrence->interval) | |
? $this->recurrence->interval | |
: 1; | |
// INTERVAL IS BY (COUNT)DAYNAME | |
if(isset($this->recurrence->byday)){ | |
$dates = array(); | |
foreach ($this->recurrence->byday as $pattern) { | |
$offsetDateTime = new DateTime(); | |
$offsetDateTime->setTimestamp((int) $offset); | |
preg_match('`([-]?\d+)?(MO|TU|WE|TH|FR|SA|SU)`m', $pattern, $m); | |
$recurrenceOffset = (isset($m[1])) ? (int) $m[1] : 1; | |
$recurrenceDay = strtr($m[2], $dayname); | |
$forDateTime = clone $offsetDateTime; | |
for ( | |
$month = (int) $offsetDateTime->format('Ym'); | |
$month <= date('Ym', strtotime('+' . $interval*12 . ' months')); | |
$month = (int) $forDateTime->modify('+'.$interval.' months')->format('Ym') | |
) { | |
$yearMonth = $forDateTime->format('Y-m'); | |
$firstDay = new DateTime('first '. $recurrenceDay . ' of ' . $yearMonth); | |
$lastDay = new DateTime('last '. $recurrenceDay . ' of ' . $yearMonth); | |
$newDate = $firstDay; | |
$daysInMonth = array(); | |
while ($newDate->getTimestamp() <= $lastDay->getTimestamp()) { | |
$daysInMonth[] = $newDate->getTimestamp(); | |
$newDate->modify('next '. $recurrenceDay); | |
} | |
if($recurrenceOffset < 0) { | |
$dates[] = $daysInMonth[count($daysInMonth) + $recurrenceOffset]; | |
} else { | |
$dates[] = $daysInMonth[$recurrenceOffset - 1]; | |
} | |
} | |
} | |
sort($dates); | |
foreach ($dates as $date) { | |
if ($date > $offset) { | |
return $date; | |
} | |
} | |
} | |
// INTERVAL IS BY DAYNUMBER OF MONTH | |
$bymonthday = isset($this->recurrence->bymonthday) | |
? explode(',', $this->recurrence->bymonthday) | |
: array(date('d', $offset)); | |
$start = strtotime(date('Y-m-01 H:i:s', $offset)); | |
$dates = array(); | |
foreach ($bymonthday as $day) { | |
// this month | |
$dates[] = strtotime(($day-1) . ' day', $start); | |
// next 'interval' month | |
$tmp = strtotime("+{$interval} month", $start); | |
$time = strtotime(($day-1) . ' day', $tmp); | |
if ((string) (int) date('d', $time) == (int) $day) { | |
$dates[] = $time; | |
} | |
// 2x 'interval' month | |
$interval *= 2; | |
$tmp = strtotime("+{$interval} month", $start); | |
$time = strtotime(($day-1) . ' day', $tmp); | |
if ((string) (int) date('d', $time) === (int) $day) { | |
$dates[] = $time; | |
} | |
} | |
sort($dates); | |
foreach ($dates as $date) { | |
if ($date > $offset) { | |
return $date; | |
} | |
} | |
} | |
protected function _nextWeeklyOccurrence($offset) | |
{ | |
$interval = isset($this->recurrence->interval) | |
? $this->recurrence->interval | |
: 1; | |
$byday = isset($this->recurrence->byday) | |
? $this->recurrence->byday | |
: array( substr(strtoupper(date('D', $offset)), 0, 2) ); | |
$start = date('l', $offset) !== 'Monday' | |
? strtotime('last monday', $offset) | |
: $offset; | |
$daysname = array( | |
'MO' => 'monday', | |
'TU' => 'tuesday', | |
'WE' => 'wednesday', | |
'TH' => 'thursday', | |
'FR' => 'friday', | |
'SA' => 'saturday', | |
'SU' => 'sunday', | |
); | |
$dates = array(); | |
foreach ($byday as $day) { | |
$dayname = $daysname[$day]; | |
// this week | |
$dates[] = strtotime($dayname, $start); | |
// next 'interval' week | |
$tmp = strtotime("+{$interval} week", $start); | |
$time = strtotime($dayname, $tmp); | |
$dates[] = $time; | |
} | |
sort($dates); | |
foreach ($dates as $date) { | |
if ($date > $offset) { | |
return $date; | |
} | |
} | |
} | |
protected function _nextDailyOccurrence($offset) | |
{ | |
$interval = isset($this->recurrence->interval) | |
? $this->recurrence->interval | |
: 1; | |
return strtotime("+{$interval} day", $offset); | |
} | |
} |
<?php | |
include 'iCal.php'; | |
$file = 'http://www.google.com/calendar/ical/ht3jlfaac5lfd6263ulfh4tql8%40group.calendar.google.com/public/basic.ics'; | |
$iCal = new iCal($file); | |
$events = $iCal->eventsByDate(); | |
// or : | |
// $events = $iCal->eventsByDateBetween('2014-01-01', '2015-01-01'); | |
// or : | |
// $events = $iCal->eventsByDateSince('2014-01-01'); | |
// or : | |
// $events = $iCal->eventsByDateSince('today'); | |
// or : | |
// $events = $iCal->eventsByDateUntil('+30 days'); | |
// in addition, every function starting with eventsBy... can be extended with another variable with a limit on the amount of events. | |
// for example: | |
// $events = $iCal->eventsByDateUntil('+30 days', 10); | |
foreach ($events as $date => $events) | |
{ | |
echo $date . "\n"; | |
echo '----------' . "\n"; | |
foreach ($events as $event) | |
{ | |
echo '* ' . $event->title() . "\n"; | |
} | |
echo "\n"; | |
} |
Is there some method to add a new event??
@cinnamon17 Unfortunately that is not possible currently. You can add events by adding them to the calendar using the program for the source (Google calendar in this example). Otherwise you are free to fork it and code it yourself. Unfortunately there is no way to push the new event back to the source. That's a limitation of the api/ical specification.
@edewaal97 Correct regex in line 349 and 353 for utf-8 encoding. It requires a regex for form: SUMMATY ;CHARSET=utf-8 : ...
Here's my fork.
Hi,
Thank you for this Script! Ist Very helpfull!
How can i diplay Start time of the event?
Thanks
Sissad
@edewaal97 I think a nice addition for the iCal class would be a function that returns the current event, if any.
@edewaal97 I am new on this i need the available dates not reserved date, I am trying to find the property by between two dates where property is available.
I would like to output something like this:
<li><a href="link to event in calendar">Title of Event - Date of Event using Month, Day format</a></li>
This would need to output the next 3 events from today forward.
So far I have been able to do it all except for the link and reformatting the date. Is this possible? Thank you.
Looks good, but how i get URL of event detail? Somethink like this https://calendar.google.com/calendar/u/0/r/day/2021/9/11?eid=Y2xoNjhjMWw2aGgzOGI5bjYxaGpjYjlrY2RqNmNiYjI2ZGgzNmI5b2M0cGowZTFsY2tzajBjajQ2ZyBsc2w1MmEwMTcydDdiczRndGdrbTViMWdzMEBn&ctz=Europe/Prague&sf=true
i have it
i have it
How were you able to get the url of the event? Also did you see my comment about reformatting the date? Is that something you could help me with?
i have it
How were you able to get the url of the event? Also did you see my comment about reformatting the date? Is that something you could help me with?
I added new functionality which can do it. Here is my version with example:
https://gist.github.com/Kcko/901b82682bcd14a511b1bb4939cd5543
Is needed to add identificator of your calendar as a second argument (see example).
Your question about date - It belongs to the basic operations in PHP, what exactly do you not know?
@Kcko For some reason your fork doesn't work getting the URL for me.
As far as the reformatting the date, I need to change the date('Y-m-d') to date('F j'). I have tried replacing using all instances or individual instances, and it either does nothing or breaks the page. I have also tried adding $date = date('F j', $date); to my code, but that didn't work either.
Here is the code I am using to output what I want.
$events = $iCal->eventsByDateUntil('+30 days', 3);
foreach ($events as $date => $events)
{
foreach ($events as $event)
{
echo "
}
}
The other I am getting is that I should only be outputting 3 events, but if the date is the same, it doesn't count them into the total. Here is an example:
- Last day to withdraw with “W” grade - First half block - 2021-09-24
- Free GED class Orientation - Du Quoin morning - 2021-09-28
- Free GED class Orientation - Murphysboro Afternoon - 2021-09-28
- Student Senate meeting - 2021-09-28
- PTK Honor Society meeting - 2021-09-28
- Honor Society Information Session - Phi Theta Kappa Honor Society - 2021-09-29
I am getting 6 returned, I believe because 4 have the same date.
Thanks in advance for any help.
@Kcko For some reason your fork doesn't work getting the URL for me.
As far as the reformatting the date, I need to change the date('Y-m-d') to date('F j'). I have tried replacing using all instances or individual instances, and it either does nothing or breaks the page. I have also tried adding $date = date('F j', $date); to my code, but that didn't work either.
Here is the code I am using to output what I want.
$events = $iCal->eventsByDateUntil('+30 days', 3);
foreach ($events as $date => $events){
foreach ($events as $event)
{
echo "
" . $event->title() . " - " . $date . "
";
}
}
The other I am getting is that I should only be outputting 3 events, but if the date is the same, it doesn't count them into the total. Here is an example:Last day to withdraw with “W” grade - First half block - 2021-09-24
Free GED class Orientation - Du Quoin morning - 2021-09-28
Free GED class Orientation - Murphysboro Afternoon - 2021-09-28
Student Senate meeting - 2021-09-28
PTK Honor Society meeting - 2021-09-28
Honor Society Information Session - Phi Theta Kappa Honor Society - 2021-09-29
I am getting 6 returned, I believe because 4 have the same date.
Thanks in advance for any help.
Hi,
The simplest date conversion:
$date = '2021-09-24';
$newFormat = new \DateTime($date);
echo $newFormat->format('F j');
My fork works fine :)
You're probablyy making a mistake somewhere, show me how to use it
Anyone else having trouble with recurring events using this class?
I have an event that starts on 2021-11-03 14:00:00 and ends at 2021-11-03 17:00:00(3hr long event) and recurs 'weekly' for two additional weeks.
When I parse the recurring event I get this back:
First event in the series - reports correctly
startdate of 2021-11-03 14:00:00
enddate of 2021-11-03 17:00:00
Second event: - incorrect
startdate of 2021-11-10 14:00:00
enddate of 1969-12-24 23:00:00
Third event: - incorrect
startdate of 2021-11-17 14:00:00
enddate of 1969-12-17 23:00:00
Does anyone know why I am receiving a 1969 date??? The start date is incrementing by 7 days each week, so what's going on with enddate?
Any help would be appreciated, thanks!
It turns out that the problem was on line 436.
You need to set the $this->_timeStart
variable to a temp variable before adding it to duration else you will run into issues as I described above. The logic is sound but the original implementation was incorrect.
Here is the fix(this allows timestart to be added to duration correctly.)
$ts = $this->_timeStart;
$this->_timeEnd = $ts + $duration;
Hope this helps someone looking at this class in the future.
Thank you,
ML
Thanks @mlongoria1 - that was flummoxing me too!
Oh dear. I'm very sorry. I forgot to use the .php extension.