Musings about Java and Swing

Swing JCalendar Component

I saw this question on Stack Overflow, and I started thinking. Years ago, when I was learning Swing, I created a Swing JCalendar component. I thought I’d posted the code as an answer to a Stack Overflow question, but I couldn’t find my JCalendar component.

I decided to write the code for the JCalendar component again. I figured I’d improved my coding skills since I wrote the first version, and I could add features to the JCalendar component that I didn’t have in the first version.

Here is the JCalendar component in action.

JCalendar Tester 1

I built a simple Swing GUI with a JTextField and a JButton to test my JCalendar component.

After left clicking the JButton, the following dialog pops up.

JCalendar Dialog 1

The JCalendarDialog is 229 x 326 pixels on my Windows Vista system. You can make the dialog smaller by changing the font size of the day numbers and the day of the week letters.

The top part of the JCalendarDialog is the JPanel created by the JCalendar class. The rest of the dialog is created by the JCalendarDialog class.

There are four arrow buttons on top of the calendar. The first button on the left moves the calendar back one year. The second button on the left moves the calendar back one month. The third button moves the calendar forward one month. The fourth button moves the calendar forward one year.

The month and days are displayed in JLabels inside of JPanels. The locale can be set to get the month and day of week names in different languages. I used JPanels, rather than JButtons, because I liked the appearance of the JPanels better. I thought it looked more like a calendar.

The slashes through some of the dates are exclusions. You can exclude certain days of the week, and you can exclude one or more date ranges. In the picture above, we’ve excluded Saturday, Sunday, and the 16th of February (President’s Day in the United States).

The day with the yellow background is the current date. The user selects a date by clicking on a non-excluded date. The background of the selected date turns green, as shown in the next picture.

JCalendar Dialog 2

Finally, the user clicks the OK button, and the date field on the original form is filled in.

JCalendar Tester 2

Now that you’ve seen the JCalendar in action, let’s look at the code. I used Java 7 to create the JCalendar. It might even compile on Java 6 and Java 5.

Here’s the JCalendar class.

package com.ggl.jcalendar;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.Border;

/**
 * <p>
 * This class creates a Swing <code>JPanel</code> containing one month of a
 * calendar. The <code>JPanel</code> can be used in a Swing application, or in
 * the <code>JCalendarDialog</code> that's included in this package.
 * </p>
 * 
 * <p>
 * There are four arrow buttons on top of the calendar. The first button on the
 * left moves the calendar back one year. The second button on the left moves
 * the calendar back one month. The third button moves the calendar forward one
 * month. The fourth button moves the calendar forward one year.
 * </p>
 * 
 * <p>
 * The current date has a yellow background. The user left clicks on a date to
 * select the date. A date must be selected before the user presses the OK
 * button. Once the user selects a date, the selected date has a green
 * background.
 * </p>
 * 
 * <p>
 * Exclusions may be added to the JCalendar so that the user may not select
 * certain dates. There are two methods to add exclusions to the JCalendar. The
 * first method, <code>setExclusionDaysOfWeek</code>, allows you to exclude
 * certain days of the week. The second method,
 * <code>addExclusionDateRange</code> allows you to add one or more date ranges
 * to exclude. The date range can be one day, or several days.
 * </p>
 * 
 * <p>
 * Here's an example of how to use a <code>JCalendar</code>:
 * 
 * <pre>
 * <code>     JPanel mainPanel = new JPanel();    
 *      mainPanel.setLayout(new BorderLayout());
 * 
 *      jcalendar = new JCalendar();
 *      jcalendar.setExclusionDaysOfWeek(Calendar.SATURDAY, Calendar.SUNDAY);
 *      jcalendar.addExclusionDateRange(new ExclusionDateRange("M/d/yy",
 *              "2/16/15", "2/16/15"));
 *      jcalendar.setCalendar(calendar);
 *      jcalendar.setDateFormat(simpleDateFormat);
 *      jcalendar.setLocale(locale);
 *      jcalendar.setStartOfWeek(Calendar.SUNDAY);
 *      jcalendar.createPanel();
 * 	
 *      mainPanel.add(jcalendar.getPanel(), BorderLayout.CENTER);
 * </code>
 * </pre>
 * 
 * @author Gilbert G. Le Blanc
 * 
 * @version 1.0 - 13 February 2015
 * 
 * @see javax.swing.JPanel
 */
public class JCalendar {

	private static final int DAYS_IN_WEEK = 7;

	private int startOfWeek;
	private int[] exclusionDaysOfWeek;

	private Border blackLineBorder;

	private Calendar calendar;
	private Calendar selectedDate;

	private DayPanel[] dayPanel;

	private JLabel monthLabel;

	private JPanel panel;

	private List<ExclusionDateRange> exclusionDateRanges;

	private Locale locale;

	private SimpleDateFormat dateFormat;

	private String dateString;

	/**
	 * This creates an instance of JCalendar with default values.
	 */
	public JCalendar() {
		this.calendar = Calendar.getInstance();
		this.locale = Locale.getDefault();
		this.startOfWeek = Calendar.SUNDAY;
		this.dateFormat = new SimpleDateFormat("MMM d, yyyy");
		this.exclusionDateRanges = new ArrayList<ExclusionDateRange>();
		this.exclusionDaysOfWeek = new int[0];

		calendar.set(Calendar.HOUR_OF_DAY, 0);
		calendar.set(Calendar.MINUTE, 0);
		calendar.set(Calendar.SECOND, 0);
		calendar.set(Calendar.MILLISECOND, 0);

		this.dateString = "";
		this.selectedDate = null;
	}

	/**
	 * This optional method sets the day that starts the week. The default is
	 * <code>Calendar.SUNDAY</code>.
	 * 
	 * @param startOfWeek
	 *            - Calendar day of week value indicating the start of the week.
	 *            For example, <code>Calendar.FRIDAY</code>.
	 */
	public void setStartOfWeek(int startOfWeek) {
		this.startOfWeek = startOfWeek;
	}

	/**
	 * This optional method sets the display month of the JCalendar using a
	 * <code>Calendar</code>. The default is the current month.
	 * 
	 * @param calendar
	 *            - <code>Calendar</code> instance that sets the display month
	 *            of the JCalendar.
	 */
	public void setCalendar(Calendar calendar) {
		this.calendar = (Calendar) calendar.clone();

		this.calendar.set(Calendar.HOUR_OF_DAY, 0);
		this.calendar.set(Calendar.MINUTE, 0);
		this.calendar.set(Calendar.SECOND, 0);
		this.calendar.set(Calendar.MILLISECOND, 0);
	}

	/**
	 * This optional method sets the <code>Locale</code> for the JCalendar. This
	 * allows for localized month and day of week names. The default is your
	 * default <code>Locale</code>.
	 * 
	 * @param locale
	 *            - The <code>Locale</code> for the JCalendar.
	 */
	public void setLocale(Locale locale) {
		this.locale = locale;
	}

	/**
	 * This optional method sets the <code>SimpleDateFormat</code> for the
	 * selected date String returned by the JCalendar. The default is
	 * <code>"MMM d, yyyy"</code>.
	 * 
	 * @param simpleDateFormat
	 *            - A <code>String</code> with the desired date format of the
	 *            selected date.
	 * 
	 * @see java.text.SimpleDateFormat
	 */
	public void setDateFormat(String simpleDateFormat) {
		this.dateFormat = new SimpleDateFormat(simpleDateFormat);
	}

	/**
	 * This optional method allows you to add date ranges to exclude from the
	 * JCalendar selection.
	 * 
	 * @param exclusionDateRange
	 *            - An <code>ExclusionDateRange</code> to exclude the range of
	 *            dates from the JCalendar selection.
	 */
	public void addExclusionDateRange(ExclusionDateRange exclusionDateRange) {
		this.exclusionDateRanges.add(exclusionDateRange);
	}

	/**
	 * This optional method allows you to add a <code>List</code> of date ranges
	 * to exclude from the JCalendar selection.
	 * 
	 * @param exclusionDateRanges
	 *            - A <code>List</code> of <code>ExclusiojnDateRange</code> to
	 *            exclude the ranges of dates from the JCalendar selection.
	 */
	public void addExclusionDateRanges(
			List<ExclusionDateRange> exclusionDateRanges) {
		this.exclusionDateRanges.addAll(exclusionDateRanges);
	}

	/**
	 * This optional method allows you to select one or more days of the week to
	 * exclude from the JCalendar selection.
	 * 
	 * @param dayOfWeek
	 *            - One or more <code>Calendar</code> days of the week to
	 *            exclude from the JCalendar selection. For example,
	 *            <code>Calendar.SATURDAY, Calendar.SUNDAY</code>.
	 */
	public void setExclusionDaysOfWeek(int... dayOfWeek) {
		exclusionDaysOfWeek = new int[dayOfWeek.length];
		for (int i = 0; i < dayOfWeek.length; i++) {
			exclusionDaysOfWeek[i] = dayOfWeek[i];
		}
	}

