0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Java 日付を選択できるカレンダー作成

Last updated at Posted at 2025-08-07

カレンダー作成


下記画像のようなカレンダー作成をしました。

スクリーンショット 2025-08-07 150233.png


カレンダーデータの作成は下記サイトを参考にしました。

https://qiita.com/kim-shun/items/4233ffd064c1f24b89e3

日付をクリックすると、System.out.printで、クリックした日付が表示されます。

また年月表示横の「< >」を押すと、前月翌月のカレンダーに切り替わります。

Calendarクラスで表示する月の日数を取得しボタンを作成。

また1日の曜日を数値で取得し、全体表示に必要な行数を計算してGridLayoutを設定。その後作ボタンをパネルに追加して作成しています。


DateSelect.java
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

class DaySet {
	//Calendar cl = Calendar.getInstance();
	//int thisYear = cl.get(Calendar.YEAR); //今年
	//int thisMonth = cl.get(Calendar.MONTH); //今月(1月=0、2月=1であるため配列monthの添字に使用)
	String thisDate;
	String[] month = {"January","February","March","April","May","June",
			"July","August","September","October","November","December"};
	String[] week = {"Su","Mo","Tu","We","Th","Fr","Sa"};
	int firstDay = 1; //月の初日
	
	int firstDayWeek;
	int lastDay;
	
	DaySet(Calendar cl, int thisYear, int thisMonth){
		switch (thisMonth) {
		case -1:
			thisMonth = 11;
			break;
		case 12:
			thisMonth = 0;
		}
		thisDate = String.valueOf(thisYear) + "年" + String.valueOf(thisMonth + 1) + "月";
		
		//月の初日の曜日(日曜日=1、月曜日=2であるため-1をして配列weekの添字に使用)
		cl.set(thisYear,thisMonth,firstDay);
		firstDayWeek = cl.get(Calendar.DAY_OF_WEEK) - 1; 
		lastDay = cl.getActualMaximum(Calendar.DAY_OF_MONTH); //月の最終日			
	}
	
	String getThisDate() {
		return this.thisDate;
	}
	String[] getWeek() {
		return this.week;
	}
	int getFirstDay() {
		return this.firstDay;
	}
	int getFirstDayWeek() {
		return this.firstDayWeek;
	}
	int getLastDay() {
		return this.lastDay;
	}
}

class DateSelectPanel extends JPanel  implements ActionListener {
	JPanel panel = new JPanel();
	
	Calendar cl = Calendar.getInstance();;
	int thisYear = cl.get(Calendar.YEAR); //今年
	int thisMonth = cl.get(Calendar.MONTH); //今月(1月=0、2月=1であるため配列monthの添字に使用)


	DaySet daySet = new DaySet(cl, thisYear, thisMonth);
	String thisDate = daySet.getThisDate();
	String[] week;
	int firstDay = daySet.getFirstDay();
	int firstDayWeek;
	int lastDay;
	JButton dayButton[];
	JButton backMonthButton;
	JButton nextMonthButton;
	
	JLabel dateLabel;
	JPanel datePanel;
	
	DateSelectPanel() {
	getCalendarPanel();
	getDatePanel();
	
	setLayout(new BorderLayout());
	this.add(BorderLayout.NORTH, datePanel);
	this.add(BorderLayout.CENTER,panel);
	}
	
	@Override
	public void actionPerformed(ActionEvent e) {
		//<を押した時
		if (e.getSource() == backMonthButton) {
			this.monthBack();
			this.removeAll();
			getDatePanel();
			panel.removeAll();
			getCalendarPanel();
			this.add(BorderLayout.NORTH, datePanel);
			this.add(BorderLayout.CENTER,panel);
			this.updateUI();

		//>を押した時
		} else if (e.getSource() == nextMonthButton) {
			this.monthNext();
			this.removeAll();
			getDatePanel();
			panel.removeAll();
			getCalendarPanel();
			this.add(BorderLayout.NORTH, datePanel);
			this.add(BorderLayout.CENTER,panel);
			this.updateUI();
		}
		//日付を押した時
		for (int i = firstDay; i <= lastDay; i++) {
			if (e.getSource() == dayButton[i]) {
				System.out.println(i);
			}
		}
	}
	
	void monthBack() {
		thisMonth = cl.get(Calendar.MONTH)-1;
		if (thisMonth == -1) {
			thisYear = cl.get(Calendar.YEAR)-1;
		}
		daySet = new DaySet(cl, thisYear, thisMonth);
	}	
	
