Skip to content
Snippets Groups Projects
InstituteCalendarHelper.class.php 30.2 KiB
Newer Older
<?php

/**
 * InstituteCalendarHelper.class.php - class for institute calendar convenience functions
 *
 * @author      Timo Hartge <hartge@data-quest>
 * @license     http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
 * @category    Stud.IP
 *
 */

class InstituteCalendarHelper
{
    const COLUMN_DATAFIELD_ID = '69f6485f3c937766866a03d9d642ecbb';
    const COLOR_DATAFIELD_ID = '41cda2be71fe9efd6e28b853fc0681f3';
    const INST_DEFAULT_COLOR_DATAFIELD_ID = '0c63321a8e93b3ccc927611709248e07';
    const DEFAULT_EVENT_COLOR = '#899ab9';

    /**
     * Returns the default calendar columns.
     *
     * @return array   default column names
     */
    private static function getDefaultColumns()
    {
        return [
            1 => ['Spalte 1', 1],
            2 => ['Spalte 2', 1],
            3 => ['Spalte 3', 1],
            4 => ['Spalte 4', 1],
            5 => ['Spalte 5', 1],
            6 => ['Spalte 6', 1]
        ];
    }

    /**
     * Fetches the stores columns merged with defaults.
     *
     * @param string $institut_id
     * @param boolean $only_visible only return visible columns
     *
     * @return array column array in a fullcalender expected format
     */
    public static function getResourceColumns($institut_id, $only_visible = false)
    {
        $columns = [];
        $inst_columns = [
            0 => ['Sammelspalte', 1]
        ];

        $db_inst_columns = InstitutePlanColumn::findByInstitute($institut_id);

        if ($db_inst_columns) {
            foreach ($db_inst_columns as $col_info) {
                $inst_columns[$col_info['column']] = [$col_info['name'], $col_info['visible']];
            }
        } else {
            $inst_columns = array_merge($inst_columns, self::getDefaultColumns());
        }
        foreach ($inst_columns as $id => $info) {
            if ($only_visible && !$info[1]) continue;
            $columns[] = ['id' => $id, 'title' => $info[0], 'visible' => $info[1]];
        }
        return $columns;
    }

    /**
     * Adds the default resource columns
     *
     * @param string $institut_id
     *
     * @return int last resource column number
     */
    public static function addDefaultResourceColumns($institut_id)
    {
        $max_col = 0;
        foreach (self::getDefaultColumns() as $col_id => $col_info) {
            $new_col = new InstitutePlanColumn([$institut_id, $col_id]);
            $new_col->name = $col_info[0];
            $new_col->visible = $col_info[1];
            if ($new_col->store()) {
                $max_col = $col_id;
            }
        }
        return $max_col;
    }

    /**
     * Adds a resource column
     *
     * @param string $institut_id
     * @param string $name
     * @param int $specific_column_number
     *
     * @return int number of affected rows
     */
    public static function addResourceColumn($institut_id, $name, $specific_column_number = 0)
    {
        $last_col = InstitutePlanColumn::getLastColumnOfInstitute($institut_id);
        if ($last_col !== null) {
            $max_col = $last_col->column;
        } else {
            $max_col = self::addDefaultResourceColumns($institut_id);
        }
        $column_number = $specific_column_number>0?$specific_column_number:intval($max_col)+1;
        $new_col = new InstitutePlanColumn([$institut_id, $column_number]);
        $new_col->name = $name;
        $new_col->visible = 1;
        return $new_col->store();
    }

    /**
     * Looks up column id for course events
     *
     * @param Course $course
     *
     * @return array course events with column id
     */
    public static function getCourseEventcolumns(Course $course)
    {
        $df = DatafieldEntryModel::findByModel($course, self::COLUMN_DATAFIELD_ID);
        if ($df[0] && $df[0]->content) {
            $event_columns = unserialize($df[0]->content);
        } else {
            $event_columns = [];
        }
        return $event_columns;
    }

    /**
     * Sets the column id for course events
     *
     * @param Course $course
     * @param string $event_id SeminarCycleDate id
     * @param string $institut_id
     * @param string $column number of the column
     *
     * @return bool stored
     */
    public static function setCourseEventcolumn($course, $event_id, $institut_id, $column)
    {
        $df = DatafieldEntryModel::findByModel($course, self::COLUMN_DATAFIELD_ID);
        if ($df[0]) {
            $event_columns = self::getCourseEventcolumns($course);
            if (!is_array($event_columns[$event_id])) {
                unset($event_columns[$event_id]);
            }
            $event_columns[$event_id][$institut_id] = $column;
            $df[0]->content = serialize($event_columns);
            return $df[0]->store();
        }
        return false;
    }

    /**
     * Looks up color value for course events
     *
     * @param Course $course
     *
     * @return array course events with color value
     */
    public static function getCourseEventcolors($course)
    {
        $df = DatafieldEntryModel::findByModel($course, self::COLOR_DATAFIELD_ID);
        if ($df[0] && $df[0]->content) {
            $event_colors = unserialize($df[0]->content);
        } else {
            $event_colors = [];
        }
        return $event_colors;
    }

    /**
     * Sets color value for course events
     *
     * @param Course $course
     * @param string $event_id SeminarCycleDate id
     * @param string $institut_id
     * @param string $color colorcode
     *
     * @return bool stored
     */
    public static function setCourseEventcolor($course, $event_id, $institut_id, $color)
    {
        $df = DatafieldEntryModel::findByModel($course, self::COLOR_DATAFIELD_ID);
        if ($df[0]) {
            $event_colors = self::getCourseEventcolors($course);
            if (!is_array($event_colors[$event_id])) {
                unset($event_colors[$event_id]);
            }
            $event_colors[$event_id][$institut_id] = $color;
            $df[0]->content = serialize($event_colors);
            return $df[0]->store();
        }
        return false;
    }

