Part of the EllisLab Network
x
 
Create New Page
 View Previous Changes    ( Last updated by sophistry )

OOCalendar

An object oriented approach to building a monthly calendar.

This code assumes a database table with event dates specified in a datetime field. It also helps to have a CSS file that defines a few table classes. you can see my example below.

The controller:

function cal($y,$m)
    
{
        $this
->load->helper('url');
        
$this->load->model('Month_model');
        
$this->Month_model->set_event_table('Events');
        
$this->Month_model->set_event_field('datetime_start');

        
$p_arr = array('year','month','month_name_long','month_prev','month_next','year_in_month_prev','year_in_month_next');
        
$data['month_array'] = $this->Month_model->db_month_box($m,$y);
        foreach (
$p_arr as $p)
        
{
            $data[$p]
= $this->Month_model->$p;
        
}
        $this
->load->view('month_table',$data);            
    
}

The model:

<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class
Month_model extends Model {

    
var $month;
    var
$month_prev;
    var
$month_next;
    
    var
$month_name_long;
    var
$month_name_short;
    
    var
$year;
    var
$year_in_month_prev;
    var
$year_in_month_next;
    
    var
$month_arr;
    var
$db_table_with_dates;
    var
$db_field_with_date;
    
    function
Month_model()
    
{
        parent
::Model();
    
}
    
    
// set the db and field
    // where events are
    
function set_event_table($tbl_name)
    
{
        $this
->db_table_with_dates = $tbl_name;
    
}
    
    
function set_event_field($fld_name)
    
{
        $this
->db_field_with_date = $fld_name;
    
}
    
    
// send month and year
    // build month box
    // and then hang the events
    // fetched from the specified db
    
function db_month_box($m,$y)
    
{
        
// get the raw month structure
        
$arr = $this->month_box($m,$y);
        
// hang some objects onto the month
        
$arr = $this->hang_events();
        
// chunk out weekly arrays, keep keys
        
$ca = array_chunk($arr,7,TRUE);
        return
$ca;
    
}
    
    
// return an array with Ymd format
    // dates as keys, calendar month with
    // padded entries to make a fully
    // rectangular month calendar
    
function month_box($m,$y)
    
{
        
if ($m > 12) $m = 12;
        if (
$m < 1) $m = 1;
        
        
$this->month = $m;
        
$this->year = $y;
        
$this->month_arr = array();
        
// get timestamp of the first
        // day of requested month and year
        
$ts_start = mktime(12, 0, 0, $this->month, 1, $this->year);        
        
$days_in_month = date('t',$ts_start);
        
        
// use noon so we don't get messed up
        
$ts_end = mktime(12, 0, 0, $this->month, $days_in_month, $this->year);

        
$pms = strtotime('-1 month',$ts_start);
        
$pme = strtotime('-1 day',$ts_start);
        
$nms = strtotime('+1 day',$ts_end);
        
// have to do extra to get prev/next month params
        
$this->month_next = date('m',$nms);
        
$this->year_in_month_next = date('Y',$nms);
        
$this->month_prev = date('m',$pms);
        
$this->year_in_month_prev = date('Y',$pms);
        
        
$days_in_month_nm = date('t',$nms);
        
$nme = mktime(12, 0, 0, $this->month_next, $days_in_month_nm, $this->year_in_month_next);

        
$prev_month_start = date('Ymd', $pms);
        
$prev_month_end = date('Ymd', $pme);
        
$next_month_start = date('Ymd', $nms);
        
$next_month_end = date('Ymd', $nme);
        
        
$start_day = date('Ymd',$ts_start);
        
$end_day = date('Ymd',$ts_end);
        
// generate array from the
        // number of days in month
        
$arr_tm = range($start_day,$end_day);
        
$arr_pm = range($prev_month_start,$prev_month_end);
        
$arr_nm = range($next_month_start,$next_month_end);
        
        
// get the day num of the
        // first/last day of the month
        
$day_of_week_first = date('w', $ts_start);
        
$day_of_week_last = date('w', $ts_end);
        
// pad the beginning of the array
        // based on day_of_week, negative keys
        
$pad_in = ($day_of_week_first) ? array_slice($arr_pm,-$day_of_week_first) : array();
        
// now the lead-out days
        
$day_mod = 6-$day_of_week_last;        
        
$pad_out = ($day_mod)?array_slice($arr_nm,0,$day_mod):array();
        
        
// put the lead-in/lead-out days on
        
$arr = array_merge($pad_in,$arr_tm,$pad_out);
        
$arr = array_flip($arr);
        
// normalize every value
        // do this to avoid making
        // a day class right now
        // just use stdClass
        
$today_Ymd = date('Ymd');
        
$this->month_name_long = date('F',$ts_start);
        
$this->month_name_short = date('M',$ts_start);
        foreach(
$arr as $k => $v)
        
{    
            $o
= new stdClass;
            
// remove the spaces in the sscanf() format string
            
list($year,$month,$day) = sscanf($k, "% 4d% 2d% 2d");
            
// store the timestamp to enable
            // access to any date formatting
            // via date() function in view
            
$o->ts = strtotime($month.'/'.$day.'/'.$year);
            
$o->day_of_month = $day;
            
$o->day_of_year = date('z',$o->ts);

            
$o->day_ordinal = date('jS',$o->ts);
            
$o->day_name_long = date('D',$o->ts);
            
$o->day_name_short = substr($o->day_name_long,0,3);
            
$o->day_name_initial = substr($o->day_name_short,0,1);
            
            
$o->not_in_month = ($month!=$this->month);
            
$o->today = ($today_Ymd==$k);
            
// start with event FALSE,
            // add events in other function
            
$o->has_event = FALSE;
            
$o->data = array();
            
$this->month_arr[$k] = $o;
        
}
        
return $this->month_arr;
    
}
    
    
function hang_events()
    
{
        $f
= $this->db_field_with_date;
        
// define some calculated fields in SQL
        
$this->db->select("*, DATE_FORMAT($f, '%m') as month, DATE_FORMAT($f, '%d') as day, DATE_FORMAT($f, '%Y%m%d') AS Ymd", FALSE);
        
// get everything in the month (any year)
        
$this->db->where("MONTH($f)",$this->month);
        
$this->db->order_by($f);
        
$q = $this->db->get($this->db_table_with_dates);
        
$ev = $q->result();
        foreach (
$ev as $e)
        
{
            $month_date
= $this->year.$e->month.$e->day;
            
// check for exact match first then annual
            // these should be separated to handle single and
            // repeating dates differently
            
if ( array_key_exists($e->Ymd, $this->month_arr) OR array_key_exists($month_date, $this->month_arr) )
            
{
                $this
->month_arr[$month_date]->has_event = TRUE;
                
$this->month_arr[$month_date]->data[] = $e;
            
}
        }
        
return $this->month_arr;
    
}
    
}
// EOF