	void monthNext() {
		thisMonth = cl.get(Calendar.MONTH)+1;
		if (thisMonth == 12) {
			thisYear = cl.get(Calendar.YEAR)+1;
		}
		daySet = new DaySet(cl, thisYear, thisMonth);
	}
	
	JPanel getCalendarPanel() {
		week = daySet.getWeek();
		firstDayWeek = daySet.getFirstDayWeek();
		lastDay = daySet.getLastDay();
		dayButton = new JButton[lastDay+1];
			
		int count = 0;
		for (int i = 0; i < firstDayWeek; i++) {
			count++;
		}
		//初日の曜日と最終日によってレイアウトを変更
		//※2027年2月などでなぜかレイアウトが崩れる。
		//System.out.println("count: " + count);
		if ((count == 6 && lastDay == 30)  || count >= 5 && lastDay == 31) {
			panel.setLayout(new GridLayout(7,7));
		} else if (count == 0 && lastDay == 28){
			panel.setLayout(new GridLayout(5,7));
		} else{
			panel.setLayout(new GridLayout(6,7));
		}
		
		//カレンダーの曜日を出力
		for (int i = 0; i < week.length; i++) {
			panel.add(new JLabel(week[i]));
		}
		
		//初日が何曜日かに合わせて空白を出力
		for (int i = 0; i < firstDayWeek; i++) {
			panel.add(new JLabel(""));
		}
	
		
		//日付を出力
		for (int i = firstDay; i <= lastDay; i++) {
			dayButton[i] = new JButton(String.valueOf(i));
			dayButton[i].addActionListener(this);
			dayButton[i].setContentAreaFilled(false);
			panel.add(dayButton[i]);
		}
		
		return panel;
		
	}
	
	JPanel getDatePanel() {
		thisDate = daySet.getThisDate();
		datePanel = new JPanel();
		dateLabel = new JLabel(thisDate);
		backMonthButton = new JButton("<");
		backMonthButton.addActionListener(this);
		backMonthButton.setContentAreaFilled(false);
		backMonthButton.setBorderPainted(false);
		nextMonthButton = new JButton(">");
		nextMonthButton.addActionListener(this);
		nextMonthButton.setContentAreaFilled(false);
		nextMonthButton.setBorderPainted(false);
		datePanel.setLayout(new GridLayout(1,7));
		datePanel.add(Box.createRigidArea(new Dimension(10,10)));
		datePanel.add(backMonthButton);
		datePanel.add(Box.createRigidArea(new Dimension(10,10)));
		datePanel.add(dateLabel);
		datePanel.add(Box.createRigidArea(new Dimension(10,10)));
		datePanel.add(nextMonthButton);
		datePanel.add(Box.createRigidArea(new Dimension(10,10)));
		return datePanel;
	}
}


class DatetoString {
	static Date dts(String s) {
		String res = s.replaceAll("年|月", "-");
		SimpleDateFormat ds1 = new SimpleDateFormat("yyyy-MM-dd");
		try {
			return ds1.parse(res);
		} catch (ParseException e) {
			e.printStackTrace();
		}
		return null;
	}
}

class DateSelect extends JFrame{
	public static void main(String[] args) {
		JFrame mainFrame = new JFrame();
        mainFrame.setTitle("カレンダー");
		mainFrame.setSize(600, 500);
        mainFrame.setLocationRelativeTo(null);
        mainFrame.setDefaultCloseOperation(EXIT_ON_CLOSE);
        mainFrame.setResizable(false);
		mainFrame.add(new DateSelectPanel());
        mainFrame.setVisible(true);
    }
}

レイアウトの設定について

表示する月の日数と1日の曜日から全体の行数を計算していますが(列数は曜日の数なので7で固定)、書き方があまりすっきりしていない。加えて2027年2月など、特定年の2月ではなぜがレイアウトが崩れるバグもあり、改善中です。

その他

chatGPTくんにチェックしてもらったところ、下記のようなご指摘を賜った。
1. 月切り替えが正しく動作しない
問題: monthBack() や monthNext() の処理で thisMonth を更新しているように見えて、Calendar のインスタンス (cl) の MONTH 値が変更されていません。
thisMonth = cl.get(Calendar.MONTH)-1;  // ← clの状態は変わってない

