月曆功能看似簡單,實則涉及日期計算、閏年判斷、週期對齊等核心邏輯。本文從零構建一個可翻頁、高亮今日、支援任意年月跳轉的動態月曆,適合學習 PHP 日期處理與前後端分離思維。
<?php
// 1. 解析請求參數(格式:YYYYMM)
$ym = isset($_GET['ym']) ? (int)$_GET['ym'] : 0;
if ($ym) {
$year = (int)substr($ym, 0, 4);
$month = (int)substr($ym, 4, 2);
// 驗證合法性
if ($month < 1 || $month > 12) $ym = 0;
$timestamp = mktime(0, 0, 0, $month, 1, $year);
} else {
$timestamp = time(); // 預設當月
}
// 2. 核心日期資訊提取
$year = (int)date('Y', $timestamp);
$month = (int)date('m', $timestamp);
$day = (int)date('d', $timestamp); // 僅當月有效
// 3. 導航月份計算
$prevYm = date('Ym', strtotime('-1 month', $timestamp));
$nextYm = date('Ym', strtotime('+1 month', $timestamp));
// 4. 當月關鍵數值
$totalDays = (int)date('t', $timestamp); // 當月總天數
$firstWeek = (int)date('w', mktime(0,0,0,$month,1,$year)); // 1號是週幾(0=週日)
$lastWeek = (int)date('w', mktime(0,0,0,$month,$totalDays,$year)); // 最後一天週幾
// 5. 空白格計算
$leadingBlanks = $firstWeek; // 前導空白
$trailingBlanks = 6 - $lastWeek; // 尾部空白
?>
<!DOCTYPE html>
<html><head><meta charset="utf-8">
<style>
.cal-wrap {width:280px;font-family:sans-serif;margin:20px auto;}
.cal-header {display:flex;justify-content:space-between;align-items:center;
background:#333;color:#fff;padding:10px;border-radius:8px 8px 0 0;}
.cal-header a {color:#fff;text-decoration:none;font-weight:bold;}
.cal-title {font-size:1.1rem;}
table {width:100%;border-collapse:collapse;background:#fff;}
th, td {height:36px;text-align:center;font-size:0.9rem;}
th {background:#f5f5f5;color:#666;font-weight:600;}
td {border:1px solid #eee;transition:background .2s;}
td:hover {background:#e8f4fd;}
.today {background:#ff6b35!important;color:#fff;font-weight:bold;border-radius:50%;}
.other-month {color:#ccc;background:#fafafa;}
.weekend {color:#e74c3c;}
</style></head><body>
<div class="cal-wrap">
<div class="cal-header">
<a href="?ym=<?=$prevYm?>" title="上月">‹</a>
<span class="cal-title"><?=$year?>年<?=$month?>月</span>
<a href="?ym=<?=$nextYm?>" title="下月">›</a>
</div>
<table>
<thead><tr>
<th class="weekend">日</th><th>一</th><th>二</th><th>三</th>
<th>四</th><th>五</th><th class="weekend">六</th>
</tr></thead>
<tbody>
<tr>
<?php
// 前導空白
for ($i = 0; $i < $leadingBlanks; $i++)
echo '<td class="other-month"> </td>';
// 日期迴圈
for ($d = 1; $d <= $totalDays; $d++) {
$classes = [];
if ($d === $day && $ym !== 0) $classes[] = 'today'; // 僅當月高亮今日
if (($leadingBlanks + $d - 1) % 7 === 0) $classes[] = 'weekend';
if (($leadingBlanks + $d) % 7 === 0) $classes[] = 'weekend';
$classAttr = $classes ? ' class="' . implode(' ', $classes) . '"' : '';
echo "<td$classAttr>$d</td>";
// 換行邏輯
if (($leadingBlanks + $d) % 7 === 0 && $d < $totalDays)
echo '</tr><tr>';
}
// 尾部空白
for ($i = 0; $i < $trailingBlanks; $i++)
echo '<td class="other-month"> </td>';
?>
</tr>
</tbody>
</table>
</div>
</body></html>
| 問題 | 解決方案 |
|---|---|
| 1號對齊週幾 | date('w', mktime(0,0,0,月,1,年)) 返回 0(日)~6(六) |
| 當月天數 | date('t', timestamp) 自動處理閏年二月 |
| 上/下月導航 | strtotime('±1 month', timestamp) 自動跨年 |
| 換行判斷 | ($leadingBlanks + $d) % 7 === 0 每 7 格換行 |
| 今日高亮 | 僅當 $ym === 0(當月)且 $d === $day 時套用 |
php-lunar)轉換陰曆日期strtotime('last month') 在 3/31 會回到 2/28/29,導航建議固定用 1 號時間戳計算date('w') 以週日為 0,部分歐洲習慣週一為 0,需注意國際化nth-child 可替代 PHP 判斷週末樣式,但 PHP 輸出 class 更靈活