首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 動態生成月曆表:從核心算法到完整 Web 應用的實戰指南

     編輯:PHP 相關知識

月曆生成是 PHP 處理日期時間、表格渲染、表單交互的綜合練習。本文提供一個功能完整、代碼清晰的月曆實現,涵蓋核心算法、用戶輸入驗證、樣式美化、邊界處理,並延伸至日曆組件化、多語言、事件標記等進階主題。

完整可運行代碼(單文件版)

<!DOCTYPE html>
<html lang="zh-TW">
<head>
    <meta charset="UTF-8">
    <title>PHP 動態月曆</title>
    <style>
        body { font-family: 'Microsoft JhengHei', sans-serif; max-width: 800px; margin: 2rem auto; padding: 0 1rem; }
        .calendar { border-collapse: collapse; width: 100%; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
        .calendar caption { background: #2c3e50; color: white; padding: 1rem; font-size: 1.5rem; font-weight: bold; }
        .calendar th, .calendar td { border: 1px solid #e0e0e0; height: 60px; text-align: center; vertical-align: top; padding: 0.5rem; }
        .calendar th { background: #f5f5f5; font-weight: 600; }
        .calendar td.today { background: #e3f2fd; font-weight: bold; color: #1976d2; }
        .calendar td.other-month { color: #9e9e9e; background: #fafafa; }
        .calendar .day-num { font-size: 1.2rem; margin-bottom: 0.5rem; }
        .calendar .events { font-size: 0.75rem; line-height: 1.3; }
        .calendar .event { background: #fff3e0; color: #e65100; padding: 1px 4px; border-radius: 3px; margin: 1px 0; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
        .form-group { display: flex; gap: 1rem; margin-bottom: 1.5rem; flex-wrap: wrap; align-items: end; }
        .form-group label { font-weight: 500; }
        .form-group input { padding: 0.5rem; border: 1px solid #ccc; border-radius: 4px; }
        .form-group button { padding: 0.5rem 1.5rem; background: #2c3e50; color: white; border: none; border-radius: 4px; cursor: pointer; }
        .error { color: #c62828; margin-bottom: 1rem; }
    </style>
</head>
<body>
    <h1>📅 PHP 動態月曆</h1>

    <form method="post" class="form-group">
        <div>
            <label>年份 (1970-2038)</label>
            <input type="number" name="year" value="" min="1970" max="2038" required>
        </div>
        <div>
            <label>月份 (1-12)</label>
            <input type="number" name="month" value="" min="1" max="12" required>
        </div>
        <button type="submit">顯示月曆</button>
    </form>

    <?php
    // 輸入驗證
    $errors = [];
    $year  = isset($_POST['year'])  ? (int)$_POST['year']  : null;
    $month = isset($_POST['month']) ? (int)$_POST['month'] : null;

    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        if ($year === null || $year < 1970 || $year > 2038) $errors[] = '年份必須在 1970-2038 之間';
        if ($month === null || $month < 1 || $month > 12) $errors[] = '月份必須在 1-12 之間';
    }

    if ($errors) {
        foreach ($errors as $e) echo "<div class='error'>⚠ $e</div>";
    } elseif ($year !== null && $month !== null) {
        renderCalendar($year, $month);
    }

    function renderCalendar($year, $month) {
        // 設置時區(關鍵:避免 strtotime 受服務器時區影響)
        date_default_timezone_set('Asia/Taipei');

        // 月份名稱(支援中英對照)
        $months = [
            1 => ['zh' => '一月', 'en' => 'January'],   2 => ['zh' => '二月', 'en' => 'February'],
            3 => ['zh' => '三月', 'en' => 'March'],     4 => ['zh' => '四月', 'en' => 'April'],
            5 => ['zh' => '五月', 'en' => 'May'],       6 => ['zh' => '六月', 'en' => 'June'],
            7 => ['zh' => '七月', 'en' => 'July'],      8 => ['zh' => '八月', 'en' => 'August'],
            9 => ['zh' => '九月', 'en' => 'September'], 10 => ['zh' => '十月', 'en' => 'October'],
            11 => ['zh' => '十一月', 'en' => 'November'], 12 => ['zh' => '十二月', 'en' => 'December'],
        ];
        $weekDays = ['日', '一', '二', '三', '四', '五', '六'];

        // 計算當月第一天是星期幾 (0=周日 ... 6=周六)
        $firstDayTimestamp = strtotime("$year-$month-01");
        $firstDayOfWeek = (int)date('w', $firstDayTimestamp);

        // 當月總天數
        $daysInMonth = (int)date('t', $firstDayTimestamp);

        // 今天日期(用於高亮)
        $todayStr = date('Y-n-j');
        $currentMonthStr = "$year-$month";

        // 示例事件數據(實際可從數據庫讀取)
        $events = [
            "$year-$month-01" => ['元旦/月度會議'],
            "$year-$month-15" => ['專案截止', '團建活動'],
            "$year-$month-25" => ['薪資發放'],
        ];

        // 開始輸出表格
        echo "<table class='calendar'>";
        echo "<caption>{$months[$month]['zh']} {$year}年 ({$months[$month]['en']})</caption>";

        // 表頭:星期
        echo "<thead><tr>";
        foreach ($weekDays as $wd) echo "<th>$wd</th>";
        echo "</tr></thead>";

        echo "<tbody><tr>";

        // 1. 輸出上月尾部佔位單元格
        for ($i = 0; $i < $firstDayOfWeek; $i++) {
            $prevMonthDay = date('j', strtotime("-" . ($firstDayOfWeek - $i) . " days", $firstDayTimestamp));
            echo "<td class='other-month'><div class='day-num'>$prevMonthDay</div></td>";
        }

        // 2. 輸出當月日期
        for ($day = 1; $day <= $daysInMonth; $day++) {
            $weekDay = ($firstDayOfWeek + $day - 1) % 7;

            // 每行開頭(周日)換行
            if ($weekDay === 0 && $day > 1) echo "</tr><tr>";

            $dateKey = "$year-$month-$day";
            $isToday = ($dateKey === $todayStr);
            $tdClass = $isToday ? "today" : "";

            echo "<td class='$tdClass'>";
            echo "<div class='day-num'>$day</div>";

            // 事件顯示
            if (isset($events[$dateKey])) {
                echo "<div class='events'>";
                foreach ($events[$dateKey] as $ev) echo "<span class='event'>$ev</span>";
                echo "</div>";
            }
            echo "</td>";
        }

        // 3. 輸出下月開頭佔位單元格(補滿最後一行)
        $lastWeekDay = ($firstDayOfWeek + $daysInMonth - 1) % 7;
        for ($i = $lastWeekDay + 1; $i < 7; $i++) {
            $nextMonthDay = $i - $lastWeekDay;
            echo "<td class='other-month'><div class='day-num'>$nextMonthDay</div></td>";
        }

        echo "</tr></tbody></table>";
    }
    ?>
</body>
</html>

核心算法深度解析

1. 關鍵日期計算

  • 當月第一天星期幾: `date('w', strtotime("$year-$month-01"))` 返回 0(日)-6(六)
  • 當月總天數: `date('t', $timestamp)` 直接獲取,自動處理閏年
  • 某日期星期幾: `($firstDayOfWeek + $day - 1) % 7` 推算,無需重複調用 date()

2. 表格結構生成邏輯

採用「單行緩衝」策略:

  1. 先輸出上月佔位格(`$firstDayOfWeek` 個)
  2. 遍歷 1 到 `$daysInMonth`,每當 `$weekDay === 0` 且非第一天時換行(``)
  3. 最後補齊下月佔位格至周六(``)

此法避免了預先計算總行數、二維數組填充的複雜度,代碼線性直觀。

3. 時區陷阱與解決

必須在腳本開頭設置 `date_default_timezone_set()`。否則 `strtotime()`、`date()` 使用服務器默認時區(通常為 UTC),導致月曆在時區邊界(如台灣 UTC+8)顯示錯誤日期。生產環境建議在 `php.ini` 全局設置或框架啟動時統一配置。

進階功能擴展方向

1. 事件/行程管理系統

// 數據庫表結構建議
CREATE TABLE calendar_events (
    id INT AUTO_INCREMENT PRIMARY KEY,
    event_date DATE NOT NULL,
    title VARCHAR(255) NOT NULL,
    description TEXT,
    color VARCHAR(7) DEFAULT '#ff9800', -- 十六進制顏色
    user_id INT,                        -- 多用戶隔離
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_date (event_date)
);

// 讀取當月事件
function fetchMonthEvents($pdo, $year, $month, $userId = null) {
    $start = "$year-$month-01";
    $end   = date('Y-m-t', strtotime($start));
    $sql = "SELECT event_date, title, color FROM calendar_events WHERE event_date BETWEEN ? AND ?";
    $params = [$start, $end];
    if ($userId) { $sql .= " AND user_id = ?"; $params[] = $userId; }
    $stmt = $pdo->prepare($sql);
    $stmt->execute($params);
    $events = [];
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
        $events[$row['event_date']][] = ['title' => $row['title'], 'color' => $row['color']];
    }
    return $events;
}

2. 多語言與本地化(IntlCalendar)

// 使用 PHP Intl 擴展(需安裝 php-intl)
$locale = 'zh_TW'; // 或 'en_US', 'ja_JP'
$cal = IntlCalendar::createInstance('Asia/Taipei', $locale);
$cal->set($year, $month - 1, 1); // IntlCalendar 月份 0-based

$monthName = $cal->getDisplayName(IntlCalendar::FIELD_MONTH, IntlCalendar::LONG, $locale);
$weekDays = [];
for ($i = 1; $i <= 7; $i++) {
    $cal->set(IntlCalendar::DAY_OF_WEEK, $i);
    $weekDays[] = $cal->getDisplayName(IntlCalendar::FIELD_DAY_OF_WEEK, IntlCalendar::SHORT, $locale);
}
// 自動支援繁體中文「週日」、英文「Sun」、日文「日」等

3. AJAX 無刷新切換月份

  • 前端:監聽上/下月按鈕,`fetch('/calendar/api?year=2024&month=5')` 獲取 HTML 片段或 JSON
  • 後端:`renderCalendar()` 返回純 HTML 片段(無頁面骨架),或 JSON 供前端組件渲染
  • 優勢:體驗流暢,可結合 History API 更新 URL 支持瀏覽器前進後退

4. 響應式設計適配

  • 手機橫屏:表格橫向滾動(`overflow-x: auto` 容器包裹)
  • 手機直屏:改為列表視圖,每行一週,或卡片式每日一卡
  • CSS Grid 實現自適應佈局:`grid-template-columns: repeat(7, 1fr)`

總結: PHP 生成月曆的核心在於「日期運算準確」「表格結構正確」「輸出安全轉義」。從單文件腳本起步,逐步剝離業務邏輯(日期計算)、數據層(事件讀取)、視圖層(HTML 渲染),引入 Intl 本地化、AJAX 交互、響應式設計,即可演進為生產級日曆組件。建議以此為基礎,嘗試實現「農曆顯示」「節假日標記」「拖拽建立事件」等進階功能。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved