カレンダー作成
下記画像のようなカレンダー作成をしました。
カレンダーデータの作成は下記サイトを参考にしました。
https://qiita.com/kim-shun/items/4233ffd064c1f24b89e3
日付をクリックすると、System.out.printで、クリックした日付が表示されます。
また年月表示横の「< >」を押すと、前月翌月のカレンダーに切り替わります。
Calendarクラスで表示する月の日数を取得しボタンを作成。
また1日の曜日を数値で取得し、全体表示に必要な行数を計算してGridLayoutを設定。その後作ボタンをパネルに追加して作成しています。
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月などでレイアウトが崩れる問題は解決していません。 レイアウト設定自体は問題なく行われているようなのですが、、、 別記事にてまとめることにします。