PHP Dates and Times
Program: Calendar
Example Using LittleCalendar()
<style type="text/css">
.prev { text-align: left; }
.next { text-align: right; }
.day, .month, .weekday { text-align: center; }
.today { background: yellow; }
.blank { }
</style>
<?php
// print the calendar for the current month if a month
// or year isn't in the query string
$month = isset($_GET['month']) ? intval($_GET['month']) : date('m');
$year = isset($_GET['year']) ? intval($_GET['year']) : date('Y');
$cal = new LittleCalendar($month, $year);
print $cal->html();
The LittleCalendar class can produce a representation of a month’s calendar in different formats. Its prepare() method calculates the right information about each day of the month and appropriate beginning and end padding. Then, separate internal methods, invoked by generate() based on its argument, produce formatting appropriate for different contexts. The html() method produces an HTML calendar suitable for display in a web page. The text() method produces a text-based calendar for display in the shell.
Example LittleCalendar
class LittleCalendar {
/** DateTime */
protected $monthToUse;
protected $prepared = false;
protected $days = array();
public function __construct($month, $year) {
/* Build a DateTime for the month we're going to display */
$this->monthToUse = DateTime::createFromFormat('Y-m|',
sprintf("%04d-%02d",
$year, $month));
$this->prepare();
}
protected function prepare() {
// Build up an array of information about each day
// in the month including appropriate padding at the
// beginning and end
// First, days of the week across the first row
foreach (array('Su', 'Mo','Tu','We','Th','Fr','Sa') as $dow) {
$endOfRow = ($dow == 'Sa');
$this->days[] = array('type' => 'dow',
'label' => $dow,
'endOfRow' => $endOfRow);
}
// Next, placeholders up to the first day of the week
for ($i = 0, $j = $this->monthToUse->format('w'); $i < $j; $i++) {
$this->days[] = array('type' => 'blank');
}
// Then, one item for each day in the month
$today = date('Y-m-d');
$days = new DatePeriod($this->monthToUse,
new DateInterval('P1D'),
$this->monthToUse->format('t') - 1);
foreach ($days as $day) {
$isToday = ($day->format('Y-m-d') == $today);
$endOfRow = ($day->format('w') == 6);
$this->days[] = array('type' => 'day',
'label' => $day->format('j'),
'today' => $isToday,
'endOfRow' => $endOfRow);
}
// Last, any placeholders for the end of the month, if we
// didn't have an endOfWeek day as the last day in the month
if (! $endOfRow) {
for ($i = 0, $j = 6 - $day->format('w'); $i < $j; $i++) {
$this->days[] = array('type' => 'blank');
}
}
}
public function html($opts = array()) {
if (! isset($opts['id'])) {
$opts['id'] = 'calendar';
}
if (! isset($opts['month_link'])) {
$opts['month_link'] =
'<a href="'.htmlentities($_SERVER['PHP_SELF']) . '?' .
'month=%d&year=%d">%s</a>';
}
$classes = array();
foreach (array('prev','month','next','weekday','blank','day','today')
as $class) {
if (isset($opts['class']) && isset($opts['class'][$class])) {
$classes[$class] = $opts['class'][$class];
}
else {
$classes[$class] = $class;
}
}
/* Build a DateTime for the previous month */
$prevMonth = clone $this->monthToUse;
$prevMonth->modify("-1 month");
$prevMonthLink = sprintf($opts['month_link'],
$prevMonth->format('m'),
$prevMonth->format('Y'),
'«');
/* Build a DateTime for the following month */
$nextMonth = clone $this->monthToUse;
$nextMonth->modify("+1 month");
$nextMonthLink = sprintf($opts['month_link'],
$nextMonth->format('m'),
$nextMonth->format('Y'),
'»');
$html = '<table id="'.htmlentities($opts['id']).'">
<tr>
<td class="'.htmlentities($classes['prev']).'">' .
$prevMonthLink . '</td>
<td class="'.htmlentities($classes['month']).'" colspan="5">'.
$this->monthToUse->format('F Y') .'</td>
<td class="'.htmlentities($classes['next']).'">' .
$nextMonthLink . '</td>
</tr>';
$html .= '<tr>';
$lastDayIndex = count($this->days) - 1;
foreach ($this->days as $i => $day) {
switch ($day['type']) {
case 'dow':
$class = 'weekday';
$label = htmlentities($day['label']);
break;
case 'blank':
$class = 'blank';
$label = ' ';
break;
case 'day':
$class = $day['today'] ? 'today' : 'day';
$label = htmlentities($day['label']);
break;
}
$html .=
'<td class="' . htmlentities($classes[$class]).'">'.
$label . '</td>';
if (isset($day['endOfRow']) && $day['endOfRow']) {
$html .= "</tr>\n";
if ($i != $lastDayIndex) {
$html .= '<tr>';
}
}
}
$html .= '</table>';
return $html;
}
public function text() {
$lineLength = strlen('Su Mo Tu We Th Fr Sa');
$header = $this->monthToUse->format('F Y');
$headerSpacing = floor(($lineLength - strlen($header))/2);
$text = str_repeat(' ', $headerSpacing) . $header . "\n";
foreach ($this->days as $i => $day) {
switch ($day['type']) {
case 'dow':
$text .= sprintf('% 2s', $day['label']);
break;
case 'blank':
$text .= ' ';
break;
case 'day':
$text .= sprintf("% 2d", $day['label']);
break;
}
$text .= (isset($day['endOfRow']) && $day['endOfRow']) ? "\n" : " ";
}
if ($text[strlen($text)-1] != "\n") {
$text .= "\n";
}
return $text;
}
}
The LittleCalendar constructor just builds a DateTime object for the month it needs to render. Then, it calls prepare(), which does the work of building up the $days member variable into an array of information about each of the days (or placeholders) to be rendered. The prepare() function first puts elements for each day of the week (as a header row) into $days, then some spacers based on the day of the week of the first day of the month. Next, it puts an element for each day of the month, and finally spacers to pad out the end of the month if necessary.
Inside prepare(), the necessary information about each day of the month is retrieved by calling format() on DateTime objects. This provides day-of-the-week information for the spacers as well as per-day information for each day. The individual days of the month are obtained by iterating through a DatePeriod spanning the month to use at a 1-day interval.
Although prepare() figures out enough information to lay out the calendar, it leaves the actual formatting to other methods. The html() method produces an HTML- formatted calendar and the text() method produces a text-formatted calendar. The html() method takes an optional array of options as an argument. You can pass a printf()-style format string in $opts['month_link'] to change how the links to the previous and next months are printed as well as an id attribute for the table. The id defaults to calendar if not specified.
Additionally, you can pass in class names to use for various elements in the layout. These go in an array-valued class option. In that class array, the classes you can specify are prev, month, next, weekday, blank, day, and today. Example includes styles that provide a basic pleasant layout for the table, including highlighting the current day in yellow.
The html() method finds the previous and next months (using DateTime::modify()) in order to generate proper previous and next links. After making a short header, it iterates through the calculated days, putting each one into an appropriate table cell. At the end of each week, the table row is closed.
The text() method has similar logic, but (obviously) different output. It generates a header containing the month and year and then iterates through the calculated days, adding a newline at the end of each week. By subclassing LittleCalendar, you could add other customized calendar outputs. For example, for fancier console output you could make a colorText() method that uses ANSI escape codes to display the current day in a different color.
No comments:
Post a Comment