	/**
	 * This mandatory method creates the JCalendar <code>JPanel</code> after all
	 * of the JCalendar values are set or defaulted. Failure to execute this
	 * method will result in a <code>NullPointerException</code> .
	 */
	public void createPanel() {
		blackLineBorder = BorderFactory.createLineBorder(Color.BLACK);

		panel = new JPanel();
		panel.setBorder(blackLineBorder);
		panel.setLayout(new BorderLayout());

		JPanel topPanel = new JPanel();
		topPanel.setLayout(new BorderLayout());

		JPanel buttonPanel = new JPanel();
		buttonPanel.setBorder(blackLineBorder);

		Font smallFont = buttonPanel.getFont().deriveFont(10.0F);

		JButton yearBackButton = new JButton("<<");
		yearBackButton.setFont(smallFont);
		yearBackButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
				calendar.add(Calendar.YEAR, -1);
				updatePartControl();
			}
		});
		buttonPanel.add(yearBackButton);

		JButton monthBackButton = new JButton("<");
		monthBackButton.setFont(smallFont);
		monthBackButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
				calendar.add(Calendar.MONTH, -1);
				updatePartControl();
			}
		});
		buttonPanel.add(monthBackButton);

		JButton monthForwardButton = new JButton(">");
		monthForwardButton.setFont(smallFont);
		monthForwardButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
				calendar.add(Calendar.MONTH, 1);
				updatePartControl();
			}
		});
		buttonPanel.add(monthForwardButton);

		JButton yearForwardButton = new JButton(">>");
		yearForwardButton.setFont(smallFont);
		yearForwardButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
				calendar.add(Calendar.YEAR, 1);
				updatePartControl();
			}
		});
		buttonPanel.add(yearForwardButton);

		monthBackButton.setPreferredSize(yearBackButton.getPreferredSize());
		monthForwardButton.setPreferredSize(yearForwardButton
				.getPreferredSize());

		topPanel.add(buttonPanel, BorderLayout.NORTH);

		JPanel monthPanel = new JPanel();
		monthPanel.setBackground(Color.WHITE);
		monthPanel.setBorder(blackLineBorder);

		monthLabel = new JLabel(getDisplayMonthYear());
		monthPanel.add(monthLabel);

		topPanel.add(monthPanel, BorderLayout.SOUTH);

		panel.add(topPanel, BorderLayout.NORTH);
		panel.add(createDayGrid(), BorderLayout.CENTER);
	}

	private JPanel createDayGrid() {
		JPanel panel = new JPanel();
		panel.setBorder(blackLineBorder);
		panel.setLayout(new GridLayout(0, DAYS_IN_WEEK));

		String[] daysOfWeek = getDaysOfWeek();
		for (int i = 0; i < daysOfWeek.length; i++) {
			JPanel weekdayPanel = new JPanel();
			weekdayPanel.setBackground(Color.WHITE);
			weekdayPanel.setBorder(blackLineBorder);

			String s = daysOfWeek[i].substring(0, 1);
			JLabel weekdayLabel = new JLabel(s);
			weekdayPanel.add(weekdayLabel);

			panel.add(weekdayPanel);
		}

		JCalendarDay[] days = getDays();
		dayPanel = new DayPanel[days.length];
		for (int i = 0; i < days.length; i++) {
			dayPanel[i] = new DayPanel();
			dayPanel[i].setJCalendarDay(days[i]);
			dayPanel[i].createPartControl();
			panel.add(dayPanel[i]);
		}

		return panel;
	}

	private void updatePartControl() {
		monthLabel.setText(getDisplayMonthYear());

		JCalendarDay[] days = getDays();
		for (int i = 0; i < days.length; i++) {
			dayPanel[i].setJCalendarDay(days[i]);
			dayPanel[i].updatePartControl();
		}
	}

	private String[] getDaysOfWeek() {
		String[] daysOfWeek = new String[DAYS_IN_WEEK];
		Calendar temp = (Calendar) calendar.clone();

		for (int i = 0; i < daysOfWeek.length; i++) {
			temp.set(Calendar.DAY_OF_WEEK, (i + startOfWeek) % DAYS_IN_WEEK);
			String s = temp.getDisplayName(Calendar.DAY_OF_WEEK,
					Calendar.SHORT_FORMAT, locale);
			daysOfWeek[i] = s;
		}

		return daysOfWeek;
	}

	private JCalendarDay[] getDays() {
		JCalendarDay[] days = new JCalendarDay[DAYS_IN_WEEK * 6];
		Calendar today = Calendar.getInstance(locale);
		Calendar temp = (Calendar) calendar.clone();
		temp.set(Calendar.DAY_OF_MONTH, 1);
		getFirstDate(temp);

		int currentMonth = calendar.get(Calendar.MONTH);

		for (int i = 0; i < days.length; i++) {
			int month = temp.get(Calendar.MONTH);
			if (month == currentMonth) {
				boolean isExcluded = isExcludedDayOfWeek(temp)
						|| isExcludedDateRange(temp);
				Color color = Color.WHITE;
				if (isToday(temp, today)) {
					color = Color.YELLOW;
				}
				if (isToday(temp, selectedDate)) {
					color = Color.GREEN;
				}
				int day = temp.get(Calendar.DAY_OF_MONTH);
				days[i] = new JCalendarDay(day, color, isExcluded);
			} else {
				days[i] = new JCalendarDay(0, Color.WHITE, false);
			}

			temp.add(Calendar.DAY_OF_MONTH, 1);
		}

		return days;
	}

	private boolean isExcludedDayOfWeek(Calendar c) {
		int dayOfWeek = c.get(Calendar.DAY_OF_WEEK);
		for (int i = 0; i < exclusionDaysOfWeek.length; i++) {
			if (dayOfWeek == exclusionDaysOfWeek[i]) {
				return true;
			}
		}
		return false;
	}

	private boolean isExcludedDateRange(Calendar c) {
		for (ExclusionDateRange range : exclusionDateRanges) {
			if (c.after(range.getFromCalendar())
					&& c.before(range.getToCalendar())) {
				return true;
			}
		}
		return false;
	}

	private boolean isToday(Calendar c1, Calendar c2) {
		if ((c1 == null) || (c2 == null)) {
			return false;
		}

		int year1 = c1.get(Calendar.YEAR);
		int month1 = c1.get(Calendar.MONTH);
		int day1 = c1.get(Calendar.DAY_OF_MONTH);

		int year2 = c2.get(Calendar.YEAR);
		int month2 = c2.get(Calendar.MONTH);
		int day2 = c2.get(Calendar.DAY_OF_MONTH);

		return (day1 == day2) && (month1 == month2) && (year1 == year2);
	}

	/**
	 * This method gets the date of the first day of the calendar week. It could
	 * be the first day of the month, but more likely, it's a day in the
	 * previous month.
	 * 
	 * @param calendar
	 *            - Working <code>Calendar</code> instance that this method can
	 *            manipulate to set the first day of the calendar week.
	 */
	private void getFirstDate(Calendar calendar) {
		int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) % DAYS_IN_WEEK;
		int amount = 0;
		for (int i = 0; i < DAYS_IN_WEEK; i++) {
			int j = (i + startOfWeek) % DAYS_IN_WEEK;
			if (j == dayOfWeek) {
				break;
			}
			amount--;
		}
		calendar.add(Calendar.DAY_OF_MONTH, amount);
	}

	private String getDisplayMonthYear() {
		int year = calendar.get(Calendar.YEAR);
		String s = calendar.getDisplayName(Calendar.MONTH,
				Calendar.LONG_FORMAT, locale);
		return s + " " + year;
	}

	/**
	 * This method returns the <code>JPanel</code> with the current month
	 * calendar.
	 * 
	 * @return The current month calendar <code>JPanel</code>.
	 */
	public JPanel getPanel() {
		return panel;
	}

	/**
	 * This method returns the selected date in the
	 * <code>SimpleDateFormat</code> of the <code>String</code> passed as input
	 * through the constructor.
	 * 
	 * @return The selected date formatted using the
	 *         <code>SimpleDateFormat</code> input string.
	 */
	public String getFormattedSelectedDate() {
		return dateString;
	}

	/**
	 * This method returns the <code>Calendar</code> instance of the selected
	 * date.
	 * 
	 * @return The <code>Calendar</code> instance of the selected date
	 */
	public Calendar getSelectedDate() {
		return selectedDate;
	}

	private class DayMouseListener extends MouseAdapter {

		@Override
		public void mousePressed(MouseEvent event) {
			DayPanel panel = (DayPanel) event.getSource();
			if (!panel.isExcluded()) {
				JLabel label = panel.getDayLabel();
				String s = label.getText().trim();

				if (!s.equals("")) {
					getSelectedDate(s);
				}
			}
		}

		private void getSelectedDate(String s) {
			selectedDate = Calendar.getInstance();
			selectedDate.set(calendar.get(Calendar.YEAR),
					calendar.get(Calendar.MONTH), Integer.valueOf(s), 0, 0, 0);
			dateString = dateFormat.format(selectedDate.getTime());
			updatePartControl();
		}

	}

	private class DayPanel extends JPanel {

		private static final long serialVersionUID = -2980436029289287026L;

		private JCalendarDay jcalendarDay;

		private JLabel dayLabel;

		public void setJCalendarDay(JCalendarDay jcalendarDay) {
			this.jcalendarDay = jcalendarDay;
		}

		public void createPartControl() {
			addMouseListener(new DayMouseListener());
			setBackground(jcalendarDay.getColor());
			setBorder(blackLineBorder);

			dayLabel = new JLabel(getDay());
			dayLabel.setForeground(Color.BLUE);
			add(dayLabel);
		}

		public void updatePartControl() {
			setBackground(jcalendarDay.getColor());
			dayLabel.setText(getDay());
			repaint();
		}

		private String getDay() {
			String s = " ";
			if (jcalendarDay.getDay() > 0) {
				s = Integer.toString(jcalendarDay.getDay());
			}
			return s;
		}

		@Override
		protected void paintComponent(Graphics g) {
			super.paintComponent(g);

			if (jcalendarDay.isExcluded()) {
				g.setColor(Color.DARK_GRAY);
				g.drawLine(getWidth(), 0, 0, getHeight());
			}
		}

		public boolean isExcluded() {
			return jcalendarDay.isExcluded();
		}

		public JLabel getDayLabel() {
			return dayLabel;
		}

	}

	private class JCalendarDay {

		private final boolean isExcluded;

		private final int day;

		private final Color color;

		public JCalendarDay(int day, Color color, boolean isExcluded) {
			this.day = day;
			this.color = color;
			this.isExcluded = isExcluded;
		}

		public int getDay() {
			return day;
		}

		public Color getColor() {
			return color;
		}

		public boolean isExcluded() {
			return isExcluded;
		}

	}

}

