GIT repositories

Index page of all the GIT repositories that are clonable form this server via HTTPS. Übersichtsseite aller GIT-Repositories, die von diesem Server aus über git clone (HTTPS) erreichbar sind.

Services

A bunch of service scripts to convert, analyse and generate data. Ein paar Services zum Konvertieren, Analysieren und Generieren von Daten.

GNU octave web interface

A web interface for GNU Octave, which allows to run scientific calculations from netbooks, tables or smartphones. The interface provides a web form generator for Octave script parameters with pre-validation, automatic script list generation, as well presenting of output text, figures and files in a output HTML page. Ein Webinterface für GNU-Octave, mit dem wissenschaftliche Berechnungen von Netbooks, Tablets oder Smartphones aus durchgeführt werden können. Die Schnittstelle beinhaltet einen Formulargenerator für Octave-Scriptparameter, mit Einheiten und Einfabevalidierung. Textausgabe, Abbildungen und generierte Dateien werden abgefangen und in einer HTML-Seite dem Nutzer als Ergebnis zur Verfügung gestellt.

Calendar class library

Because there are already thousands of calendars in the net, I added my own one as well ;-). This is a highly maintainable and easily extendable bunch of classes to display month and day calendars with events. It consists the following classes:

  • The main calendar class GregorianCalendar, which organizes the bundle and provides the interface to the application/script.

  • the renderers GregorianMonthCalendarRenderer and GregorianDayCalendarRenderer, which are the base for the visualization. They are fed with preprocessed data.

  • The events are managed by the GregorianCalendarEventController. This class is to load, filter and save events in files or a data base, or every possible source (that's why it is a separate class).

  • The class GregorianCalendarEvent is the base class for your own events, managed by the controller and used by the other classes.

That's all. The sample code illustrates that you don't need to do much to have a functioning calendar, and that it is very easy to extend it. The class sources are normally split in more class files and here collected in one block. Normally every class is in a file with the same name as the class and the extension ".class.php".

Da es ja schon Tausende Kalender im Netz gibt, gebe ich hier meinen Senf dazu ;-). Hier ist ein sehr leicht bedienteres und wartbares "Klassenbündel", um Monats- und Tageskalender mit Ereignissen darzustellen, illustriert. Es besteht aus folgenden Klassen:

  • Die Kernklasse GregorianCalendar organisiert alles und bietet die Schnittstelle zur Anwendung bzw. dem Skript, welches den Kalender nutzt.

  • Die Rendering-Klassen GregorianMonthCalendarRenderer und GregorianDayCalendarRenderer sind für die Darstellung verantwortlich. Sie werden schon mit vorbearbeiteten Informationen gefüttert.

  • Der GregorianCalendarEventController ist verantwortlich, dass Ereignisse gespeichert und gefiltert geladen werden können. Das kann aus einer Datenbank sein (wie im Beispiel, aber auch aus einer Datei oder mehreren Dateien, oder durch eine socket-Abfrage geschehen (deshalb ist dies auch eine eigene Klasse)

  • Das GregorianCalendarEvent ist die Basisklasse für Ereignisse, wird vom Controller verwaltet und von den anderen Klassen verwendet.

Am Besten einfach mal den Beispielquelltext unten durchschmökern, dann wird ersichtlich, wie einfach es ist, Änderungen zu machen. Normalerweise sind die hier illustrierten Klassenquelltexte in einzelnen Dateien gespeichert, deren Dateinamen den Klassennamen entsprechen und die Endung ".class.php" haben.

Sample source code

Anwendungsbeispiel

MyCalendar.class.php

<?php
require_once(__DIR__ '/swlib/swlib.class.php');
use sw\GregorianCalendar
use sw\GregorianMonthCalendarRenderer
use sw\GregorianDayCalendarRenderer
use sw\GregorianCalendarEvent
use sw\GregorianCalendarEventController
use sw\LocalDate;
use sw\UtcDate;
 
 
/**
 * Sample Gegorian Month Calendar
 * @package de.atwillys.sw.php.cal.sample
 * @author Stefan Wilhelm, 2010
 * @license GPL
 *
 *
 * If you want to make your own renderer, overload these methods:
 *
 */
class SampleMonthCalendarRenderer extends GregorianMonthCalendarRenderer {
 
    //    public function onStart() {
    //    }
    //
    //    public function onEnd() {
    //    }
    //
    //    public function onWeekStart($weekId) {
    //    }
    //
    //    public function onWeekEnd($weekId) {
    //    }
    //
    //    public function onDay(IDate $day, $isPadding, $isSelected, $events) {
    //    }
}
 
 
/**
 * Sample Gegorian Day Calendar
 * @package de.atwillys.sw.php.cal.sample
 * @author Stefan Wilhelm, 2010
 * @license GPL
 *
 *
 * Here I added the block view of the sample events, it is more or less
 * like a trace dump in the day calendar table:
 *
 */
class SampleDayCalendarRenderer extends GregorianDayCalendarRenderer {
 
    /**
     * Returns a day calendar Event block representation
     * @param GregorianCalendarEvent $event
     * @return string
     *
     * Principally all you have to do is to overload this method as well:
     */
    protected function renderEvent(GregorianCalendarEvent $event) {
        $tick = $this->getParent()->getHourDivision();
        $t1 = new LocalDate($event->getStart());
        $t2 = new LocalDate($event->getEnd());
        $o  = $event->name . " (" . $event->id . ")";
        $o .= '<br/>' . sprintf("%02d:%02d", $t1->getHour(), $t1->getMinute()) .
                ' to ' . sprintf("%02d:%02d", $t2->getHour(), $t2->getMinute());
        $t1 = $tick * floor($event->getStart() / $tick);
        $t2 = $tick * floor($event->getEnd() / $tick);
        $t1 = new LocalDate($t1);
        $t2 = new LocalDate($t2);
        $o .= '<br/>' . sprintf("%02d:%02d", $t1->getHour(), $t1->getMinute()) .
                ' to ' . sprintf("%02d:%02d", $t2->getHour(), $t2->getMinute());
        return $o;
    }
}
 
 
/**
 * Sample Gegorian Calendar event
 * @package de.atwillys.sw.php.cal.sample
 * @author Stefan Wilhelm, 2010
 * @license GPL
 *
 * This is a sample event, which is saved in the data base, the table is
 * build with the SQL:
 *
 *    SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
 *    CREATE TABLE IF NOT EXISTS `events` (
 *      `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
 *      `name` text NOT NULL,
 *      `begin` bigint(20) NOT NULL,
 *      `end` bigint(20) NOT NULL,
 *      `type` text NOT NULL,
 *      `data` text NOT NULL COMMENT 'serialized text data',
 *      `flags` set('p','d') NOT NULL DEFAULT 'p' COMMENT 'pending, deleted',
 *      PRIMARY KEY (`id`)
 *    ) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
 *
 */
class SampleEvent extends GregorianCalendarEvent {
 
    /**
     * GregorianCalendarEvent constructor
     * @param string $nameOrDBArray=''
     * @param mixed $start=null
     * @param mixed $end=null
     * @param array $data=array()
     * @param string $type=null
     * @param mixed $id=null
     */
    public function __construct($nameOrDBArray='', $start=null, $end=null, array $data=array(), $type=null, $id=null) {
        if(!is_array($nameOrDBArray)) {
            parent::__construct($nameOrDBArray, $start, $end, $data, $type, $id);
        } else {
            $this->unserialize($nameOrDBArray);
        }
    }
 
    /**
     * Returns an associatove array to save the event in a database. The
     * data are JSON encoded
     * @return string
     */
    public function serialize() {
        $r = array(
            'id' => $this->id,
            'name' => $this->name,
            'begin' => $this->start,
            'end' => $this->end,
            'type' => $this->type,
            'data' => $this->data
        );
        unset($r['data']['resource']);
        $r['data'] = json_encode($r['data'], JSON_FORCE_OBJECT);
        return $r;
    }
 
 
    /**
     * Sets all object variables corresponding to the assoc array returned by
     * serialize().
     * @param array $r
     */
    public function unserialize(array $r) {
        $this->id = $r['id'];
        $this->name = $r['name'];
        $this->start = $r['begin'];
        $this->end = $r['end'];
        $this->type = $r['type'];
        $this->data = empty($r['data']) ? array() : json_decode($r['data'], true);
    }
}
 
/**
 * Sample Gegorian Calendar event controller
 * @package de.atwillys.sw.php.cal.sample
 * @author Stefan Wilhelm, 2010
 * @license GPL
 *
 * This is the sample event controller, organizes the database and provides the
 * funtionality to obtain and save data.
 * As mentioned, it belongs to a table build with the SQL query:
 *
 *    SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
 *    CREATE TABLE IF NOT EXISTS `events` (
 *      `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
 *      `name` text NOT NULL,
 *      `begin` bigint(20) NOT NULL,
 *      `end` bigint(20) NOT NULL,
 *      `type` text NOT NULL,
 *      `data` text NOT NULL COMMENT 'serialized text data',
 *      `flags` set('p','d') NOT NULL DEFAULT 'p' COMMENT 'pending, deleted',
 *      PRIMARY KEY (`id`)
 *    ) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
 *
 */
class SampleEventController extends GregorianCalendarEventController {
 
    /**
     * The database settings, for the hackers: this sample is NOT
     * on the atwillys server ...
     * @var array
     */
    private $database = array(
        'database' => 'samplecalendar',
        'user' => 'cal',
        'password' => 'cal',
        'server' => 'localhost'
    );
 
    /**
     * Database
     * @return MySql
     */
    protected function db() {
        if(is_array($this->database)) {
            $this->database = new MySql($this->database['database'],
            $this->database['user'], $this->database['password'],
            $this->database['server']);
        }
        return $this->database;
    }
 
    /**
     * Returns all events that start and end between a given period.
     * @param mixed $from
     * @param mixed $to
     * @return SampleEvent[]
     */
    public function getEventsBetween($from, $to) {
        $from = new LocalDate($from);
        $to = new LocalDate($to);
        $r = $this->db()->query('SELECT * FROM events WHERE begin >= ' .
                Mysql::escape($from->getTimeStamp(), true)  . ' && end <' .
                MySql::escape($to->getTimeStamp(), true) . ' && NOT FIND_IN_SET(\'d\', flags)' );
        foreach($r as $k => $v) $r[$k] = new SampleEvent($v);
        return $r;
    }
 
    /**
     * Returns all events that do not end before and do not start after the given
     * time period.
     * @param mixed $from
     * @param mixed $to
     * @return SampleEvent[]
     */
    public function getEventsWhichMatch($from, $to) {
        $from = new LocalDate($from);
        $to = new LocalDate($to);
        $r = $this->db()->query('SELECT * FROM events WHERE NOT end < ' .
                Mysql::escape($from->getTimeStamp(), true)  . ' && NOT begin >' .
                MySql::escape($to->getTimeStamp(), true) . ' && NOT FIND_IN_SET(\'d\', flags)' );
        foreach($r as $k => $v) $r[$k] = new SampleEvent($v);
        return $r;
    }
 
 
    /**
     * Returns the event by id, null if not found
     * @param int $id
     */
    public function getEvent($id) {
        $r = $this->db()->query('SELECT * FROM events WHERE id= ' .
                Mysql::escape($id, true) . ' LIMIT 1');
        if(!empty($r)) {
            $event = new SampleEvent();
            $event->unserialize(reset($r));
            return $event;
        } else {
            return null;
        }
    }
 
    /**
     * Saves an event
     * @param SampleEvent $event
     */
    public function saveEvent(SampleEvent $event) {
        $r = $event->serialize();
        if(!empty($r['id']) && is_numeric($r['id'])) {
            $this->db()->queryUpdateTable('events', $r);
        } else if(empty($r['id'])) {
            $r['id'] = null;
            $this->db()->queryInsertInto('events', $r);
        } else {
            throw new Exception('Sample event id is invalid: ' . $id);
        }
    }
 
}

index.php

<?php
 
/**
 * Here the index.php, which uses the classes above ...
 * Note: GET is not sanatized
 * Note: There is a if(false) {} to add events in your sample MySql table
 */
require_once(__DIR__ . '/MyCalendar.class.php');
 
use sw\Tracer;
use sw\LocalDate;
 
// Let's set another time zone for local dates
date_default_timezone_set('Europe/London');
 
try {
 
    // Instantize our event controller
    $rec = new SampleEventController();
 
    // Instantize the main calendar object and make settings ...
    $cal = new GregorianCalendar();
    $cal->setEventController($rec);         // Set our own event controller
    $cal->setDayDisplayFrom("06:00:00");    // The day cal will ignore 05:59
    $cal->setDayStartsAt("08:00:00");       // From 06:00 to 07:59 is grayed
    $cal->setDayEndsAt("19:59:59");         // From 20:00 to 22:59 is grayed
    $cal->setDayDisplayTo("22:59:59");      // From 23:00 to 23:59 is ignored
    $cal->setHourDivision(900);             // One tick is 15 minutes (resolution)
 
    // These are our renderers:
    $monthCal = new SampleMonthCalendarRenderer();
    $dayCal = new SampleDayCalendarRenderer();
 
} catch(Exception $e) {
    print "exception:" . $e->getMessage();
    Tracer::traceException($e);
}
 
 
try {
    // The day to show in the month calendar, note that you should
    // SANATIZE this before ...
    if(isset($_GET['month-calendar-show'])) {
        $_SESSION['month-calendar-show'] = $_GET['month-calendar-show'];
    }
    if(isset($_SESSION['month-calendar-show'])) {
        $cal->setDateToShow($_SESSION['month-calendar-show']);
    }
} catch(Exception $e) {
    Tracer::traceException($e);
    $cal->setDateToShow( $cal->getToday() );
}
 
try {
    // The selected day in the month/day calendar, note that you should
    // SANATIZE this before ...
    if(isset($_GET['month-calendar-select'])) {
        $_SESSION['month-calendar-select'] = $_GET['month-calendar-select'];
    }
    if(isset($_SESSION['month-calendar-select'])) {
        $cal->setSelectedDate($_SESSION['month-calendar-select']);
    }
} catch(Exception $e) {
    Tracer::traceException($e);
    $cal->setSelectedDate( $cal->getDateToShow() );
}
 
 
 
if(false)
{
    // Here a piece of code that creates some events in your database
    MySql::traced(true);
 
    // We round the timestamp to half an our
    $from = new LocalDate(); // new local date without argument is now.
    $from->setTimeStamp(round($from->getTimeStamp()/1800)*1800);
    $to = $from->add(9 * 3600); // events takes place for 9 hurs
    $ev = new SampleEvent('Overlapping 0', $from, $to, array('who' => uniqid(),
        'test1' => rand(0, 10)), 'sample' );
    $rec->saveEvent($ev); // save it in the database
    Tracer::trace("Overlapping 0 = $ev"); // and trce it ...
 
    $from = $from->add(5*1800);
    $to = $from->add(5*1800);
    $ev = new SampleEvent('Overlapping 1', $from, $to, array('who' => uniqid(),
        'test1' => rand(0, 10)), 'sample' );
    $rec->saveEvent($ev);
    Tracer::trace("Overlapping 1 = $ev");
 
    $from = $from->add(2435);
    $to = $from->add(9873);
    $ev = new SampleEvent('Overlapping 2', $from, $to, array('who' => uniqid(),
        'test1' => rand(0, 10)), 'sample' );
    $rec->saveEvent($ev);
    Tracer::trace("Overlapping 2 = $ev");
 
    $from = $from->add(234);
    $to = $from->add(4827);
    $ev = new SampleEvent('Overlapping 3', $from, $to, array('who' => uniqid(),
        'test1' => rand(0, 10)), 'sample' );
    $rec->saveEvent($ev);
    Tracer::trace("Overlapping 3 = $ev");
 
    $from = $from->add(4321);
    $to = $from->add(12345);
    $ev = new SampleEvent('Overlapping 4', $from, $to, array('who' => uniqid(),
        'test1' => rand(0, 10)), 'sample' );
    $rec->saveEvent($ev);
    Tracer::trace("Overlapping 4 = $ev");
 
}
 
// The view starts here:
 
?><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
<title>Sample calendar</title>
<link rel="stylesheet" href="index.css" type="text/css" />
<link rel="stylesheet" href="include/gregorian-month-calendar.css" type="text/css" />
<link rel="stylesheet" href="include/gregorian-day-calendar.css" type="text/css" />
<link rel="stylesheet" href="include/sample-calendar.css" type="text/css" />
</head><body>
<?php
 
try {
    print '<table id="sample-calendar"><tr>';
    print '<td class="month-calendar-block">';
    print "\n" . $cal->renderMonthCalendar($monthCal) . "\n";
    print $testout . "\n";
    print '</td>';
    print '<td class="day-calendar-block">';
    print "\n" . $cal->renderDayCalendar($dayCal) . "\n";
    print '</td>';
    print '</tr></table>';
} catch(Exception $e) {
    print "exception:" . $e->getMessage();
    Tracer::traceException($e);
}
?>
</body></html>

Output

As the output is not that small, I made a html snapshot of one page, which you can find it here.

Ausgabe

Da die Ausgabe eine ganze Seite zur Darstellung benötigt habe ich einen HTML Schnappschuss von einer Ausgabe des Kalenders gemacht. Du kannst ihn here anschauen.

Class source code

Klassen-Quelltext

<?php
 
/**
 * Gegorian Calendar management with the ability to render day calendars and
 * month calendars using the corresponding rendering classes. Also manages
 * the events for selected time spans.
 * @gpackage de.atwillys.sw.php.swlib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2010
 * @license GPL
 * @version 1.0
 * @uses GregorianCalendarEvent
 * @uses GregorianCalendarEventController
 * @uses GregorianDayCalendarRenderer
 * @uses GregorianMonthCalendarRenderer
 */
 
namespace sw;
 
class GregorianCalendar {
 
  /**
   * The date (and time) to show.
   * @var IDate
   */
  private $dateToShow = null;
 
  /**
   * The (real) today, set this only in the constructor for history purposes
   * @var IDate
   */
  private $today = null;
 
  /**
   * The (real) date which is selected, e.g. to displad a day calendar
   * @var IDate
   */
  private $dateSelected = null;
 
  /**
   * The class to instantiate, must be an IDate implementation, normally
   * LocalDate or UtcDate
   * @var IDate
   */
  private $dateClass = 'LocalDate';
 
  /**
   * Defines if the week starts with Sunday or Monday
   * @var bool
   */
  private $weekStartsWithSunday = false;
 
  /**
   * A numeric array which contains the weekday indices for using with localized
   * day name PHP functions.
   * @var array
   */
  private $weekDayIndex = array(1, 2, 3, 4, 5, 6, 0);
 
  /**
   * Contains the weekday abbreviations
   * @var array
   */
  private $weekDayAbbr = array('sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat');
 
  /**
   * Defines the time period of division in a day calendar (in seconds).
   * @var int
   */
  private $hourDivision = 3600;
 
  /**
   * Defines when the day starts for the calendar (timestamp format in seconds)
   * @var int
   */
  private $dayStartsAt = '00:00:00';
 
  /**
   * Defines when the day ends for the calendar (timestamp format in seconds)
   * @var int
   */
  private $dayEndsAt = '23:59:59';
 
  /**
   * Every time between 00:00:00 and $dayDisplayedFrom (les than $dayDisplayedFrom)
   * will not be displayed
   * @var int
   */
  private $dayDisplayedFrom = '00:00:00';
 
  /**
   * Every time > $dayDisplayedTo to '23:59:59' will not be displayed
   * @var int
   */
  private $dayDisplayedTo = '23:59:59';
 
  /**
   * Reference to the event controller object
   * @var GregorianCalendarEventController
   */
  private $eventController = null;
 
  /**
   * Constructor
   * @param IDate $dateToShow
   * @param IDate $today
   * @param string $dateClass
   */
  public function __construct($dateToShow=null, $today=null, $dateClass='LocalDate') {
    $this->dateClass = $dateClass;
    $this->setToday($today);
    $this->setDateToShow($dateToShow);
    $this->setWeekStartsOnSunday(false);
    $this->setDayStartsAt($this->dayStartsAt);
    $this->setDayEndsAt($this->dayEndsAt);
    $this->setDayDisplayFrom($this->dayDisplayedFrom);
    $this->setDayDisplayTo($this->dayDisplayedTo);
    $this->setSelectedDate($this->getToday());
  }
 
  /**
   * Returns of a time $t is/takes place at the day $day. The day must be normed
   * to 00:00:00h.
   * @param IDate $day
   * @param IDate $t
   * @return bool
   */
  protected final function isSameDay(IDate $day, IDate $t) {
    return ($t->getTimeStamp() >= $day->getTimeStamp()) && ($t < $day->getTimeStamp() + 86400);
  }
 
  /**
   * Returns the date specified as today (which is normally today, but can
   * be specified for historic date referencing)
   * @return IDate
   */
  public final function getToday() {
    return $this->today;
  }
 
  /**
   * Returns the date specified as selected (e.g. for displaying a day calendar)
   * @return IDate
   */
  public final function getSelectedDate() {
    return $this->dateSelected;
  }
 
  /**
   * Returns the date which has to be displayed. This can be a time with hours
   * minues and seconds. The renderers have to adjust the displayed interval.
   * For a month calendar, the calenar will e.g. render the month July 2010 if
   * $dateToShow is 2010-07-15 03:05:30.
   */
  public final function getDateToShow() {
    return $this->dateToShow;
  }
 
  /**
   * Returns if the week shall start on Sunday (or Monday)
   * @return bool
   */
  public final function getWeekStartsOnSunday() {
    return $this->weekStartsWithSunday;
  }
 
  /**
   * Returns the week day index dependent on $weekStartsWithSunday, either
   * [0,1,2,3,4,5,6] (if Sun first day) or [1,2,3,4,5,6,0] (if Mon first day)
   * @return bool
   */
  public final function getWeekDayIndex() {
    return $this->weekDayIndex;
  }
 
  /**
   * Returns the week day abbreviation depending on $weekStartsWithSunday
   * @param int $index
   * @return string
   */
  public final function getWeekDayAbbreviation($index) {
    $index -= 1;
    if (!isset($this->weekDayIndex[$index])) {
      throw new LException("No such week day index: $index");
    } else {
      return $this->weekDayAbbr[$this->weekDayIndex[$index]];
    }
  }
 
  /**
   * Retuens the time period of on division in a day calendar (in seconds).
   * @return int
   */
  public function getHourDivision() {
    return $this->hourDivision;
  }
 
  /**
   * Returns when the day starts for the calendar
   * @return int
   */
  public final function getDayStartsAt() {
    return $this->dayStartsAt;
  }
 
  /**
   * Returns when the day ends for the calendar
   * @return int
   */
  public final function getDayEndsAt() {
    return $this->dayEndsAt;
  }
 
  /**
   * Every time between 00:00:00 and getDayDisplayedFrom()
   * will not be displayed
   * @return int
   */
  public final function getDayDisplayedFrom() {
    return $this->dayDisplayedFrom;
  }
 
  /**
   * Every time > getDayDisplayedTo() to '23:59:59' will not be displayed
   * @return int
   */
  public final function getDayDisplayedTo() {
    return $this->dayDisplayedTo;
  }
 
  /**
   * Returns a reference to the event controller, creates a standard controller
   * of no controller instantiated/set.
   * @return GregorianCalendarEventController
   */
  public final function getEventController() {
    if (!$this->eventController instanceof GregorianCalendarEventController) {
      $this->eventController = new GregorianCalendarEventController();
    }
    return $this->eventController;
  }
 
  /**
   * Returns the date specified as today (which is normally today, but can
   * be specified for historic date referencing)
   * @param mixed $today
   */
  public final function setToday($today) {
    $today = is_null($today) ? (new $this->dateClass()) : (new $this->dateClass(strval($today)));
    if (!($today instanceof IDate)) {
      throw new LException('Date classes used here must implement IDate');
    }
    $this->today = $today;
    $this->today->setSerial(null, null, null, 0, 0, 0);
  }
 
  /**
   * Sets the date specified as selected (e.g. for displaying a day calendar)
   * @return IDate
   */
  public final function setSelectedDate($selected) {
    $selected = is_null($selected) ? (new $this->dateClass()) : (new $this->dateClass(strval($selected)));
    if (!($selected instanceof IDate)) {
      throw new LException('Date classes used here must implement IDate');
    }
    $this->dateSelected = $selected;
  }
 
  /**
   * Stets the date which has to be displayed. This can be a time with hours
   * minues and seconds. The renderers have to adjust the displayed interval.
   * For a month calendar, the calenar will e.g. render the month July 2010 if
   * $dateToShow is 2010-07-15 03:05:30.
   * @param mixed $dateToShow
   */
  public final function setDateToShow($dateToShow) {
    $dateToShow = !is_null($dateToShow) ? (new $this->dateClass(strval($dateToShow))) : (($this->today instanceof IDate) ? clone $this->today : new $this->dateClass());
    if (!($dateToShow instanceof IDate)) {
      throw new LException('Date classes used here must implement IDate');
    }
    $this->dateToShow = $dateToShow;
  }
 
  /**
   * Sets if the week shall start on Sunday (or Monday)
   * @param bool $bool
   */
  public final function setWeekStartsOnSunday($bool) {
    $this->weekStartsWithSunday = $bool ? true : false;
    $this->weekDayIndex = $this->weekStartsWithSunday ? array(0, 1, 2, 3, 4, 5, 6) : array(1, 2, 3, 4, 5, 6, 0);
  }
 
  /**
   * Sets when the day starts for the calendar
   * @param mixed $time
   */
  public final function setDayStartsAt($time) {
    $time = new $this->dateClass($time);
    $this->dayStartsAt = 3600 * $time->getHour() + 60 * $time->getMinute() + $time->getSecond();
  }
 
  /**
   * Sets when the day ends for the calendar
   * @param mixed $time
   */
  public final function setDayEndsAt($time) {
    $time = new $this->dateClass($time);
    $this->dayEndsAt = 3600 * $time->getHour() + 60 * $time->getMinute() + $time->getSecond();
  }
 
  /**
   * Every time between 00:00:00 and $time (les than $time)
   * will not be displayed
   * @param mixed $time
   */
  public final function setDayDisplayFrom($time) {
    $time = new $this->dateClass($time);
    $this->dayDisplayedFrom = 3600 * $time->getHour() + 60 * $time->getMinute() + $time->getSecond();
  }
 
  /**
   * Every time > $time to '23:59:59' will not be displayed
   * @param mixed $time
   */
  public final function setDayDisplayTo($time) {
    $time = new $this->dateClass($time);
    $this->dayDisplayedTo = 3600 * $time->getHour() + 60 * $time->getMinute() + $time->getSecond();
  }
 
  /**
   * Sets a new event controller
   * @param GregorianCalendarEventController $controller
   */
  public final function setEventController($controller) {
    if (is_null($controller) || $controller instanceof GregorianCalendarEventController) {
      $this->eventController = $controller;
    } else {
      throw new LException('Calendar event controllers must be null (to unset) or GregorianCalendarEventController');
    }
  }
 
  /**
   * Sets the time period of on division in a day calendar (in seconds).
   * @param int $period
   */
  public function setHourDivision($period) {
    if (!is_numeric($period)) {
      throw new LException('Day division period must be numeric');
    } else {
      $period = intval($period);
      if ($period <= 0) {
        throw new LException('Day division period must be > 0');
      } else if (3600 % $period != 0) {
        throw new LException('An special amount of division periods must fit exactly 1 hour (3600 MOD period = 0)');
      } else {
        $this->hourDivision = $period;
      }
    }
  }
 
  /**
   * Returns a rendered representation of a month calendar
   * @param GregorianMonthCalendarRenderer $renderer
   * @return array
   */
  public final function renderMonthCalendar(GregorianMonthCalendarRenderer $renderer) {
    if (!$renderer instanceof GregorianMonthCalendarRenderer) {
      throw new LException('You must use a GregorianMonthCalendarRenderer to render this');
    }
    $renderer->setParent($this);
    $monthStart = new LocalDate($this->dateToShow->getYear() . "-" . $this->dateToShow->getMonth() . "-01");
    $monthEnd = $monthStart->getNext('month');
    $monthEnd->setTimeStamp($monthEnd->getTimeStamp() - 1);
    $calStart = $monthStart->getLast($this->weekStartsWithSunday ? 'sunday' : 'monday');
    $calEnd = $monthEnd->getNext($this->weekStartsWithSunday ? 'sunday' : 'monday');
 
    if ($calEnd->inWeeks() - $calStart->inWeeks() < 6) {
      $calEnd = $calEnd->getNext($this->weekStartsWithSunday ? 'sunday' : 'monday');
    }
    $calEnd->setTimeStamp($calEnd->getTimeStamp() - 1);
 
    $selected = clone $this->getSelectedDate();
    $selected->setSerial(null, null, null, 0, 0, 0);
    $selected = $selected->getTimeStamp();
 
    Tracer::trace('TZ           = ' . $monthEnd->getTimeZoneName());
    Tracer::trace('Date to show = ' . $this->dateToShow . '/' . $this->dateToShow->getWeekDay());
    Tracer::trace('Month start  = ' . $monthStart . '/' . $monthStart->getWeekDay());
    Tracer::trace('Month end    = ' . $monthEnd . '/' . $monthEnd->getWeekDay());
    Tracer::trace('Cal start    = ' . $calStart . '/' . $calStart->getWeekDay());
    Tracer::trace('Cal end      = ' . $calEnd . '/' . $calEnd->getWeekDay());
    Tracer::trace('Cal selected = ' . $this->getSelectedDate() . '/' . $selected);
    Tracer::trace("Num of weeks = " . ($calEnd->inWeeks() - $calStart->inWeeks()));
 
    $today = clone $this->today;
    $ts_s = $monthStart->getTimeStamp();
    $ts_e = $monthEnd->getTimeStamp();
    $wday = $week = 0;
 
    $events = array();
    $dayPeriod = 24 * 3600 - 1;
 
    $renderer->onStart();
    $renderer->onWeekStart(0);
    for ($day = new LocalDate($calStart); $day->getTimeStamp() <= $calEnd->getTimeStamp(); $day = $day->getNext('day')) {
      if ($wday > 6) {
        $wday = 0;
        $renderer->onWeekEnd($week++);
        $renderer->onWeekStart($week);
      }
      $events = $this->getEventController()->getEventsWhichMatch($day, $day->getTimeStamp() + $dayPeriod);
      $ts = $day->getTimeStamp();
      $renderer->onDay(clone $day, $ts < $ts_s || $ts > $ts_e, $ts == $selected, $events);
      $wday++;
    }
    $renderer->onEnd();
    return $renderer->getOutput();
  }
 
  /**
   * Returns a rendered representation of a day calendar
   * @param GregorianDayCalendarRenderer $renderer
   * @param $date=null
   */
  public final function renderDayCalendar(GregorianDayCalendarRenderer $renderer, $date=null) {
    if (!$renderer instanceof GregorianDayCalendarRenderer) {
      throw new LException('You must use a GregorianDayCalendarRenderer to render this');
    }
    if (!is_null($date)) {
      $date = new $this->dateClass($date);
    } else {
      $date = clone $this->getSelectedDate();
    }
 
    $renderer->setParent($this);
    $dayStart = new $this->dateClass($date);
    $dayEnd = new $this->dateClass($date);
    $dayStart->setSerial(null, null, null, 0, 0, 0);
    $dayEnd->setSerial(null, null, null, 23, 59, 59);
    $dayStartsAt = $dayStart->getTimeStamp() + $this->getDayStartsAt();
    $dayEndsAt = $dayStart->getTimeStamp() + $this->getDayEndsAt();
    $displayFrom = $dayStart->getTimeStamp() + $this->getDayDisplayedFrom();
    $displayTo = $dayStart->getTimeStamp() + $this->getDayDisplayedTo();
    $tick = $this->getHourDivision();
 
    Tracer::trace('Date to show = ' . $date);
    Tracer::trace('Day start    = ' . $dayStart . '/' . $dayStart->toTimeString());
    Tracer::trace('Day end      = ' . $dayEnd . '/' . $dayEnd->toTimeString());
    Tracer::trace('Cal start    = ' . $dayStartsAt . '/' . ($this->getDayStartsAt() / 3600));
    Tracer::trace('Cal end      = ' . $dayEndsAt . '/' . ($this->getDayEndsAt() / 3600));
 
    $events = $this->getEventController()->getEventsWhichMatch($displayFrom, $displayTo);
    $map = array();
 
    if (!empty($events)) {
 
      // Conditionize events: Sort the events by timestamp, assure that keys
      // are the the start timestamps.
      function lmb_sortCallback($a, $b) {
        return $a->getStart() > $b->getStart() ? 1 : -1;
      }
 
      usort($events, 'lmb_sortCallback');
      $ev = $events;
      $events = array();
      foreach ($ev as $event) {
        $events[$event->getId()] = $event;
      }
      unset($ev);
 
      foreach ($events as $event)
        Tracer::trace("EVENT: $event");
 
      // Generate a 2D map, where the row keys are the tick timestamps
      $map = array();
      for ($i = $displayFrom; $i <= $displayTo; $i+=$tick) {
        $map[$i] = array();
      }
 
      // Place the events in the map, beginning in column 0, first all events
      // that do not overlap (to shrink the loop after that)
      $ev = $events;
      while (!empty($ev)) {
        $event = array_shift($ev);
        $row = $tick * floor($event->getStart() / $tick);
 
        if ($row < $displayFrom) {
          $row = $displayFrom;
          $events[$row] = $event;
        }
 
        $end = $tick * ceil($event->getEnd() / $tick);
        $id = $event->getId();
        $col = 0;
        // Loop over all known columns to check if there is space ...
        while (isset($map[$row][$col])) {
          // Check if ther is space ...
          $fits = true;
          for ($i = $row; $i <= $end; $i+=$tick) {
            if ($map[$i][$col]) {
              // Not here, try next column
              $fits = false;
              break;
            }
          }
          if ($fits) {
            break;
          } else {
            $col++;
          }
        }
        // New column required, create it for the whole day
        if (!isset($fits) || !$fits) {
          for ($i = $displayFrom; $i <= $displayTo; $i+=$tick) {
            $map[$i][$col] = false;
          }
        }
        // Place the event in the row and column (and the rows that the
        // event takes place, mark the start row as ID, all other as
        // true (means "busy")
        $map[$row][$col] = $id;
        for ($i = $row + $tick; $i < $end; $i+=$tick) {
          $map[$i][$col] = true;
        }
      }
 
      // Cleanup
      unset($ev, $fits, $col, $row, $id, $end, $event, $i, $key, $nCols, $nEvents);
 
//            // Trace the map
//            foreach($map as $key => $row) {
//                $o = strftime("%H:%M:%S", $key) . " = ";
//                foreach($row as $id) {
//                    if($id === true) {
//                        $o .= ' busy';
//                    } else if($id === false) {
//                        $o .= ' free';
//                    } else {
//                        $o .= " " . sprintf("%04d", $id);
//                    }
//                }
//                Tracer::trace_r($o);
//            }
//            unset($o, $key, $row, $id);
    }
 
    $renderer->onStart($date, $tick, $displayFrom, $dayStartsAt, $dayEndsAt, $displayTo, $events);
    for ($time = $dayStart->getTimeStamp(); $time <= $dayEnd->getTimeStamp(); $time += $tick) {
      $renderer->onTick(new $this->dateClass($time), $tick, $time < $dayStartsAt || $time > $dayEndsAt, $time < $displayFrom || $time > $displayTo, $events, $map);
    }
    $renderer->onEnd();
    return $renderer->getOutput();
  }
 
}
 
?>
<?php
 
/**
 * Gegorian Month Calendar renderer. Overwrite this class if you want to have an
 * alterantive rendered representation of the month calendar.
 * @gpackage de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2010
 * @license GPL
 * @version 1.0
 * @uses GregorianCalendar
 * @uses GregorianCalendarEvent
 */
 
namespace sw;
 
class GregorianMonthCalendarRenderer {
 
  /**
   * Defines the parent calendar
   * @var GregorianCalendar
   */
  private $parent = null;
 
  /**
   * The output of the rendering process
   * @var string
   */
  protected $output = '';
 
  /**
   * Constructor
   * @param GregorianCalendar $parentGregorianCalendar
   */
  public function __construct($parentGregorianCalendar = null) {
    $this->parent = $parentGregorianCalendar;
  }
 
  /**
   * Sets the parent of the calendar
   * @param GregorianCalendar $calendar
   */
  public final function setParent(GregorianCalendar $calendar) {
    if (!is_null($this->parent) && $this->parent !== $calendar) {
      throw new LException('You cannot re-assign the parent calendar instance');
    } else {
      $this->parent = $calendar;
    }
  }
 
  public final function getParent() {
    return $this->parent;
  }
 
  /**
   * Returns the output of the rendering process
   * @return string
   */
  public final function getOutput() {
    return $this->output;
  }
 
  /**
   * Callback to start the calendar - every HTML content before the weeks
   * (and days in the weeks). Normally a table, thead, th with weekday names.
   * @return void
   */
  public function onStart() {
    $date = $this->getParent()->getDateToShow();
    $o = '';
    $o .= '<table class="gregorian-month-calendar">';
    $o .= '<caption>';
    $o .= '<a class="prev" href="' . $_SERVER['PHP_SELF'] . '?month-calendar-show=' . $date->getLast('month')->toDateString() . '">' . '&nbsp;' . '</a>';
    $o .= '<a class="next" href="' . $_SERVER['PHP_SELF'] . '?month-calendar-show=' . $date->getNext('month')->toDateString() . '">' . '&nbsp;' . '</a>';
    $o .= '<a class="title">' . ucfirst(strftime("%B %Y", $date->getTimeStamp())) . '</a>';
    $o .= '</caption>';
    for ($i = 1; $i <= 7; $i++) {
      $o .= '<col class="' . $this->getParent()->getWeekDayAbbreviation($i) . '" />';
    }
    $o .= '<tr>';
    foreach ($this->getParent()->getWeekDayIndex() as $i) {
      $o .= '<th><a>' . strtoupper(substr(gmstrftime('%A', ($i - 4) * 24 * 3600), 0, 1)) . '</a></th>';
    }
    $o .= '</tr>';
    $this->output .= $o;
  }
 
  /**
   * All HTML to close the table, table footer etc.
   * @return void
   */
  public function onEnd() {
    $this->output .= '</table>';
  }
 
  /**
   * Start a new week, normally a <tr> or an additional <td></td> with the
   * calendar week (e.g. CW45) etc.
   * @return void
   */
  public function onWeekStart($weekId) {
    $this->output .= '<tr>';
  }
 
  /**
   * Finish a new week, normally a </tr>
   * @return void
   */
  public function onWeekEnd($weekId) {
    $this->output .= "</tr>\n";
  }
 
  /**
   * Renders a day in the week (normally a <tr>$dateDayOfMonthTwoDigits</tr>
   * @param IDate $day
   * @param bool isPadding
   * @param bool isSelected
   * @param GregorianMonthCalendarRenderer[] $events
   * @return mixed
   */
  public function onDay(IDate $day, $isPadding, $isSelected, $events) {
    $class = array();
    $class[] = $this->getParent()->getWeekDayAbbreviation($day->getWeekDay());
    switch (Math::signz($day->getTimeStamp() - $this->getParent()->getToday()->getTimeStamp())) {
      case -1: $class[] = 'passed';
        break;
      case 0 : $class[] = 'today';
        break;
    }
    if (!empty($events))
      $class[] = 'has-events';
    if ($isPadding)
      $class[] = 'padding';
    if ($isSelected)
      $class[] = 'selected';
    $class = 'class="' . implode(' ', $class) . '"';
    $title = 'title="' . ucwords(strftime("%A, %B %e, %Y", $day->getTimeStamp())) . (count($events) == 0 ? '' : ' (' . count($events) . ' events)') . '"';
    $date = '<a href="' . $_SERVER['PHP_SELF'] . '?month-calendar-select=' . $day->toDateString() . '">' . sprintf("%02d", $day->getDay()) . '</a>';
    $day->isPadding = $isPadding;
    $this->output .= "<td $class $title>$date</td>";
  }
 
}
<?php
 
/**
 * Gegorian Day Calendar renderer class. Overwrite this class if you want a
 * different rendered representation.
 * @gpackage de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2010
 * @license GPL
 * @version 1.0
 * @uses GregorianCalendar
 * @uses GregorianCalendarEvent
 */
 
namespace sw;
 
class GregorianDayCalendarRenderer {
 
  /**
   * Defines the parent calendar
   * @var GregorianCalendar
   */
  private $parent = null;
 
  /**
   * The output of the rendering process
   * @var string
   */
  protected $output = '';
 
  /**
   * The HTML element id of the table
   * @var string
   */
  protected $tableId = '';
 
  /**
   * Constructor
   * @param GregorianCalendar $parentGregorianCalendar
   */
  public function __construct($parentGregorianCalendar = null) {
    $this->parent = $parentGregorianCalendar;
  }
 
  /**
   * Sets the parent of the calendar
   * @param GregorianCalendar $calendar
   */
  public final function setParent(GregorianCalendar $calendar) {
    if (!is_null($this->parent) && $this->parent !== $calendar) {
      throw new LException('You cannot re-assign the parent calendar instance');
    } else {
      $this->parent = $calendar;
    }
  }
 
  /**
   * Returns the parent Calendar object (which the renderer is for)
   * @return GregorianCalendar
   */
  public final function getParent() {
    return $this->parent;
  }
 
  /**
   * Sets the id of the table that contains the day calendar
   * @param string $id
   */
  public final function setTableId($id) {
    if (empty($id)) {
      $this->tableId = '';
    } else {
      $this->tableId = trim($id);
    }
  }
 
  /**
   * Returns the id of the table that contains the day calendar
   * @return string
   */
  public final function getTableId() {
    return $this->tableId;
  }
 
  /**
   * Returns the output of the rendering process
   * @return string
   */
  public final function getOutput() {
    return $this->output;
  }
 
  /**
   * Callback to start the calendar - every HTML content before the weeks
   * (and days in the weeks). Normally a table, thead, th with weekday names.
   * @param IDate $date
   * @param int $tick
   * @param int $displayFrom
   * @param int $dayStartsAt
   * @param int $dayEndsAt
   * @param int $displayTo
   * @param GregorianCalendarEvent[] &$events
   * @return void
   */
  public function onStart($date, $tick, $displayFrom, $dayStartsAt, $dayEndsAt, $displayTo, &$events) {
    $id = empty($this->tableId) ? '' : ('id="' . str_replace('#', '', $this->tableId) . '" ');
    $o = '';
    $o .= '<table ' . $id . 'class="gregorian-day-calendar">';
    $o .= '<caption>' . ucwords(strftime("%A, %B %e, %Y", $date->getTimeStamp())) . '</caption>';
    $this->output .= "$o\n";
  }
 
  /**
   * All HTML to close the table, table footer etc.
   * @return void
   */
  public function onEnd() {
    $this->output .= "</table>\n";
    $this->events = array();
  }
 
  /**
   * Renders one tick with the division period, padding means that the day
   * of the calendar has either not started yet or already ended (according
   * to $dayStartsAt and $dayEndsAt)
   * @param IDate $date
   * @param int $tick
   * @param bool $isPadding
   * @param bool $hidden
   * @param GregorianCalendarEvent[] &$events
   * @param array() &$eventMap
   * @return void
   */
  public function onTick(IDate $date, $tick, $isPadding, $hidden, &$events, &$eventMap) {
    if ($hidden)
      return;
    $class = array();
    if ($isPadding)
      $class[] = 'padding';
    if ($date->getMinute() == 0) {
      $class[] = 'full-hour';
      $fullHourTime = sprintf("%02d:%02d", $date->getHour(), $date->getMinute());
    } else {
      $fullHourTime = '&nbsp;';
    }
 
    $time = sprintf("%04d-%02d-%02d %02d:%02d", $date->getYear(), $date->getMonth(), $date->getDay(), $date->getHour(), $date->getMinute());
 
    if (!empty($events)) {
      $ts = $date->getTimeStamp();
      $row = &$eventMap[$ts];
      $td = '';
      $td .= '<td class="space"></td>' . "\t";
      foreach ($row as $index => $col) {
        if ($col === true) {
          $td .= '<td class="space"></td>' . "\t";
          $td .= '<!-- rowspan -->'; // event running, managed by rowspan
        } else if (is_null($col)) {
          $td .= '<!-- colspan -->'; // event running, managed by colspan
        } else if ($col === false) {
          $td .= '<td class="space"></td>' . "\t";
          $td .= '<td class="data">&nbsp;</td>'; // an empty cell
        } else if (is_numeric($col)) {
          $td .= '<td class="space"></td>' . "\t";
          $event = $events[$col]; // by ID
          // $rows = (ceil($event->getEnd() / $tick) - ($ts / $tick));
          $cols = count($row) - $index; // index starts at 0 --> min cols=1, max=count(...)
          $rows = 1;
          for ($i = $ts; $i < $ts + ($tick * count($eventMap)); $i+=$tick) {
            if (isset($eventMap[$i + $tick][$index]) && $eventMap[$i + $tick][$index] === true) {
              $rows++;
            } else {
              break;
            }
          }
 
          for ($i = $ts; $i < $ts + ($tick * $rows); $i+=$tick) {
            if (!isset($eventMap[$i][$index + 1]) || $eventMap[$i][$index + 1] !== false) {
              $cols = 1;
              break;
            }
          }
 
          if ($cols > 1) {
            for ($i = $ts; $i < $ts + ($tick * $rows); $i+=$tick) {
              for ($j = $index; $j < count($row); $j++) {
                $eventMap[$i][$j] = null;
              }
            }
            $row[$index] = $event->getId();
            // Take the spacers into account
            $cols = 2 * $cols - 1;
          }
          $td .= $this->renderEvent($event, $rows, $cols);
        }
        $td .= "\n";
      }
    } else {
      $td = '<td class="space"></td><td></td>';
    }
 
    $o = '';
    $o .= '<tr value="' . $time . '" ' . (empty($class) ? '' : (' class="' . implode(' ', $class)) . '"') . '>' . "\n";
    $o .= "\t" . '<td class="time" title="' . $time . '" >' . $fullHourTime . '</td>' . "\n";
    $o .= $td;
    $o .= '</tr>' . "\n";
    $this->output .= $o;
  }
 
  /**
   * Returns a day calendar string representation of an event
   * @param GregorianCalendarEvent $event
   * @param int $rows
   * @params int $cols
   * @return string
   */
  protected function renderEvent(GregorianCalendarEvent $event, $rows, $cols) {
    $td = '<td class="data event" rowspan="' . $rows . '" colspan="' . $cols . '">';
    $td .= (method_exists($event, 'render')) ? $event->render($this) : '';
    $td .= "</td>";
    return $td;
  }
 
}
<?php
 
/**
 * Base class for Gegorian Calendar Events. Events are defined using a unique
 * identifier (e.g. the primary key of a database or file), a start timestamp
 * and an end timestamp. The configuration of the GregorianCalendar class
 * decides if these timestamps are interpreted as local or UTC. Further "fixed"
 * properties are the type of event (e.g. "meeting", "festival" ...) and an
 * associative data array that contains variable information about the particular
 * event. All keys in the array can be accessed like properties (if e.g.
 *  $data = array(
 *      'where' => 'there',
 *      'who' => 'me',
 *      'why' => 'because'
 *  );
 *
 * Then $event->where === 'there'.
 * Note that you should choose "PHP-variable-name-conform" array keys for this,
 * alternatively you can use the data getter: $event->getData("who") === "me".
 *
 * @gpackage de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2010
 * @license GPL
 * @version 1.0
 */
 
namespace sw;
 
class GregorianCalendarEvent {
 
  /**
   * A unique identifier of the event
   * @var string
   */
  protected $id = '';
 
  /**
   * The name of the event
   * @var string
   */
  protected $name = 'New Event';
 
  /**
   * The timestamp when the event starts
   * @var IDate
   */
  protected $start = 0;
 
  /**
   * The timestamp when the event ends
   * @var IDate
   */
  protected $end = 0;
 
  /**
   * The type of event
   * @var string
   */
  protected $type = '';
 
  /**
   *
   * @var array
   */
  protected $data = array();
 
  /**
   * GregorianCalendarEvent constructor
   * @param string $name=''
   * @param mixed $start=null
   * @param mixed $end=null
   * @param array $data=array()
   * @param string $type=null
   * @param mixed $id=null
   */
  public function __construct($name='', $start=null, $end=null, array $data=array(), $type=null, $id=null) {
    if ($start instanceof IDate) {
      $start = $start->getTimeStamp();
    } else if (empty($start)) {
      $start = time();
    } else if (!is_numeric($start)) {
      throw new LException('GregorianCalendarEvent start time must be an integer timestamp');
    }
 
    if ($end instanceof IDate) {
      $end = $end->getTimeStamp();
    } else if (empty($end)) {
      $end = $start + 3600;
    } else if (!is_numeric($end)) {
      throw new LException('GregorianCalendarEvent end time must be an integer timestamp');
    }
 
    if ($end <= $start) {
      throw new LException('GregorianCalendarEvent end time must be later than the start time');
    }
 
    $this->start = $start;
    $this->end = $end;
 
    if (!empty($name))
      $this->name = trim($name);
    if (!empty($id))
      $this->id = $id;
    if (!empty($data))
      $this->data = $data;
    if (!empty($type))
      $this->type = trim($type); // implicit toString
  }
 
  /**
   * Returns a property if the correcponding getter (get<$ame>)exists OR the
   * key exsts in the data array. Returns null if none of both exists. Note
   * that values stored in the data array are inaccessible if a the key is
   * e.g. "from", "to", "name" etc.
   * @param string $name
   */
  public function __get($name) {
    if (method_exists($this, 'get' . $name)) {
      $name = 'get' . $name;
      return $this->$name();
    } else if (isset($this->data[$name])) {
      return $this->data[$name];
    } else {
      return null;
    }
  }
 
  /**
   * Sets a property if the correcponding setter (set<$ame>) exists. If no
   * setter is found, then the value will be stored in the data array. Note
   * that values stored in the data array are inaccessible if a the key is
   * e.g. "from", "to", "name" etc. The setter will be called instead.
   * @param string $name
   * @param mixed $value
   * @return void
   */
  public function __set($name, $value) {
    if (method_exists($this, 'set' . $name)) {
      $name = 'set' . $name;
      $this->$name($value);
    } else {
      $this->data[$name] = $value;
    }
  }
 
  /**
   * String representation
   * @return string
   */
  public function __toString() {
    return '['
            . strftime('%Y-%m-%d %H:%M:%S %Z', $this->start)
            . ' - '
            . strftime('%Y-%m-%d %H:%M:%S %Z', $this->end)
            . '] '
            . $this->name
            . ' {id="' . $this->id . '", type="' . $this->type
            . '", data=["' . implode('", "', $this->data) . '"]'
            . '}';
  }
 
  /**
   * Returns the id of the event
   * @return mixed
   */
  public function getId() {
    return $this->id;
  }
 
  /**
   * Returns the name of the event
   * @return string
   */
  public function getName() {
    return $this->name;
  }
 
  /**
   * Returns when the event starts
   * @return IDate
   */
  public function getStart() {
    return $this->start;
  }
 
  /**
   * Returns when the event ends
   * @return IDate
   */
  public function getEnd() {
    return $this->end;
  }
 
  /**
   * Returns the type of event
   * @return string
   */
  public function getType() {
    return $this->type;
  }
 
  /**
   * Returns the data array or a valued in the data array specified using
   * $key, null if not found
   * @return mixed
   */
  public function getData($key=null) {
    if (!empty($key)) {
      return isset($this->data[$key]) ? $this->data[$key] : null;
    } else {
      return $this->data;
    }
  }
 
  /**
   * Sets the id of the event
   * @param mixed $id
   */
  public function setId($id) {
    if (!is_scalar($id)) {
      throw new LException('An calendar event ID cannot be an object or array');
    } else {
      $this->id = $id;
    }
  }
 
  /**
   * Sets the name of the event
   * @param string $name
   */
  public function setName($name) {
    $this->name = trim($name);
  }
 
  /**
   * Sets the timestamp when the event starts
   * @param int $from
   */
  public function setFrom($from) {
    if (is_numeric($from)) {
      $this->start = intval($from);
    } else if ($from instanceof IDate) {
      $this->start = $from->getTimeStamp();
    } else {
      throw new LException('"from"-data is no timestamp');
    }
  }
 
  /**
   * Sets when the event ends
   * @param int $to
   */
  public function setTo($to) {
    if (is_numeric($to)) {
      $this->end = intval($to);
    } else if ($to instanceof IDate) {
      $this->end = $to->getTimeStamp();
    } else {
      throw new LException('"to"-data is no timestamp');
    }
  }
 
  /**
   * Sets the event type
   * @param string $type
   */
  public function setType($type) {
    $this->type = trim(strtolower($type));
  }
 
  /**
   * Sets the custom/user data array
   * @param array $data
   */
  public function setData(array $data) {
    $this->data = $data;
  }
 
}
<?php
 
/**
 * Gegorian Calendar event controller. Overwrite the methods to customize
 * the controller (load from file, from database etc. The class ist not
 * abstract because it will be instantiated by the calendar if no derived
 * controller is specified. This class methods returns empty date, which have
 * no effect.)
 * @gpackage de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2010
 * @license GPL
 * @version 1.0
 * @uses GregorianCalendarEvent
 */
 
namespace sw;
 
class GregorianCalendarEventController {
 
  /**
   * Returns all events that start and end between a given period. E.g. if
   * $from and $to are the timestamps for 00:00 to 23:59:59 on the same day,
   * then the method would return all events that take place exactly on this
   * day (start and end on this day).
   * @param mixed $from
   * @param mixed $to
   * @return GregorianCalendarEvent[]
   */
  public function getEventsBetween($from, $to) {
    return array();
  }
 
  /**
   * Returns all events that do not end before and do not start after the given
   * time period. If e.g. $from and $to are timespamps corresponding to 00:00
   * and 23::59:59 on the same day, then this method would return all events
   * that partially or completely take place on this day.
   * @param mixed $from
   * @param mixed $to
   * @return GregorianCalendarEvent[]
   */
  public function getEventsWhichMatch($from, $to) {
    return array();
  }
 
  /**
   * Returns an event given by its identifier
   * @return GregorianCalendarEvent
   */
  public function getEvent($id) {
    return null;
  }
 
}

Sample CSS

Beispiel CSS

/**
 * Common CSS
 * @author Stefan Wilhelm
 * @package de.atwillys.php.swlib.css.std
 */
 
body, abbr, acronym, address, area, b, base, basefont, big, blockquote, br, button,
caption, center, cite, code, col, colgroup, dd, del, del, dfn, dir, div, dl, dt, em,
fieldset, font, form, h1, h2, h3, h4, h5, hr, i, iframe, img, input, ins, isindex,
kbd, label, li, map, menu, object, ol, option, p, pre, s, samp, select, small, span,
strike, strong, sub, sup, table, tbody, td, textarea, tfoot, th, thead, title, tr,
tt, u, ul, var, a, a:link, a:hover, a:visited, hr, img, code, pre, tt, samp, input,
textarea, select, option {
    font-family: "Helvetica", "Verdana", monospace;
    background: transparent;
    margin: 0px 0px 0px 0px;
    padding: 0px 0px 0px 0px;
    text-decoration: none;
    color: #000000;
    font-size: 11pt;
    line-height: 13pt;
    border: none 0px;
}
 
body {
    padding:10px 10px 10px 10px;
}
 
h1 {
    font-size: 17pt;
}
 
h2 {
    font-size: 16pt;
}
 
h3 {
    font-size: 14pt;
}
 
h4, h5 {
    font-size: 11pt;
}
 
a:link, a:visited {
    font-weight: bold;
    color: #000099;
}
 
a:hover {
    color: #990000;
}
 
img {
    margin: 5px 5px 5px 5px;
}
 
hr {
    color:black;
    background-color:black;
    border:0px none;
    border-bottom:1px solid black;
    width:100%
}
 
code, pre, tt, samp {
    font-family: monospace;
    color:#000000;
}
 
form {
    margin:0px;
    padding:2px;
    border:0px none;
}
 
/**
 * CSS for Gregorian month calendars
 * @author Stefan Wilhelm
 * @package de.atwillys.php.swlib.cal
*/
 
table.gregorian-month-calendar {
    width: 245px;
    border-collapse: collapse;
    border-spacing: 0px;
}
 
table.gregorian-month-calendar caption {
    margin: 0px 0px 0px 0px;
    padding: 0px 0px 0px 0px;
    background: #bbbbbb;
    color: #111111;
    vertical-align: middle;
    text-align: center;
    font-size: 14pt;
    line-height: 18pt;
    margin-bottom: 2px;
    background-color: #eeeeee;
    border: ridge 1px #666666;
    border-radius: 3px;
    -webkit-border-radius: 3px;
    -moz-border-radius: 3px;
}
 
table.gregorian-month-calendar caption a,
table.gregorian-month-calendar caption a:link,
table.gregorian-month-calendar caption a:visited {
    display:block;
    margin: 0px 0px 0px 0px;
    padding: 0px 0px 0px 0px;
    background: #bbbbbb;
    color: #111111;
    vertical-align: middle;
    text-align: center;
    font-size: 14pt;
    line-height: 18pt;
    background-color: #eeeeee;
    border: ridge 1px #666666;
    border-radius: 3px;
}
 
table.gregorian-month-calendar caption a.prev,
table.gregorian-month-calendar caption a.prev:link,
table.gregorian-month-calendar caption a.prev:visited {
    margin: 0px 10px 0px 10px;
    border: none;
    width: 10px;
    float: left;
    background: transparent;
    background-image: url("prev.png");
    background-repeat: no-repeat;
    background-position: center center;
}
 
table.gregorian-month-calendar caption a.next,
table.gregorian-month-calendar caption a.next:link,
table.gregorian-month-calendar caption a.next:visited {
    margin: 0px 10px 0px 10px;
    border: none;
    width: 10px;
    float: right;
    background: transparent;
    background-image: url("next.png");
    background-repeat: no-repeat;
    background-position: center center;
}
 
table.gregorian-month-calendar caption a.title,
table.gregorian-month-calendar caption a.title:link,
table.gregorian-month-calendar caption a.title:visited {
    width:auto;
}
 
 
table.gregorian-month-calendar th,
table.gregorian-month-calendar td {
    vertical-align: middle;
    text-align: center;
}
 
 
table.gregorian-month-calendar th a,
table.gregorian-month-calendar th a:link,
table.gregorian-month-calendar th a:visited,
table.gregorian-month-calendar th a:hover,
table.gregorian-month-calendar td a,
table.gregorian-month-calendar td a:link,
table.gregorian-month-calendar td a:visited,
table.gregorian-month-calendar td a:hover {
    display: block;
    width: 35px;
    height: 20px;
    vertical-align: middle;
    text-align: center;
    border: ridge 1px #666666;
    border-radius: 3px;
    -webkit-border-radius: 3px;
    -moz-border-radius: 3px;
}
 
 
table.gregorian-month-calendar th a,
table.gregorian-month-calendar th a:link,
table.gregorian-month-calendar th a:visited {
    padding: 2px 2px 2px 2px;
    background-color: #979887;
    margin-bottom: 2px;
}
 
table.gregorian-month-calendar td a,
table.gregorian-month-calendar td a:link,
table.gregorian-month-calendar td a:visited,
table.gregorian-month-calendar td a:hover {
    color: #111111;
    padding: 7px 2px 7px 2px;
    background-color: #bbbbbb;
}
 
 
table.gregorian-month-calendar td.passed a,
table.gregorian-month-calendar td.passed a:link,
table.gregorian-month-calendar td.passed a:visited {
    color: #555555;
}
 
table.gregorian-month-calendar td.padding a,
table.gregorian-month-calendar td.padding a:link,
table.gregorian-month-calendar td.padding a:visited {
    color: #bbbbbb;
    background-color: #eeeeee;
    border: ridge 1px #bbbbbb;
}
 
table.gregorian-month-calendar td.sat a,
table.gregorian-month-calendar td.sat a:link,
table.gregorian-month-calendar td.sat a:visited,
table.gregorian-month-calendar td.sun a,
table.gregorian-month-calendar td.sun a:link,
table.gregorian-month-calendar td.sun a:visited {
    background-color: #eeee66;
}
 
table.gregorian-month-calendar td.today a,
table.gregorian-month-calendar td.today a:link,
table.gregorian-month-calendar td.today a:visited {
    color: #ff0077;
}
 
table.gregorian-month-calendar td.selected a,
table.gregorian-month-calendar td.selected a:link,
table.gregorian-month-calendar td.selected a:visited {
    color: #222222;
    background-color: #66ee66;
    border: ridge 1px #666666;
}
 
table.gregorian-month-calendar td.has-events a,
table.gregorian-month-calendar td.has-events a:link,
table.gregorian-month-calendar td.has-events a:visited {
    background-image: url('has-events.png');
    background-repeat: no-repeat;
}
 
table.gregorian-month-calendar td a:hover,
table.gregorian-month-calendar td.selected a:hover,
table.gregorian-month-calendar td.has-events a:hover,
table.gregorian-month-calendar td.sat a:hover,
table.gregorian-month-calendar td.sun a:hover,
table.gregorian-month-calendar td.padding a:hover {
    color: #222222;
    background-color: #44cc44;
    border: ridge 1px #666666;
}
 
/**
 * CSS for Gregorian day calendars
 * @author Stefan Wilhelm
 * @package de.atwillys.php.swlib.cal
*/
 
table.gregorian-day-calendar {
    width: 100%;
    border-collapse: collapse;
    border-spacing: 0px;
}
 
table.gregorian-day-calendar caption {
    margin: 0px 0px 0px 0px;
    padding: 0px 0px 0px 0px;
    width: 99.9%;
    background: #eeeeee;
    color: #111111;
    vertical-align: middle;
    text-align: center;
    font-size: 14pt;
    line-height: 18pt;
    margin-bottom: 2px;
    border: ridge 1px #666666;
    border-radius: 3px;
    -webkit-border-radius: 3px;
    -moz-border-radius: 3px;
}
 
table.gregorian-day-calendar td {
    vertical-align: middle;
    padding: 0px 0px 0px 0px;
    text-align: left;
    border-top: dotted 1px #aaaaaa;
    border-bottom: dotted 1px #aaaaaa;
}
 
table.gregorian-day-calendar td.time {
    text-align: right;
    width: 80px;
    height: 20px;
}
 
table.gregorian-day-calendar tr.full-hour td.time {
    border-top: solid 1px #000000;
}
 
table.gregorian-day-calendar tr.padding {
    background-color: #eeeeee;
}
 
table.gregorian-day-calendar tr td.space {
    width: 4px;
}
 
 
table.gregorian-day-calendar tr td.data.event {
    padding: 4px 4px 4px 4px;
    vertical-align: top;
    border: 0px;
    border-collapse: collapse;
    background: #aaaaff;
    box-shadow:0 0 1px #000000;
    -webkit-box-shadow:0 0 1px #000000;
    -moz-box-shadow:0 0 1px #000000;
    border-radius: 10px;
    -webkit-border-radius: 10px;
    -moz-border-radius: 10px;
}
 
table.gregorian-day-calendar tr td.event div {
}