and the view:

<table id="calendar" cellspacing="0" cellpadding="0" summary="This month's calendar">
<
caption>
<?php echo anchor('example/cal/'.$year_in_month_prev.'/'.$month_prev, '&laquo;', array('title'=>"previous month", 'class'=>"nav")); ?>
<?php
echo $month_name_long; ?> <?php echo $year; ?>
<?php
echo anchor('example/cal/'.$year_in_month_next.'/'.$month_next, '&raquo;', array('title'=>"next month", 'class'=>"nav")); ?>
</caption>
    <
tr>
    
<?php foreach($month_array[0] as $head): ?>
        
<th scope="col"><?php echo $head->day_name_initial; ?></th>
    
<?php endforeach; ?>
    
</tr>
    
<?php foreach($month_array as $week): ?>
        
<tr>
        
<?php foreach($week as $day): ?>
            
<td class=
                
<?php if($day->today){echo '"today"';}
                
elseif($day->not_in_month){echo '"not_in_month"';}
                else{echo
'"day"';} ?>
            
>
                                    
                
<?php echo ($day->has_event) ? anchor('example/cal/'.$year.'/'.$month.'/'.$day->day_of_month, $day->day_of_month) : $day->day_of_month; ?>
            
                
<!--
                <
ul>
                
<?php foreach($day->data as $event): ?>
                        
<li><?php echo $event->id; ?>: <?php echo $event->event_name; ?></li>
                
<?php endforeach; ?>
                
</ul>
                -->
            
            </
td>
        
<?php endforeach; ?>
        
</tr>
    
<?php endforeach; ?>
    
</table>

The CSS for the table (needs a gif file):

body {
    font
: normal 12px/20px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
    
color: #616B76;
}

a {
    color
: #DF9496;
}

#calendar {
    
width: 141px;
    
padding: 0;
    
margin: 0;
    
border-left: 1px solid #A2ADBC;
    
font: normal 12px/20px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
    
color: #616B76;
    
text-align: center;
    
background-color: #fff;
}

.nav, .nav a {
    font
: bold 18px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
    
color: #fff;    
    
text-align: center;
    
text-decoration: none;
}    


caption {
    margin
: 0;
    
padding: 0;
    
width: 141px;
    
background: #A2ADBC;
    
color: #fff;     
    
font: bold 12px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
    
text-align: center;
}

th {
    font
: bold 11px/20px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
    
color: #616B76;
    
background: #D9E2E1;
    
border-right: 1px solid #A2ADBC;
    
border-bottom: 1px solid #A2ADBC;
    
border-top: 1px solid #A2ADBC;
}

.today, td.today a, td.today a:link, td.today a:visited {
    color
: #F6F4DA;
    
font-weight: bold;
    
background: #DF9496;
}

.not_in_month, td.not_in_month a, td.not_in_month a:link, td.not_in_month a:visited {
    color
: #CCCCCC;
    
font-weight: normal;
    
background: #EEEEEE;
}

td
.day {
    border
-right: 1px solid #A2ADBC;
    
border-bottom: 1px solid #A2ADBC;
    
width: 20px;
    
height: 20px;
    
text-align: center;
    
background: url(../img/bg_calendar.gif) no-repeat right bottom;
}

td
.day a {
    text
-decoration: none;
    
font-weight: bold;
    
display: block;
}

td
.day a:link, td.day a:visited {    
    color
: #B88546;
    
background: url(../img/bg_calendar.gif) no-repeat;
}

td
.day a:hover, td.day a:active {
    color
: #6aa3ae;
    
background: url(../img/bg_calendar.gif) no-repeat right top;
}