解決策:

void monthBack() {
    cl.add(Calendar.MONTH, -1);
    thisMonth = cl.get(Calendar.MONTH);
    thisYear = cl.get(Calendar.YEAR);
    daySet = new DaySet(cl, thisYear, thisMonth);
}	

void monthNext() {
    cl.add(Calendar.MONTH, 1);
    thisMonth = cl.get(Calendar.MONTH);
    thisYear = cl.get(Calendar.YEAR);
    daySet = new DaySet(cl, thisYear, thisMonth);
}
2 . ボタン配列のインデックスがずれている
問題: 日付を押したときの処理で、dayButton[i] を使用していますが、配列サイズは lastDay+1 で、インデックス 0 は未使用。
for (int i = firstDay; i <= lastDay; i++) {
	if (e.getSource() == dayButton[i]) {
		System.out.println(i);
	}
}

解決策:
0番目を使わずに 1~lastDay を使うのは正しい前提ですが、管理しづらいので、リストか ArrayList を使うと安全です。
あるいは、dayButton[i-1] にして配列の 0 始まりを生かす。

3 . 「2027年2月」などでレイアウト崩れ
問題: グリッドの行数を条件で分岐させていますが、これが不安定。
if ((count == 6 && lastDay == 30)  || count >= 5 && lastDay == 31) {
	panel.setLayout(new GridLayout(7,7));
}

解決策:
カレンダーの表示枠を「曜日行 + 最大6週」で固定するのがシンプルで安全です。

panel.setLayout(new GridLayout(7, 7));  // 7 rows: 1 for weekdays + 6 weeks

これに対して下記のように返答
1→class DaySet でclをセットしなおしているから問題ないのでは?
2→確かに
3→固定すると表示が崩れますわ

chatGPTくんの回答

ご指摘ありがとうございます!以下、あなたのコメントに対して丁寧に補足&整理して回答します。
✅ 1. cl を DaySet 側で set() しているから OK → その通りです
```java cl.set(thisYear, thisMonth, firstDay); ``` ↑ DaySet コンストラクタで Calendar の状態を毎回明示的に更新しているので、thisMonth = cl.get(Calendar.MONTH) ± 1 であっても成立する、というのは 確かにその通りです。

ただし以下の点だけ注意しておくとさらに安定します:

🔍 補足的アドバイス:
Calendar の状態を直接書き換えているため、thisMonth や thisYear を cl に依存しすぎない設計の方が読みやすく、バグも防げます。

cl.set(...) を明示しているので問題ない

ただし、thisMonth の境界処理(-1, 12)を DaySet で補正しても、cl 側にその変化は即時反映されない可能性あり → 今の設計では DaySet 側で補正しているからOK

結論:
今の構成では 正しく動作しますが、読みやすさや将来のメンテ性を考慮すると cl.add(Calendar.MONTH, ±1) を使ったほうが安全ではある、という提案でした。

✅ 2. dayButton[i] のインデックスずれ → ご納得ありがとうございます!
ここは管理しづらいので、いっそこうするのもアリです:
List<JButton> dayButtons = new ArrayList<>();
// ...
for (int i = firstDay; i <= lastDay; i++) {
    JButton btn = new JButton(String.valueOf(i));
    btn.addActionListener(this);
    dayButtons.add(btn);
    panel.add(btn);
}

→ こうしておけば、インデックスと日付の数値を混同しなくて済みます。

🚫 3. 表示崩れ:GridLayout(7,7) 固定のほうが崩れる → 了解です!
これはあなたの言う通りです。 月によっては行数が 5週、6週、場合によっては 4週(2月+月曜始まりなど)もありえるため、

🔍 最適な方法は以下のようなロジック:

int totalCells = firstDayWeek + lastDay;
int rowCount = (int)Math.ceil(totalCells / 7.0); // ← 必要な週数を算出
panel.setLayout(new GridLayout(rowCount + 1, 7)); // +1 は曜日行

つまり:

日付数+開始曜日の空白から行数を自動計算

必要な行数だけ GridLayout を調整

曜日行を含めて +1


chatGPTくんにお礼を言うと、補足アドバイスまでくれました。ありがたいですね。
ですが、このアドバイス通りに修正しても、2027年2月などでレイアウトが崩れる問題は解決していません。 レイアウト設定自体は問題なく行われているようなのですが、、、 別記事にてまとめることにします。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?