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.

Laravel Tech Community
Laravel Tech Community
Laravel Tech Community
Build a Dynamic PHP Calendar with HTML & CSS – Full Code Explained

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>&nbsp;</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>&nbsp;</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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

PHPCSSHTMLCalendarDate Functions
Laravel Tech Community
Written by

Laravel Tech Community

Specializing in Laravel development, we continuously publish fresh content and grow alongside the elegant, stable Laravel framework.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.