PHPでその月のカレンダーを自動生成するプログラムを作っていきましょう。
自動カレンダーを作成することで、予約システムやスケジュール管理、イベント表示などにも応用できる日付処理のロジックを学ぶことができます。
完成イメージ

カレンダー作成の考え方
カレンダーをプログラムで自動生成するときは、人間が紙にカレンダーを書き込むときと同じ手順で考えると理解しやすいです。まず、最初の行には曜日が必ず固定で並びます。これは「日・月・火…」と配列で用意しておきます。
次に「1日」を書き込むのですが、この「1日」がどの曜日に来るのかは月ごとに違います。もし1日が水曜日から始まるなら、その前に3つ分の空白セルを置いてから1日を配置します。
あとは繰り返しで、2日、3日…と数字を順番に書き込みます。ただし、月によって28日や31日までと日数が違うので、その月の「最終日」をあらかじめ調べておく必要があります。そして7日ごとに改行して次の行を作り、また同じように数字を入れていきます。
最後に、月の最終日を書き終えたあとに余った曜日分の空白セルを入れて整えれば完成です。
このように、手でカレンダーを作るときの「順番」をそのままプログラムに落とし込むイメージで考えると、カレンダー生成のロジックがスッキリ理解できます。
フォルダ構成とファイル名
- 作成フォルダ:
php-practice-calendar - 作成ファイル:
calendar01.php - 使用CSSファイル(任意):
style.cssは任意で作成
calendar01.php基本コード
<?php
// タイムゾーンを日本に設定
date_default_timezone_set('Asia/Tokyo');
// 今日の日付を取得
$year = date('Y');
$month = date('n'); // 1〜12
$today = date('j');
// 今月1日のタイムスタンプ
$firstDay = mktime(0, 0, 0, $month, 1, $year);
// 今月の最終日(28〜31)
$lastDay = date('t', $firstDay);
// 1日の曜日(0:日, 1:月, ... 6:土)
$startWeekDay = date('w', $firstDay);
// 曜日ラベル
$weekLabels = ['日', '月', '火', '水', '木', '金', '土'];
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title><?= "{$year}年{$month}月のカレンダー" ?></title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<h1><?= "{$year}年{$month}月のカレンダー" ?></h1>
</header>
<main>
<table>
<tr>
<?php foreach ($weekLabels as $i => $label): ?>
<th class="<?= $i === 0 ? 'sunday' : ($i === 6 ? 'saturday' : '') ?>">
<?= $label ?>
</th>
<?php endforeach; ?>
</tr>
<tr>
<?php
// 空セルを出力する処理
//今月の1日が 何曜日から始まるか に応じて、最初の行に空白のセルを挿入
//今月1日が水曜日(=$startWeekDay = 3)だった場合、
//0=日曜 1=月曜 2=火曜 3=水曜 4=木曜 5=金曜 6=土曜
//日・月・火の3つの空セルを先に出力します
for ($i = 0; $i < $startWeekDay; $i++) {
echo "<td></td>";
}
//実際の日付(1日〜月末)をループで出力
for ($day = 1; $day <= $lastDay; $day++) {
//今月1日が何曜日から始まるか($startWeekDay)をベースに、
//「今何日目か($day)」を足し算して、カレンダー上の曜日番号を算出
//% 7 は「7で割った余り」なので、曜日のループ(0〜6)循環
$weekDay = ($startWeekDay + $day - 1) % 7;
$class = '';
//クラス付与で土曜・日曜・今日を区別
if ($weekDay === 0) {
$class = 'sunday';
} elseif ($weekDay === 6) {
$class = 'saturday';
}
//当日だったら class=todayをつける
if ($day == $today) {
$class .= ' today';
}
//実際に1日ごとに出力
echo "<td class='{$class}'>{$day}</td>";
//週末(土曜日=6)になったら、その行(tr)を閉じる </tr>
//でもまだ月末ではないなら、新しい週の行を開く <tr>
if ($weekDay === 6) {
echo "</tr>";
if ($day !== $lastDay) echo "<tr>";
}
}
// 月の最終週:余った空白セルを出力
//月末が金曜日や水曜日などで終わった場合
//表の最後の行の 右端が余るので、空白セル <td></td> を追加して調整
$endWeekDay = ($startWeekDay + $lastDay) % 7;
if ($endWeekDay !== 0) {
for ($i = $endWeekDay; $i < 7; $i++) {
echo "<td></td>";
}
echo "</tr>";
}
?>
</table>
</main>
</body>
</html>
プログラムの構成と解説
タイムゾーンと日付情報の取得
date_default_timezone_set('Asia/Tokyo');
$year = date('Y');
$month = date('n'); // 1〜12
$today = date('j');
- 日本時間に設定(
Asia/Tokyo) - 年・月・日をそれぞれ取得
date('n')は、「今月」の月を表します。date('j')は、「今日」の日にちになります。
月の開始日・最終日を取得
$firstDay = mktime(0, 0, 0, $month, 1, $year);
$lastDay = date('t', $firstDay);
$startWeekDay = date('w', $firstDay);
$firstDay = mktime(0, 0, 0, $month, 1, $year);mktime()関数は「指定した日時のタイムスタンプを作る」関数です。- ここでは「今の月の1日・0時0分0秒」のタイムスタンプを作っています。
- たとえば、2025年9月なら「2025年9月1日 0:00:00」の情報を
$firstDayに入れています。 - タイムスタンプとは?「1970年1月1日からの秒数」を表す数値で、日付や時間の計算に便利な形式
$lastDay = date('t', $firstDay)date('t', タイムスタンプ)は、その月の最終日(28~31)を取得します。$firstDayで取得した「今月の1日」を元に、その月が何日まであるかを$lastDayに代入しています。例:2025年9月なら$lastDay = 30
$startWeekDay = date('w', $firstDay);date('w', タイムスタンプ)は、その日が何曜日かを0〜6で返す関数です。0 = 日曜,1 = 月曜,2 = 火曜,3 = 水曜….6 = 土曜- 今月1日が何曜日かを調べることで、カレンダーの最初の行の空白セルの数を決めるのに使います。
例:2025年9月1日は月曜日 →$startWeekDay = 1
PHPの date() 関数:フォーマット一覧
| 書式 | 意味 | 例(2025年9月7日) |
|---|---|---|
'Y' | 年(4桁) | 2025 |
'y' | 年(下2桁) | 25 |
'm' | 月(2桁・ゼロ埋め) | 09 |
'n' | 月(1桁・ゼロなし) | 9 |
'd' | 日(2桁・ゼロ埋め) | 07 |
'j' | 日(1桁・ゼロなし) | 7 |
't' | 月の日数(28〜31) | 30(9月の場合) |
曜日ラベルを出力(表の1行目)
$weekLabels = ['日', '月', '火', '水', '木', '金', '土'];
- テーブルの1行目に、曜日(Sun〜Sat)を並べます。
- 日曜は
.sunday、土曜は.saturdayのcssのクラスを追加して色を変更しておきます。
カレンダー本体(日付の出力)
// 空セルを出力する処理
//今月の1日が 何曜日から始まるか に応じて、最初の行に空白のセルを挿入
//今月1日が水曜日(=$startWeekDay = 3)だった場合、
//0=日曜 1=月曜 2=火曜 3=水曜 4=木曜 5=金曜 6=土曜
//日・月・火の3つの空セルを先に出力します
for ($i = 0; $i < $startWeekDay; $i++) {
echo "<td></td>";
}
// 実際の日付(1日〜月末)をループで出力
for ($day = 1; $day <= $lastDay; $day++) {
//今月1日が何曜日から始まるか($startWeekDay)をベースに、
//「今何日目か($day)」を足し算して、カレンダー上の曜日番号を算出
//% 7 は「7で割った余り」なので、曜日のループ(0〜6)循環
$weekDay = ($startWeekDay + $day - 1) % 7;
$class = '';
//クラス付与で土曜・日曜・今日を区別
if ($weekDay === 0){
$class = 'sunday';
} elseif ($weekDay === 6){
$class = 'saturday';
}
if ($day == $today){
$class .= ' today';
}
echo "<td class='{$class}'>{$day}</td>";
if ($weekDay === 6) {
echo "</tr>";
if ($day !== $lastDay) echo "<tr>";
}
}
- 土曜で1行を閉じ、次の
<tr>へ - 最後の週で足りない分の空白は別途処理(↓)
カレンダー末尾の空白セル(余り分)
$endWeekDay = ($startWeekDay + $lastDay) % 7;
if ($endWeekDay !== 0) {
for ($i = $endWeekDay; $i < 7; $i++) {
echo "<td></td>";
}
echo "</tr>";
}
- 最後の行で、カレンダー右端に空白を埋める処理です
CSS見本
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
a:link{
text-decoration: none;
}
a:hover{
text-decoration: underline
}
header{
text-align: center;
}
main{
max-width: 700px;
margin: 0 auto;
}
footer{
background-color: #ddd;
padding: 2rem 0;
text-align: center;
margin-top: 4rem;
}
/* ページネーション */
.pagenation{
max-width: 700px;
border: 1px solid #ddd;
margin: 1rem auto;
border-radius: 50vh;
padding: 0.5rem;
}
/* カレンダー */
table {
border-collapse: collapse;
margin: 1em 0;
width: 100%;
}
th,
td {
border: 1px solid #999;
height: 70px;
width: calc(100% / 7);
text-align: center;
}
th {
background: #eee;
}
.sunday {
color: red;
}
.saturday {
color: blue;
}
.today {
background: #fdd;
font-weight: bold;
}
.today {
background: #ffff99;
}
.reserved {
background: #f88;
color: #fff;
font-weight: bold;
}
/* 予約フォーム */
.reserve_section{
border:1px solid #ddd;
border-radius: 5px;
padding: 1rem 4rem;
}
.reserve_section p{
margin-bottom: 1rem;
}
.reserve_section label{
display: block;
}
.reserve_section select{
width: 100%;
padding: 0.5rem 0;
}
.reserve_section input[type="text"],
.reserve_section input[type="email"]{
width: 100%;
padding: 0.5rem 1rem;
}
.reserve_section p:last-child{
text-align: center;
}
.reserve_section button{
padding: 0.5rem 5rem;
background-color: #f88;
border-radius: 5px;
border: none;
}
.reserve_section button:hover{
background-color: rgb(250, 86, 86);
}
翌月・前月に移動できるように
<?php
date_default_timezone_set('Asia/Tokyo');
// URLパラメータで年・月を取得(なければ今日)
$year = isset($_GET['year']) ? (int)$_GET['year'] : date('Y');
$month = isset($_GET['month']) ? (int)$_GET['month'] : date('n');
$today = date('Y-n-j'); // 今日の日付(例: 2025-9-2)
// 前月・翌月の計算
$prevMonth = $month - 1;
$prevYear = $year;
if ($prevMonth < 1) {
$prevMonth = 12;
$prevYear--;
}
$nextMonth = $month + 1;
$nextYear = $year;
if ($nextMonth > 12) {
$nextMonth = 1;
$nextYear++;
}
// 今月の最初と最後の日
$firstDay = mktime(0, 0, 0, $month, 1, $year);
$lastDay = date('t', $firstDay);
$startWeekDay = date('w', $firstDay);
// 曜日ラベル
$weekLabels = ['日', '月', '火', '水', '木', '金', '土'];
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title><?= "{$year}年{$month}月のカレンダー" ?></title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<h1><?= "{$year}年{$month}月のカレンダー" ?></h1>
<!-- ナビゲーション -->
<nav class="pagenation">
<a href="?year=<?= $prevYear ?>&month=<?= $prevMonth ?>">← 前月</a> |
<a href="?">今月</a> |
<a href="?year=<?= $nextYear ?>&month=<?= $nextMonth ?>">翌月 →</a>
</nav>
</header>
<main>
<table>
<tr>
<?php foreach ($weekLabels as $i => $label): ?>
<th class="<?= $i === 0 ? 'sunday' : ($i === 6 ? 'saturday' : '') ?>">
<?= $label ?>
</th>
<?php endforeach; ?>
</tr>
<tr>
<?php
// 前方の空セル
for ($i = 0; $i < $startWeekDay; $i++) {
echo "<td></td>";
}
// 日付を出力
for ($day = 1; $day <= $lastDay; $day++) {
$weekDay = ($startWeekDay + $day - 1) % 7;
$class = '';
$currentDate = "{$year}-{$month}-{$day}";
if ($weekDay === 0) $class = 'sunday';
elseif ($weekDay === 6) $class = 'saturday';
if ($currentDate === $today) $class .= ' today';
echo "<td class='{$class}'>{$day}</td>";
// 土曜日で改行
if ($weekDay === 6 && $day !== $lastDay) {
echo "</tr><tr>";
}
}
// 後方の空セル
$endWeekDay = ($startWeekDay + $lastDay) % 7;
if ($endWeekDay !== 0) {
for ($i = $endWeekDay; $i < 7; $i++) {
echo "<td></td>";
}
echo "</tr>";
}
?>
</table>
</main>
</body>
</html>