This class is unusual for me because the 3 inner classes are private classes. I wanted the javadoc to show the public JCalendar methods only. Since the inner classes exist to facilitate the construction and operation of the JCalendar JPanel, I made them private.

I have one constructor that creates a default JCalendar. I have several add and set methods that allow the programmer to customize the JCalendar. The disadvantage to setting values this way is that the programmer has to remember to call the createPanel method to actually create the JCalendar JPanel. The advantage to setting values this way is that there can be many values to set. The advantages outweighed the disadvantages.

The JCalendar private methods are straightforward, except for the getFirstDate method, which has a javadoc description. This method gets the date of the first day of the week, so we can construct the day portion of the JCalendar. Since the calendar can start on any day of the week, this was a tricky method to get right.

Here’s the icon I used. You can pick a different PNG icon if you want.

JCalendar Icon

Next, let’s take a look at the JCalendarDialog class. This class takes the JPanel created by the JCalendar class and puts it in a JDialog.

package com.ggl.jcalendar;

import java.awt.BorderLayout;
import java.awt.Dialog.ModalityType;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;

/**
 * <p>
 * This class creates a <code>JDialog</code> using the JCalendar
 * <code>JPanel</code>. This is the most common usage for a JCalendar.
 * </p>
 * 
 * <p>
 * Generally, the JCalendarDialog would be executed as a part of a
 * <code>JButton</code> action listener. Something like this:
 * </p>
 * 
 * <pre>
 *  <code>private class CalendarActionListener implements ActionListener {
 * 
 *      private JFrame frame;
 * 
 *      private JTextField textField;
 * 
 *      public CalendarActionListener(JFrame frame, JTextField textField) {
 *          this.frame = frame;
 *          this.textField = textField;
 *      }
 * 
 *      &#64Override
 *      public void actionPerformed(ActionEvent event) {
 *          JCalendarDialog dialog = new JCalendarDialog(frame);
 *          dialog.setLocale(Locale.ENGLISH);
 *          dialog.createDialog();
 *          if (dialog.getReturnCode() == JCalendarDialog.OK_PRESSED) {
 *              textField.setText(dialog.getDateString());
 *          }
 *      }
 * 
 *  }
 *  </code>
 * </pre>
 * 
 * @author Gilbert G. Le Blanc
 * @version 1.0 - 13 February 2015
 * 
 * @see java.awt.event.ActionListener
 * @see javax.swing.JButton
 * @see javax.swing.JDialog
 * 
 */