    /**
     * Looks up default color value for institute course events
     *
     * @param SimpleORMap $context
     *
     * @return array course events with color value
     */
    public static function getInstituteDefaultEventcolors($context)
        $df = DatafieldEntryModel::findByModel($context, self::INST_DEFAULT_COLOR_DATAFIELD_ID);
        if ($df && $df[0]->content) {
            $event_colors = unserialize($df[0]->content);
        } else {
            $event_colors = [];
        }
        return $event_colors;
    }

    /**
     * Sets default institute course events color value for semtypes
     *
     * @param SimpleORMap $context
     * @param string $semtype
     * @param string $color colorcode
     *
     * @return bool stored
     */
    public static function setInstituteDefaultEventcolor($context, $semtype, $color)
        $df = DatafieldEntryModel::findByModel($context, self::INST_DEFAULT_COLOR_DATAFIELD_ID);
            $event_colors = self::getInstituteDefaultEventcolors($context);
            $event_colors[$semtype['name']] = $color;
            $df[0]->content = serialize($event_colors);
            return $df[0]->store();
        }
        return false;
    }

    /**
     * Sets color value for every course events of given courses semtype
     *
     * @param Course $course
     * @param string $institut_id
     * @param string $color colorcode
     *
     * @return bool stored
     */
    public static function setSemtypeEventcolor($course, $institut_id, $color)
    {
        $semtype = $course->getSemType();
        $institut = Institute::find($institut_id);
        if ($institut) {
            self::setInstituteDefaultEventcolor($institut, $semtype, $color);
        }
        $courses = Course::findBySQL('status =? AND Institut_id=?', [$semtype['id'], $course->institut_id]);
        if ($courses) {
            foreach ($courses as $semtype_course) {
                foreach (SeminarCycleDate::findBySeminar($semtype_course->seminar_id) as $cycle_date) {
                    self::setCourseEventcolor($semtype_course, $cycle_date->id, $institut_id, $color);
                }
            }
            return true;
        }
        return false;
    }

    /**
     * Prepares an array of course id and names for creation of dropable calendar events
     *
     * @param array $courses Array of courses
     * @param array $semester Semester
     *
     * @return array prepared array
     */
    public static function getEventlessCourses($courses, $semester = null)
    {
        $eventless = [];
        foreach (array_keys($courses) as $cid) {
            $course = Course::find($cid);
            $cycle_dates = SeminarCycleDate::findBySeminar($course->seminar_id);
            if (count($cycle_dates) < 1) {
                $eventless[$cid] = $course->getFullName('number-name');
            } elseif ($semester) {
                $has_date_in_semester = false;
                foreach ($cycle_dates as $cycle_date) {
                    foreach ($cycle_date->getAllDates() as $course_date) {
                        if ($course_date->date >= $semester->beginn && $course_date->date <= $semester->ende) {
                            $has_date_in_semester = true;
                            break;
                        }
                    }
                    if ($has_date_in_semester) break;
                }
                if (!$has_date_in_semester) {
                    $eventless[$cid] = $course->getFullName('number-name');
                }
            }
        }
        return $eventless;
    }

    /**
     * Creates FullCalendar event date of course events
     *
     * @param array $courses Array of courses
     * @param string $institut_id
     * @param array $semester Semester
     * @param array $specific_weekday fetch only events for specific weekday
     *
     * @return array fullcalendar events
     */
    public static function getEvents($courses, $institut_id, $semester = null, $specific_weekday = null)
    {
        $today = date('w');

        $user_insts = array_map(function ($arr) {
            return $arr['Institut_id'];
        }, Institute::getMyInstitutes($GLOBALS['user']->id));

        $min_time = explode(':', Config::get()->INSTITUTE_COURSE_PLAN_START_HOUR);
        $minbigtime = (int) $min_time[0];
        $max_time = explode(':', Config::get()->INSTITUTE_COURSE_PLAN_END_HOUR);
        $maxbigtime = (int) $max_time[0];

        $institut = Institute::find($institut_id);

        if (!$institut) {
            return [];
        }

        $inst_default_colors = self::getInstituteDefaultEventcolors($institut);

        $events = [];
        Course::findEachMany(function ($course) use (
            $courses,
            &$events,
            $today,
            $minbigtime,
            $maxbigtime,
            $user_insts,
            $institut_id,
            $inst_default_colors,
            $semester,
            $specific_weekday
        ) {
            $semtype = $course->getSemType();

Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
            $event_columns = self::getCourseEventcolumns($course) ?: [];
            $event_colors = self::getCourseEventcolors($course) ?: [];

            if (in_array($course->institut_id, $user_insts)) {
                $is_editable = true;
                $is_start_editable = !LockRules::Check($course->id, 'room_time');
                $is_duration_editable = false;
            } else {
                $is_editable = false;
                $is_start_editable = false;
                $is_duration_editable = false;
            }

            foreach (SeminarCycleDate::findBySeminar($course->seminar_id) as $cycle_date) {
                if ($semester) {
                    $has_date_in_semester = false;
                    foreach ($cycle_date->getAllDates() as $course_date) {
                        if ($course_date->date >= $semester->beginn && $course_date->date <= $semester->ende) {
                            $has_date_in_semester = true;
                            break;
                        }
                    }
                    if (!$has_date_in_semester) {
                        continue;
                    }
                }

                if (is_numeric($specific_weekday) && $specific_weekday != $cycle_date['weekday']) {
                    continue;
                }

                $conform = true;

                if ($cycle_date['weekday'] == 0) {
                    $day_offset = 7 - $today;
                } else {
                    $day_offset = $cycle_date['weekday'] - $today;
                }

                $start_time = explode(':', $cycle_date['start_time']);
                $bigtime = (int) $start_time[0];
                if ($bigtime > $maxbigtime || $bigtime < $minbigtime) {
                    $conform = false;
                } elseif ($bigtime % 2) {
                    $bigtime--;
                }
                $start_time = $bigtime . ':00:00';

                $end_time = explode(':', $start_time);
                $end_time[0] += 2;
                $end_time = implode(':', $end_time);

                $move_url = URLHelper::getURL('dispatch.php/admin/courseplanning/move_event');
                $name = $course->getFullName('number-name');

                if ($start_time != $cycle_date['start_time'] && $end_time != $cycle_date['end_time']) {
                    $start = self::iso8601date(strtotime($day_offset . ' days'), $cycle_date['start_time']);
                    $end = self::iso8601date(strtotime($day_offset . ' days'), $cycle_date['end_time']);
                    $backgroundcolor = '#6c737a';
                    $textcolor = '#ffffff';
                } else {
                    $start = self::iso8601date(strtotime($day_offset . ' days'), $start_time);
                    $end = self::iso8601date(strtotime($day_offset . ' days'), $end_time);

                    $backgroundcolor = null;
                    if (array_key_exists($cycle_date->id, $event_colors)) {
                        if (is_array($event_colors[$cycle_date->id]) && array_key_exists($institut_id, $event_colors[$cycle_date->id])) {
                            $backgroundcolor = $event_colors[$cycle_date->id][$institut_id];
                        }
                    }
                    if (!$backgroundcolor) {
                        $backgroundcolor = array_key_exists($semtype['name'], $inst_default_colors)
                            ? $inst_default_colors[$semtype['name']]
                            : self::DEFAULT_EVENT_COLOR;
                    }

                    $textcolor = '#ffffff';
                    if (self::calculateLuminosityRatio($backgroundcolor, $textcolor) < 3) {
                        $textcolor = '#000000';
                    }
                }
                if (!$is_editable) {
                    $backgroundcolor = '#c4c7c9';
                    $textcolor = '#000000';
                }

                $resource_column = '0';
                if (array_key_exists($cycle_date->id, $event_columns)) {
                    if (is_array($event_columns[$cycle_date->id]) && array_key_exists($institut_id, $event_columns[$cycle_date->id])) {
                        $resource_column = $event_columns[$cycle_date->id][$institut_id];
                    }
                }

                $events[] = [
                    'resourceId'       => $resource_column,
                    'id'               => $cycle_date->id,
                    'title'            => $name,
                    'start'            => $start,
                    'end'              => $end,
                    'textColor'        => $textcolor,
                    'backgroundColor'  => $backgroundcolor,
                    'borderColor'      => '#000',
                    'editable'         => $is_editable,
                    'startEditable'    => $is_start_editable,
                    'durationEditable' => $is_duration_editable,
                    'resourceEditable' => true,
                    'studip_api_urls'  => ['move' => $move_url],
                    'studip_view_urls' => ['edit' => URLHelper::getURL('dispatch.php/course/details/index/' . $cycle_date->seminar_id)],
                    'metadate_id'      => $cycle_date->metadate_id,
                    'course_id'        => $cycle_date->seminar_id,
                    'tooltip'          => self::getCycleInfos($course, $cycle_date),
                    'icon'             => $is_start_editable ? '' : 'lock-locked',
                    'conform'          => $conform,
                ];
            }
        }, array_keys($courses));

        return $events;
    }

    /**
     * Creates a fullcalendar event of given SeminarCycleDate
     *
     * @param SeminarCycleDate $cycle_date
     * @param string $institut_id
     *
     * @return array enriched course info string for tooltip
     */
    public static function getCycleEvent($cycle_date, $institut_id)
    {
        $course = Course::find($cycle_date->seminar_id);
        $semtype = $course->getSemType();
        $institut = Institute::find($institut_id);
        $inst_default_colors = self::getInstituteDefaultEventcolors($institut);

        $today = date('w');
        $user_insts = array_map(function ($arr) {
            return $arr['Institut_id'];
        }, Institute::getMyInstitutes($GLOBALS['user']->id));

        $min_time = explode(':', Config::get()->INSTITUTE_COURSE_PLAN_START_HOUR);
        $minbigtime = (int) $min_time[0];
        $max_time = explode(':', Config::get()->INSTITUTE_COURSE_PLAN_END_HOUR);
        $maxbigtime = (int) $max_time[0];

        $start_time = explode(':', $cycle_date['start_time']);
        $bigtime = (int) $start_time[0];

        if ($bigtime > $maxbigtime || $bigtime < $minbigtime) {
            return null;
        } elseif ($bigtime % 2) {
            $bigtime--;
        }
        $start_time = $bigtime . ':00:00';

        $end_time = explode(':', $start_time);
        $end_time[0] += 2;
        $end_time = implode(':', $end_time);

        if (in_array($course->institut_id, $user_insts)) {
            $is_editable = true;
            $is_start_editable = !LockRules::Check($cycle_date->seminar_id, 'room_time');
            $is_duration_editable = false;
        } else {
            $is_editable = false;
            $is_start_editable = false;
            $is_duration_editable = false;
        }

        if ($cycle_date['weekday'] == 0) {
            $day_offset = (7 - $today);
        } else {
            $day_offset = ($cycle_date['weekday'] - $today);
        }

        $event_columns = self::getCourseEventcolumns($course);
        $event_colors = self::getCourseEventcolors($course);

        $move_url = URLHelper::getURL('dispatch.php/admin/courseplanning/move_event');
        $name = $course->getFullName('number-name');

        if ($start_time != $cycle_date['start_time'] && $end_time != $cycle_date['end_time']) {
            $start = self::iso8601date(strtotime($day_offset . ' days'), $cycle_date['start_time']);
            $end = self::iso8601date(strtotime($day_offset . ' days'), $cycle_date['end_time']);
            $backgroundcolor = '#6c737a';
            $textcolor = '#ffffff';
        } else {
            $start = self::iso8601date(strtotime($day_offset . ' days'), $start_time);
            $end = self::iso8601date(strtotime($day_offset . ' days'), $end_time);

            $backgroundcolor = null;
            if (array_key_exists($cycle_date->id, $event_colors)) {
                if (is_array($event_colors[$cycle_date->id]) && array_key_exists($institut_id, $event_colors[$cycle_date->id])) {
                    $backgroundcolor = $event_colors[$cycle_date->id][$institut_id];
                }
            }
            if (!$backgroundcolor) {
                $backgroundcolor = array_key_exists($semtype['name'], $inst_default_colors)?$inst_default_colors[$semtype['name']]:self::DEFAULT_EVENT_COLOR;
            }

            $textcolor = '#ffffff';
            if (self::calculateLuminosityRatio($backgroundcolor, $textcolor) < 3) {
                $textcolor = '#000000';
            }
        }
        if (!$is_editable) {
            $backgroundcolor = '#c4c7c9';
            $textcolor = '#000000';
        }

        $resource_column = '0';
        if (array_key_exists($cycle_date->id, $event_columns)) {
            if (is_array($event_columns[$cycle_date->id]) && array_key_exists($institut_id, $event_columns[$cycle_date->id])) {
                $resource_column = $event_columns[$cycle_date->id][$institut_id];
            }
        }

        return [
            'resourceId'       => $resource_column,
            'id'               => $cycle_date->id,
            'title'            => $name,
            'start'            => $start,
            'end'              => $end,
            'textColor'        => $textcolor,
            'backgroundColor'  => $backgroundcolor,
            'borderColor'      => '#000',
            'editable'         => $is_editable,
            'startEditable'    => $is_start_editable,
            'durationEditable' => $is_duration_editable,
            'resourceEditable' => true,
            'studip_api_urls'  => ['move' => $move_url],
            'studip_view_urls' => ['edit' => URLHelper::getURL('dispatch.php/course/details/index/' . $cycle_date->seminar_id)],
            'metadate_id'      => $cycle_date->metadate_id,
            'course_id'        => $cycle_date->seminar_id,
            'tooltip'          => self::getCycleInfos($course, $cycle_date),
            'icon'             => $is_start_editable ? '' : 'lock-locked'
        ];
    }

    /**
     * Creates a string with course infos to be displayed as a tooltip in calendar events
     *
     * @param SeminarCycleDate $cycle_date
     *
     * @return string enriched course info string for tooltip
     */
    private static function getCycleInfos($course, $cycle_date)
    {

        $info_string = $course->getFullName('number-name') . "\n";

        $dozenten = [];
        foreach (CourseMember::findByCourseAndStatus($course->id, 'dozent') as $cmember) {
            $dozenten[$cmember->user->user_id] = $cmember->user->getFullName();
        }
        if ($dozenten) {
            $info_string .= implode(', ', $dozenten) . "\n";
        }

        $rooms = [];
        foreach ($cycle_date->getAllDates() as $course_date) {
            $room = $course_date->getRoom();
                $rooms[$room->id] = $room->name;
            }
        }
        if ($rooms) {
            $info_string .= implode(', ', $rooms) . "\n";
        }

        if ($course->getSemClass()->offsetGet('module')) {
            $mvv_pathes = [];
            $course_start = $course->start_time;
            $course_end = ($course->end_time < 0 || is_null($course->end_time))
                        ? PHP_INT_MAX
                        : $course->end_time;
            // set filter to show only pathes with valid semester data
            ModuleManagementModelTreeItem::setObjectFilter('Modul',
                function ($modul) use ($course_start, $course_end) {
                    // check for public status
                    if (!$GLOBALS['MVV_MODUL']['STATUS']['values'][$modul->stat]['public']) {
                        return false;
                    }
                    $modul_start = Semester::find($modul->start)->beginn ?: 0;
                    $modul_end = Semester::find($modul->end)->ende ?: PHP_INT_MAX;
                    return ($modul_start <= $course_end && $modul_end >= $course_start);
                }
            );

            ModuleManagementModelTreeItem::setObjectFilter('StgteilVersion',
                function ($version) {
                    return (bool) $GLOBALS['MVV_STGTEILVERSION']['STATUS']['values'][$version->stat]['public'];
                }
            );

            $trail_classes = ['Modulteil', 'StgteilabschnittModul', 'StgteilAbschnitt', 'StgteilVersion'];
            $mvv_object_pathes = MvvCourse::get($course->getId())->getTrails($trail_classes);
            if ($mvv_object_pathes) {
                if (Config::get()->COURSE_SEM_TREE_DISPLAY) {
                    $mvv_tree = [];
                    foreach ($mvv_object_pathes as $mvv_object_path) {
                        // show only complete pathes
                        if (count($mvv_object_path) == 4) {
                            // flatten the pathes to a linked list
                            $stg = reset($mvv_object_path);
                            $parent_id = 'root';
                            foreach ($mvv_object_path as $mvv_object) {
                                $mvv_object_id = $mvv_object instanceof StgteilabschnittModul
                                        ? $mvv_object->modul_id
                                        : $mvv_object->id;
                                $mvv_tree[$parent_id][$mvv_object_id] = [
                                    'id'    => $mvv_object_id,
                                    'name'  => $mvv_object->getDisplayName(),
                                    'class' => get_class($mvv_object),
                                 ];
                                $parent_id = $mvv_object_id;
                            }
                        }
                    }
                    if (count($mvv_tree)) {
                        // add the root node
                        $mvv_tree['start'][] = [
                            'id'    => 'root',
                            'name'  => Config::get()->UNI_NAME_CLEAN,
                            'class' => ''
                        ];
                    }
                } else {
                    foreach ($mvv_object_pathes as $mvv_object_path) {
                        // show only complete pathes
                        if (count($mvv_object_path) == 4) {
                            $mvv_object_names = [];
                            $modul_id = '';
                            foreach ($mvv_object_path as $mvv_object) {
                                if ($mvv_object instanceof StgteilabschnittModul) {
                                    $modul_id = $mvv_object->modul_id;
                                }
                                $mvv_object_names[] = $mvv_object->getDisplayName();
                            }
                            $mvv_pathes[] = [$modul_id => $mvv_object_names];
                        }
                    }
                }
                // to prevent collisions of object ids in the tree
                // in the case of same objects listed in more than one part
                // of the tree
                $id_sfx = new stdClass();
                $id_sfx->c = 1;
            }
            foreach ($mvv_pathes as $mvv_path) {
                foreach ($mvv_path as $mvv_path_content) {
                    $info_string .= implode(' > ', $mvv_path_content) . "\n";
                }
            }
        }

        return $info_string;
    }

    public static function getBackgroundEvents($start = null)
    {
        $datetime = new DateTime();
        $slot_duration = 1;
        $start_time = 8;
        $end_time = 16;
        $events = [];
        $day_interval = 1;

        if ($start == null) {
            $datetime->modify('monday this week');
            $datetime->add(new DateInterval('PT' . $start_time . 'H'));
            $day_interval = 7;
        }

        for ($i = 1; $i <= $day_interval; $i++) {
            for ($slot = $start_time; $slot < $end_time; $slot += $slot_duration) {
                if ($slot % 2) {
                    $datetime->setTime($slot, 0);
                    $events[] = [
                        'start'     => self::iso8601date($datetime, $slot),
                        'end'       => self::iso8601date($datetime, $slot + $slot_duration),
                        'rendering' => 'background',
                    ];

                }
            }
            $datetime->setTime($start_time, 0);
            $datetime->add(new DateInterval('P1D'));
        };
        return $events;
    }

    private static function iso8601date($date, $time = '00:00:00', $timezone = '+00:00')
    {
        // If only date parameter is passed and is a DateTimeInterface object or
        // unix timestamp, assume time from date
        if (func_num_args() === 1 && ($date instanceof DateTimeInterface || ctype_digit($date))) {
            $time = $date;
        }

        // Get time
        if ($time instanceof DateTimeInterface) {
            $time = $time->format('H:i:s');
        } elseif (ctype_digit($time)) {
            $time = date('H:i:s', $time);
        } elseif (sscanf($time, '%u:%u:%u', $hours, $minutes, $seconds)) {
            $time = sprintf('%02u:%02u:%02u', $hours, $minutes, $seconds);
        }

        // Get date
        if ($date instanceof DateTimeInterface) {
            $date = $date->format('Y-m-d');
        } elseif (ctype_digit($date)) {
            $date = date('Y-m-d', $date);
        }

        return "{$date}T{$time}{$timezone}";
    }


    // calculates the luminosity of an given RGB color
    // the color code must be in the format of RRGGBB
    // the luminosity equations are from the WCAG 2 requirements
    // http://www.w3.org/TR/WCAG20/#relativeluminancedef
    private static function calculateLuminosity($color)
    {
        $r = hexdec(substr($color, 0, 2)) / 255; // red value
        $g = hexdec(substr($color, 2, 2)) / 255; // green value
        $b = hexdec(substr($color, 4, 2)) / 255; // blue value
        if ($r <= 0.03928) {
            $r = $r / 12.92;
        } else {
            $r = pow(($r + 0.055) / 1.055, 2.4);
        }
        if ($g <= 0.03928) {
            $g = $g / 12.92;
        } else {
            $g = pow(($g + 0.055) / 1.055, 2.4);
        }
        if ($b <= 0.03928) {
            $b = $b / 12.92;
        } else {
            $b = pow(($b + 0.055) / 1.055, 2.4);
        }
        $luminosity = 0.2126 * $r + 0.7152 * $g + 0.0722 * $b;
        return $luminosity;
    }

    // calculates the luminosity ratio of two colors
    // the luminosity ratio equations are from the WCAG 2 requirements
    // http://www.w3.org/TR/WCAG20/#contrast-ratiodef
    private static function calculateLuminosityRatio($color1, $color2)
    {
        $c1 = ltrim($color1, '#');
        $c2 = ltrim($color2, '#');
        $l1 = self::calculateLuminosity($c1);
        $l2 = self::calculateLuminosity($c2);
        if ($l1 > $l2) {
            $ratio = ($l1 + 0.05) / ($l2 + 0.05);
        } else {
            $ratio = ($l2 + 0.05) / ($l1 + 0.05);
        }
        return $ratio;
    }
}