Build a Dynamic PHP Calendar with HTML & CSS – Full Code Explained
This tutorial walks through creating a fully functional PHP calendar class that generates an HTML table, handles navigation between months and years, and integrates customizable CSS styling, providing complete source code and step‑by‑step explanations for developers.
PHP Calendar Class
The Calendar class encapsulates all logic required to render a month‑view calendar as an HTML table. It stores the target year, month, the weekday of the first day ( $start_week), and the total number of days in the month ( $days). The constructor reads year and month from the query string and falls back to the current date when they are absent.
<?php
class Calendar {
private $year, $month, $start_week, $days;
/** Initialize date properties */
public function __construct() {
$this->year = isset($_GET['year']) ? $_GET['year'] : date('Y');
$this->month = isset($_GET['month']) ? $_GET['month'] : date('m');
$this->start_week = date('w', mktime(0,0,0,$this->month,1,$this->year));
$this->days = date('t', mktime(0,0,0,$this->month,1,$this->year));
}
/** Render the full calendar when the object is echoed */
public function __toString() {
$output = '<table align="center">';
$output .= $this->changeDate();
$output .= $this->weeksList();
$output .= $this->daysList();
$output .= '</table>';
return $output;
}
/** Generate the header row with weekday names */
private function weeksList() {
$week = ['日','一','二','三','四','五','六'];
$out = '<tr>';
foreach ($week as $day) {
$out .= '<th>' . $day . '</th>';
}
$out .= '</tr>';
return $out;
}
/** Generate the day cells for the current month */
private function daysList() {
$out = '<tr>';
// Empty cells before the first day of the month
for ($i = 0; $i < $this->start_week; $i++) {
$out .= '<td> </td>';
}
// Fill the days of the month
for ($j = 1; $j <= $this->days; $j++) {
$i++;
$isToday = ($j == date('d') && $this->year == date('Y') && $this->month == date('m'));
$out .= $isToday
? '<td class="fontb">' . $j . '</td>'
: '<td>' . $j . '</td>';
// Start a new row after every 7 cells
if ($i % 7 == 0) $out .= '</tr><tr>';
}
// Pad the remaining cells of the last week
while ($i % 7 !== 0) {
$out .= '<td> </td>';
$i++;
}
$out .= '</tr>';
return $out;
}
/** Helper: URL parameters for the previous year */
private function prevYear($year, $month) {
$year = max(1970, $year - 1);
return "year=$year&month=$month";
}
/** Helper: URL parameters for the previous month */
private function prevMonth($year, $month) {
if ($month == 1) {
$year = max(1970, $year - 1);
$month = 12;
} else {
$month--;
}
return "year=$year&month=$month";
}
/** Helper: URL parameters for the next year */
private function nextYear($year, $month) {
$year = min(2038, $year + 1);
return "year=$year&month=$month";
}
/** Helper: URL parameters for the next month */
private function nextMonth($year, $month) {
if ($month == 12) {
$year = min(2038, $year + 1);
$month = 1;
} else {
$month++;
}
return "year=$year&month=$month";
}
/** Build the navigation row with year/month selectors */
private function changeDate($url = 'index.php') {
$out = '<tr>';
$out .= '<td><a href="' . $url . '?' . $this->prevYear($this->year,$this->month) . '"><<<</a></td>';
$out .= '<td><a href="' . $url . '?' . $this->prevMonth($this->year,$this->month) . '"><</a></td>';
$out .= '<td colspan="3">';
$out .= '<form>';
// Year selector
$out .= '<select name="year" onchange="window.location=\'' . $url . '?year="+this.value+"&month=' . $this->month . '\'">';
for ($i = 1970; $i <= 2038; $i++) {
$selected = ($i == $this->year) ? 'selected="selected"' : '';
$out .= "<option value=\"$i\" $selected>$i</option>";
}
$out .= '</select>';
// Month selector
$out .= '<select name="month" onchange="window.location=\'' . $url . '?year=' . $this->year . '&month="+this.value\'">';
for ($j = 1; $j <= 12; $j++) {
$selected = ($j == $this->month) ? 'selected="selected"' : '';
$out .= "<option value=\"$j\" $selected>$j</option>";
}
$out .= '</select>';
$out .= '</form>';
$out .= '</td>';
$out .= '<td><a href="' . $url . '?' . $this->nextMonth($this->year,$this->month) . '">></a></td>';
$out .= '<td><a href="' . $url . '?' . $this->nextYear($this->year,$this->month) . '">>></a></td>';
$out .= '</tr>';
return $out;
}
}
?>HTML Wrapper and Styling
A minimal HTML page instantiates the class and echoes it. The page includes a <style> block that defines the table border, cell dimensions, and a highlight style ( .fontb) for the current day.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>PHP Calendar Example</title>
<style>
table { border:1px solid #ccc; border-collapse:collapse; }
th, td { width:30px; height:30px; text-align:center; border:1px solid #ddd; }
.fontb { color:#fff; background:#0066ff; }
form { margin:0; padding:0; }
</style>
</head>
<body>
<?php
require_once 'Calendar.php'; // path to the class file
$calendar = new Calendar();
echo $calendar;
?>
</body>
</html>Extending the Calendar
To attach events, add an associative array (e.g., $events[day] = 'Meeting') and modify daysList() to render the event inside the cell.
For richer UI, replace the dropdown form with a JavaScript date‑picker and use AJAX to fetch the HTML for a different month without a full page reload.
The class limits years to the Unix‑timestamp range (1970‑2038). Adjust the constants in prevYear(), nextYear(), etc., if a broader range is required.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Laravel Tech Community
Specializing in Laravel development, we continuously publish fresh content and grow alongside the elegant, stable Laravel framework.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