public class JCalendarDialog {

	public static final int OK_PRESSED = 1;
	public static final int CANCEL_PRESSED = 2;

	private int returnCode;
	private int startOfWeek;
	private int[] exclusionDaysOfWeek;

	private Calendar calendar;
	private Calendar selectedDate;

	private JCalendar jcalendar;

	private JDialog dialog;

	private JFrame frame;

	private List<ExclusionDateRange> exclusionDateRanges;

	private Locale locale;

	private String dateString;
	private String dialogTitle;
	private String simpleDateFormat;

	/**
	 * This creates an instance of the JCalendarDialog for the
	 * <code>JFrame</code> with the calendar selection button, and sets the
	 * default values.
	 * 
	 * @param frame
	 *            - A <code>JFrame</code> instance.
	 */
	public JCalendarDialog(JFrame frame) {
		this.frame = frame;
		this.locale = Locale.getDefault();
		this.calendar = Calendar.getInstance();
		this.dialogTitle = "Date Selector";
		this.simpleDateFormat = "MMM d, yyyy";
		this.startOfWeek = Calendar.SUNDAY;
		this.exclusionDaysOfWeek = new int[0];
		this.exclusionDateRanges = new ArrayList<ExclusionDateRange>();
	}

	/**
	 * This optional method sets the day that starts the week. The default is
	 * <code>Calendar.SUNDAY</code>.
	 * 
	 * @param startOfWeek
	 *            - Calendar day of week value indicating the start of the week.
	 *            For example, <code>Calendar.FRIDAY</code>.
	 */
	public void setStartOfWeek(int startOfWeek) {
		this.startOfWeek = startOfWeek;
	}

