Skip to content
timsamuelsson edited this page Jan 6, 2014 · 8 revisions

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: [CODE] 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);            
}

[/CODE]

The model: [CODE]<?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 [/CODE]

and the view: [CODE]

<?php foreach($month_array[0] as $head): ?> <?php endforeach; ?> <?php foreach($month_array as $week): ?> <?php foreach($week as $day): ?>
<?php echo anchor('example/cal/'.$year_in_month_prev.'/'.$month_prev, '«', 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, '»', array('title'=>"next month", 'class'=>"nav")); ?>
<?php echo $head->day_name_initial; ?>
today){echo '"today"';} elseif($day->not_in_month){echo '"not_in_month"';} else{echo '"day"';} ?> >
            &lt;?php echo ($day->has_event) ? anchor('example/cal/'.$year.'/'.$month.'/'.$day->day_of_month, $day->day_of_month) : $day->day_of_month; ?&gt;
        
            &lt;!--
            <ul> 
            &lt;?php foreach($day->data as $event): ?&gt;
                    <li>&lt;?php echo $event->id; ?&gt;: &lt;?php echo $event->event_name; ?&gt;</li>
            &lt;?php endforeach; ?&gt;
            </ul>
            --&gt;
        
        </td>
    &lt;?php endforeach; ?&gt;
    </tr>
&lt;?php endforeach; ?&gt;
[/CODE]

The CSS for the table (needs a gif file): [CODE]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; }[/CODE]

Clone this wiki locally