calendar($MM, $YYYY) 接受月份與年份參數,預設為當月。NB)。strftime 格式化,支援本地化月份名稱(需伺服器 locale 設定)。<?php
/**
* 動態月曆生成
* @param int|string $MM 月份 (1-12),空則當月
* @param int|string $YYYY 年份 (四位數),空則当年
*/
function calendar($MM = '', $YYYY = '') {
if ($MM === '') $MM = date("m");
if ($YYYY === '') $YYYY = date("Y");
if (!checkdate($MM, 1, $YYYY)) return; // 非法日期靜默返回
// 關鍵日期資訊
$days = (int)strftime("%d", mktime(0,0,0, $MM+1, 0, $YYYY)); // 該月總天數
$firstDay = (int)strftime("%w", mktime(0,0,0, $MM, 1, $YYYY)); // 1號星期幾 (0=Sun)
$lastDay = (int)strftime("%w", mktime(0,0,0, $MM, $days, $YYYY)); // 最後一天星期幾
$monthName = strftime("%b %Y", mktime(0,0,0, $MM, 1, $YYYY)); // 如 "Jan 2024"
$preMonth = strftime("%m", mktime(0,0,0, $MM-1, 1, $YYYY));
$preYear = strftime("%Y", mktime(0,0,0, $MM-1, 1, $YYYY));
$nextMonth = strftime("%m", mktime(0,0,0, $MM+1, 1, $YYYY));
$nextYear = strftime("%Y", mktime(0,0,0, $MM+1, 1, $YYYY));
$self = $_SERVER['PHP_SELF'];
$nb = isset($_GET['NB']) ? $_GET['NB'] : '';
echo "<table border=\"1\" cellpadding=\"1\" cellspacing=\"1\">\n";
echo "<tr>\n";
echo "<th valign=\"top\"><a href=\"$self?NB=$nb&MM=$preMonth&YY=$preYear\">P</a></th>\n";
echo "<th colspan=\"5\" valign=\"top\">$monthName</th>\n";
echo "<th valign=\"top\"><a href=\"$self?NB=$nb&MM=$nextMonth&YY=$nextYear\">N</a></th>\n";
echo "</tr>\n";
echo "<tr style=\"font-family:Verdana;font-size:x-small\">\n";
foreach (['Sun','Mon','Tue','Wed','Thu','Fri','Sat'] as $d) echo "<th>$d</th>\n";
echo "</tr>\n";
$current = 1;
for ($row = 1; $row <= 5; $row++) { // 最多 5 週(含跨月補齊最多 6 列,這裡固定 5 列可能不足,見下方改進)
echo "<tr align=\"left\" valign=\"top\" style=\"font-family:Verdana;font-size:x-small\">\n";
$diff = $firstDay - $lastDay;
if ($firstDay > $lastDay && $current == 1 && $diff != 1) {
// 情況:首日星期 > 尾日星期,且非緊湊排列(如 31 天月份)
for ($x = $lastDay; $x >= 0; $x--) {
$pd = $days - $x;
echo "<td>$pd</td>\n";
}
for ($z = 1; $z < $firstDay - $lastDay; $z++) echo "<td> </td>\n";
for ($y = $firstDay; $y < 7; $y++) {
echo "<td>$current</td>\n";
$current++;
}
} elseif ($firstDay != 0 && $current == 1) {
// 一般情況:第一列前面補空格
for ($z = 1; $z <= $firstDay; $z++) echo "<td> </td>\n";
for ($y = $firstDay; $y < 7; $y++) {
echo "<td>$current</td>\n";
$current++;
}
} else {
// 後續列:填滿 7 格或直到月份結束
for ($u = 1; $u <= 7 && $current <= $days; $u++) {
echo "<td>$current</td>\n";
$current++;
}
}
echo "</tr>\n";
if ($current > $days) break; // 提前結束
}
echo "</table>\n";
}
?>
<?php
// 直接顯示當月
calendar();
// 或指定年月
calendar(12, 2024);
?>
| 問題 | 改進方案 |
|---|---|
| 固定 5 列可能不足(如 31 天且週六起,需 6 列) | 改用 while ($current <= $days) 迴圈動態生成列,不限制列數。 |
| 跨月補齊邏輯複雜且有邊界錯誤 | 採用統一算法: 1. 計算第一天前需補幾格 $pad = $firstDay。2. 總格數 $total = $pad + $days。3. 列數 ceil($total/7)。4. 雙層迴圈輸出,索引 $idx = $row*7 + $col,若 $idx < $pad 輸出空格,$idx >= $pad+$days 輸出空格,否則輸出 $idx - $pad + 1。 |
| 硬編碼樣式、難以客製化 | 輸出純資料結構(二維陣列),由模板引擎或獨立渲染函式生成 HTML,支援 CSS class 注入。 |
依賴 strftime(PHP 8.1+ 棄用) | 改用 IntlDateFormatter 或 DateTime 格式化,確保長期相容。 |
function getCalendarData(int $month, int $year): array {
$firstDay = (int)(new DateTime("$year-$month-01"))->format('w');
$days = (int)(new DateTime("$year-$month-01"))->format('t');
$weeks = [];
$day = 1;
for ($row = 0; $day <= $days; $row++) {
$weeks[$row] = [];
for ($col = 0; $col < 7; $col++) {
$idx = $row * 7 + $col;
if ($idx < $firstDay) {
$weeks[$row][$col] = null; // 上月補齊
} elseif ($day <= $days) {
$weeks[$row][$col] = $day++;
} else {
$weeks[$row][$col] = null; // 下月補齊
}
}
}
return ['weeks' => $weeks, 'monthName' => (new DateTime("$year-$month-01"))->format('F Y')];
}
此版本回傳純陣列,前端可自由渲染為 Table、Flex Grid、React/Vue 組件等。