	/**
	 * This optional method sets the display month of the JCalendarDialog using
	 * a <code>Calendar</code>. The default is the current month.
	 * 
	 * @param calendar
	 *            - <code>Calendar</code> instance that sets the display month
	 *            of the JCalendarDialog.
	 */
	public void setCalendar(Calendar calendar) {
		this.calendar = (Calendar) calendar.clone();
	}

	/**
	 * This optional method sets the title of the JCalendarDialog to correspond
	 * to the <code>JLabel</code> text on the selected date field.
	 * 
	 * @param dialogTitle
	 *            - A <code>String</code> with the JCalendar dialog title.
	 */
	public void setDialogTitle(String dialogTitle) {
		this.dialogTitle = dialogTitle;
	}

	/**
	 * This optional method sets the <code>Locale</code> for the
	 * JCalendarDialog. This allows for localized month and day of week names.
	 * The default is your default <code>Locale</code>.
	 * 
	 * @param locale
	 *            - The <code>Locale</code> for the JCalendarDialog.
	 */
	public void setLocale(Locale locale) {
		this.locale = locale;
	}

	/**
	 * This optional method sets the <code>SimpleDateFormat</code> for the
	 * selected date String returned by the JCalendarDialog. The default is
	 * <code>"MMM d, yyyy"</code>.
	 * 
	 * @param simpleDateFormat
	 *            - A <code>String</code> with the desired date format of the
	 *            selected date.
	 * 
	 * @see java.text.SimpleDateFormat
	 */
	public void setSimpleDateFormat(String simpleDateFormat) {
		this.simpleDateFormat = simpleDateFormat;
	}

	/**
	 * This optional method allows you to add date ranges to exclude from the
	 * JCalendar selection.
	 * 
	 * @param exclusionDateRange
	 *            - An <code>ExclusionDateRange</code> to exclude the range of
	 *            dates from the JCalendar selection.
	 */
	public void addExclusionDateRange(ExclusionDateRange exclusionDateRange) {
		this.exclusionDateRanges.add(exclusionDateRange);
	}

	/**
	 * This optional method allows you to add a <code>List</code> of date ranges
	 * to exclude from the JCalendarDialog selection.
	 * 
	 * @param exclusionDateRanges
	 *            - A <code>List</code> of <code>ExclusiojnDateRange</code> to
	 *            exclude the ranges of dates from the JCalendarDialog
	 *            selection.
	 */
	public void addExclusionDateRanges(
			List<ExclusionDateRange> exclusionDateRanges) {
		this.exclusionDateRanges.addAll(exclusionDateRanges);
	}

	/**
	 * This optional method allows you to select one or more days of the week to
	 * exclude from the JCalendarDialog selection.
	 * 
	 * @param dayOfWeek
	 *            - One or more <code>Calendar</code> days of the week to
	 *            exclude from the JCalendarDialog selection. For example,
	 *            <code>Calendar.SATURDAY, Calendar.SUNDAY</code>.
	 */
	public void setExclusionDaysOfWeek(int... dayOfWeek) {
		exclusionDaysOfWeek = new int[dayOfWeek.length];
		for (int i = 0; i < dayOfWeek.length; i++) {
			exclusionDaysOfWeek[i] = dayOfWeek[i];
		}
	}

	/**
	 * This mandatory method creates the JCalendarDialog <code>JDialog</code>
	 * after all of the JCalendarDialog values are set or defaulted. Failure to
	 * execute this method will result in a <code>NullPointerException</code> .
	 */
	public void createDialog() {
		dialog = new JDialog(frame);
		dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
		dialog.setTitle(dialogTitle);

		JPanel mainPanel = new JPanel();
		mainPanel.setLayout(new BorderLayout());

		jcalendar = new JCalendar();
		jcalendar.setExclusionDaysOfWeek(exclusionDaysOfWeek);
		jcalendar.addExclusionDateRanges(exclusionDateRanges);
		jcalendar.setCalendar(calendar);
		jcalendar.setDateFormat(simpleDateFormat);
		jcalendar.setLocale(locale);
		jcalendar.setStartOfWeek(startOfWeek);
		jcalendar.createPanel();

		mainPanel.add(jcalendar.getPanel(), BorderLayout.CENTER);

		JPanel buttonPanel = new JPanel();
		buttonPanel.setLayout(new FlowLayout());

		JButton okButton = new JButton("OK");
		okButton.addActionListener(new OKButtonActionListener());
		buttonPanel.add(okButton);

		JButton cancelButton = new JButton("Cancel");
		cancelButton.addActionListener(new CancelButtonActionListener());
		buttonPanel.add(cancelButton);

		okButton.setPreferredSize(cancelButton.getPreferredSize());

		mainPanel.add(buttonPanel, BorderLayout.SOUTH);

		dialog.add(mainPanel);

		dialog.pack();
		dialog.setLocationRelativeTo(frame);
		dialog.setModalityType(ModalityType.APPLICATION_MODAL);
		dialog.setVisible(true);
	}

	/**
	 * This method returns the return code of the JDialog. This allows the
	 * creator of the JCalendarDialog to determine whether or not a date was
	 * selected.
	 * 
	 * @return OK_PRESSED or CANCEL_PRESSED, depending on which button was
	 *         clicked.
	 */
	public int getReturnCode() {
		return returnCode;
	}

	/**
	 * This method returns the selected date in the
	 * <code>SimpleDateFormat</code> of the <code>String</code> passed as input
	 * through the constructor.
	 * 
	 * @return The selected date formatted using the
	 *         <code>SimpleDateFormat</code> input string.
	 */
	public String getFormattedSelectedDate() {
		return dateString;
	}

	/**
	 * This method returns the <code>Calendar</code> instance of the selected
	 * date.
	 * 
	 * @return The <code>Calendar</code> instance of the selected date
	 */
	public Calendar getSelectedDate() {
		return selectedDate;
	}

	private class CancelButtonActionListener implements ActionListener {

		@Override
		public void actionPerformed(ActionEvent event) {
			returnCode = CANCEL_PRESSED;
			dialog.setVisible(false);
		}

	}

	private class OKButtonActionListener implements ActionListener {

		@Override
		public void actionPerformed(ActionEvent event) {
			String s = jcalendar.getFormattedSelectedDate();
			if (!s.equals("")) {
				dateString = s;
				selectedDate = jcalendar.getSelectedDate();
				returnCode = OK_PRESSED;
				dialog.setVisible(false);
			}
		}

	}

}

There are 2 private inner classes to process the OK button press and the Cancel button press.

Again, I have one constructor that creates a default JCalendarDialog. I have several add and set methods that allow the programmer to customize the JCalendarDialog. The programmer has to remember to call the createDialog method after the add and set methods.

Next, let’s take a look at the JCalendarTester class. This is a straightforward Swing class to create a text field to hold the selected date and a button to bring up the JCalendar dialog.

package com.ggl.jcalendar;

import java.awt.BorderLayout;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.util.Calendar;
import java.util.Locale;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

public class JCalendarTester implements Runnable {

	private Image image;

	private JFrame frame;

	private JButton calendarIcon;

	private JTextField dateField;

	public JCalendarTester() {
		this.image = getImage();
	}

	@Override
	public void run() {
		frame = new JFrame("JCalendar Tester");
		frame.setIconImage(image);
		frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
		frame.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent event) {
				exitProcedure();
			}
		});

		JPanel panel = new JPanel();

		dateField = new JTextField(12);
		panel.add(dateField);

		calendarIcon = new JButton(new ImageIcon(image));
		calendarIcon.addActionListener(new CalendarActionListener(frame,
				dateField));
		panel.add(calendarIcon);

		JPanel buttonPanel = new JPanel();

		JButton quitButton = new JButton("Quit");
		quitButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
				exitProcedure();
			}
		});

		buttonPanel.add(quitButton);

		frame.add(panel, BorderLayout.CENTER);
		frame.add(buttonPanel, BorderLayout.SOUTH);

		frame.pack();
		frame.setLocationByPlatform(true);
		frame.setVisible(true);
	}

	public void exitProcedure() {
		frame.dispose();
		System.exit(0);
	}

	public Image getImage() {
		Image image = null;
		try {
			image = ImageIO.read(getClass()
					.getResourceAsStream("/calendar.png"));
		} catch (IOException e) {
			e.printStackTrace();
		}
		return image;
	}

	public static void main(String[] args) {
		SwingUtilities.invokeLater(new JCalendarTester());
	}

	private class CalendarActionListener implements ActionListener {

		private JFrame frame;

		private JTextField textField;

		public CalendarActionListener(JFrame frame, JTextField textField) {
			this.frame = frame;
			this.textField = textField;
		}

		@Override
		public void actionPerformed(ActionEvent event) {
			JCalendarDialog dialog = new JCalendarDialog(frame);
			dialog.addExclusionDateRange(new ExclusionDateRange("M/d/yy",
					"2/16/15", "2/16/15"));
			dialog.setDialogTitle("Appointment Date");
			dialog.setExclusionDaysOfWeek(Calendar.SATURDAY, Calendar.SUNDAY);
			dialog.setLocale(Locale.ENGLISH);
			dialog.createDialog();
			if (dialog.getReturnCode() == JCalendarDialog.OK_PRESSED) {
				textField.setText(dialog.getFormattedSelectedDate());
			}
		}

	}
}

You can see how the JCalendarDialog is set up in the JButton action listener class.

Finally, we have the ExclusionDateRange class so that the programmer can select one or more date ranges of one or more days to exclude from the JCalendar selection.

package com.ggl.jcalendar;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

/**
 * <p>
 * This data class is used to specify a date range. Because
 * <code>Calendar</code> is such a difficult class to work with, the constructor
 * takes the date range as <code>String</code>s.
 * </p>
 * 
 * <p>
 * Each instance of this data class represents one date range. More than one
 * date range can be passed to JCalendar by calling the JCalendar
 * <code>addExclusionDateRange</code> method once for each date range.
 * </p>
 * 
 * @author Gilbert G. Le Blanc
 * 
 * @version 1.0 - 13 February 2015
 * 
 * @see java.text.SimpleDateFormat
 * @see java.util.Calendar
 */
public class ExclusionDateRange {

	private Calendar fromCalendar;
	private Calendar toCalendar;

	/**
	 * This creates an ExclusionDateRange instance with a date range.
	 * 
	 * @param simpleDateFormat
	 *            - A <code>String</code> with the simple date format of the
	 *            next two date strings.
	 * @param fromDateString
	 *            - A <code>String</code> representing the from date of the
	 *            exclusion date range.
	 * @param toDateString
	 *            - A <code>String</code> representing the to date of the
	 *            exclusion date range.
	 */
	public ExclusionDateRange(String simpleDateFormat, String fromDateString,
			String toDateString) {
		SimpleDateFormat inputDate = new SimpleDateFormat(simpleDateFormat);

		this.fromCalendar = createCalendarDate(inputDate, fromDateString);
		this.toCalendar = createCalendarDate(inputDate, toDateString);

		if ((fromCalendar == null) || (toCalendar == null)) {
			return;
		}

		clearTime(fromCalendar);
		clearTime(toCalendar);

		fromCalendar.add(Calendar.DAY_OF_MONTH, -1);
		toCalendar.add(Calendar.DAY_OF_MONTH, 1);
	}

	private Calendar createCalendarDate(SimpleDateFormat inputDate,
			String dateString) {
		Date date;
		try {
			date = inputDate.parse(dateString);
		} catch (ParseException e) {
			e.printStackTrace();
			return null;
		}

		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		return calendar;
	}

	private void clearTime(Calendar c) {
		c.set(Calendar.HOUR_OF_DAY, 0);
		c.set(Calendar.MINUTE, 0);
		c.set(Calendar.SECOND, 0);
		c.set(Calendar.MILLISECOND, 0);
	}

	/**
	 * This method returns the from date as a <code>Calendar</code> instance.
	 * 
	 * @return A <code>Calendar</code> instance of the from date of the excluded
	 *         date range.
	 */
	public Calendar getFromCalendar() {
		return fromCalendar;
	}

	/**
	 * This method returns the to date as a <code>Calendar</code> instance.
	 * 
	 * @return A <code>Calendar</code> instance of the to date of the excluded
	 *         date range.
	 */
	public Calendar getToCalendar() {
		return toCalendar;
	}

}

The constructor takes date strings and converts them to Calendar instances. I had to subtract a day from the from date and add a day to the to date to make the date range inclusive. The Calendar after and before methods are exclusive.

Thank you for reading this article. I hope this code is helpful to you as a Swing component, and as an example of how one can put a Swing component together.

Post a Comment

Your email is kept private. Required fields are marked *