Calendar source code – Part 1

Here’s some source code I wrote for a quick and dirty calendar widget I needed for a project I’m currently working on. Those of you who’ve dealt with Java before will probably know that a calendar UI widget is missing from the hundreds of standard API classes on offer. There are a huge amount of free ones available out there, some better than others.

I needed a lightweight quick and dirty widget without any bells and whistles – select a month, year and the dates are displayed in a table when you select. The user can highlight a single date, but there’s no interaction with the actual table. Here’s a screenshot of the end result.

Java calendar screenshot

Java calendar widget (with Nimbus L&F)


OK, so what’s the point of this? Well, firstly, if anyone wants to use it, feel free to do so (disclaimer : I take no responsibility for what this code does, including setting your computer on fire). Secondly, due to necessity, it has quite a few extra branches. Calendars tend to do this… firstly, you have February. So you need to check if the year is a leap year. Luckily, Java provides us with this ability, but for those who want a leap year algorithm : If the year is divisible by 4 & 100 it is, except if it is also divisible by 400 it is not (credit to Kernighan & Richie for this one.)
You also have varying number of days in each month, and to top it all off, you have to squeeze everything into a seven by six table.
Adding to the complexity is that this particular widget is ordered from Monday – Sunday rather than the US Sunday – Saturday.

Doing all this is a fairly simple process. As I said, it’s quick and dirty, doesn’t use a model for the data, and has some funky loops involving magic numbers (one of which coincidentally is 42 :) )

Thirdly, in the next part, I’ll talk about what QA problems this introduces, from the point of view of a black box (especially the importance of the order the tests are undertaken), and also with regards to unit testing.

Without further ado, here’s the source. Note that the init method is a bunch of Netbeans autogenerated code from the visual GUI designer. It’s solely for the purpose of creating the layout manager and adding the components, you can implement your own if you want, I included it for completeness. Also, this is a JPanel class, so obviously you need to add it to a frame to display it.

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

/**
*
* @author mike
*/
public class CalendarDialog extends javax.swing.JPanel
{
private int[] daysInMonth = new int[]{31,28,31,30,31,30,31,31,30,31,30,31};
private GregorianCalendar calendar = new GregorianCalendar();
private Object[] days = new Object[50];

/** Creates new form CalendarDialog */
public CalendarDialog()
{
initComponents();
createCalendarData();
}

public void createCalendarData()
{
calendar.set(Calendar.YEAR, Integer.parseInt((String)years.getSelectedItem()));
calendar.set(Calendar.MONTH, months.getSelectedIndex());
calendar.set(Calendar.DAY_OF_MONTH, 1);

int offset = calendar.get(Calendar.DAY_OF_WEEK);
int totDays = daysInMonth[calendar.get(Calendar.MONTH)];

if(calendar.isLeapYear(calendar.get(Calendar.YEAR)) && calendar.get(Calendar.MONTH)==1)
totDays += 1;

if(offset == 1)
offset +=5;
else
offset -= 2;

for(int i=0;i < totDays+offset;i++)
{
if(i calTable.setValueAt("", i/7, (i+7)%7);
else
calTable.setValueAt(i+1-offset, i/7, (i+7)%7);
}
for(int i=totDays+offset;i<42;i++)
calTable.setValueAt("", i/7, (i+7)%7);
}

public Date getSelectedDate()
{
int row = calTable.getSelectedRow();
int col = calTable.getSelectedColumn();
if(row != -1 && col != -1 && !(calTable.getValueAt(row, col) instanceof String))
{
GregorianCalendar cal = new GregorianCalendar();
cal.set(Calendar.YEAR, Integer.parseInt((String)years.getSelectedItem()));
cal.set(Calendar.MONTH, months.getSelectedIndex());
cal.set(Calendar.DAY_OF_MONTH, (Integer)calTable.getValueAt(row, col));
return cal.getTime();
}
else
return null;

}

/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
//
private void initComponents() {

months = new javax.swing.JComboBox();
years = new javax.swing.JComboBox();
jScrollPane1 = new javax.swing.JScrollPane();
calTable = new javax.swing.JTable();
jLabel1 = new javax.swing.JLabel();
selDate = new javax.swing.JTextField();

java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("calendarwidget/Bundle"); // NOI18N
setBorder(javax.swing.BorderFactory.createTitledBorder(javax.swing.BorderFactory.createTitledBorder(""), bundle.getString("CalendarDialog.border.title"))); // NOI18N

months.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }));
months.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(java.awt.event.ItemEvent evt) {
monthsItemStateChanged(evt);
}
});

years.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "2010", "2011", "2012", "2013", "2014", "2015", "2016", "2017", "2018", "2019", "2020" }));
years.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(java.awt.event.ItemEvent evt) {
yearsItemStateChanged(evt);
}
});

calTable.setModel(new javax.swing.table.DefaultTableModel(
new Object [][] {
{null, null, null, null, null, null, null},
{null, null, null, null, null, null, null},
{null, null, null, null, null, null, null},
{null, null, null, null, null, null, null},
{null, null, null, null, null, null, null},
{null, null, null, null, null, null, null}
},
new String [] {
"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"
}
));
calTable.setRowSelectionAllowed(false);
calTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
calTable.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent evt) {
calTableMouseClicked(evt);
}
});
jScrollPane1.setViewportView(calTable);
calTable.getColumnModel().getColumn(0).setHeaderValue(bundle.getString("CalendarDialog.calTable.columnModel.title0")); // NOI18N
calTable.getColumnModel().getColumn(1).setHeaderValue(bundle.getString("CalendarDialog.calTable.columnModel.title1")); // NOI18N
calTable.getColumnModel().getColumn(2).setHeaderValue(bundle.getString("CalendarDialog.calTable.columnModel.title2")); // NOI18N
calTable.getColumnModel().getColumn(3).setHeaderValue(bundle.getString("CalendarDialog.calTable.columnModel.title3")); // NOI18N
calTable.getColumnModel().getColumn(4).setHeaderValue(bundle.getString("CalendarDialog.calTable.columnModel.title4")); // NOI18N
calTable.getColumnModel().getColumn(5).setHeaderValue(bundle.getString("CalendarDialog.calTable.columnModel.title5")); // NOI18N
calTable.getColumnModel().getColumn(6).setHeaderValue(bundle.getString("CalendarDialog.calTable.columnModel.title6")); // NOI18N

jLabel1.setText(bundle.getString("CalendarDialog.jLabel1.text")); // NOI18N

selDate.setText(bundle.getString("CalendarDialog.selDate.text")); // NOI18N

javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(months, javax.swing.GroupLayout.PREFERRED_SIZE, 140, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(years, javax.swing.GroupLayout.PREFERRED_SIZE, 78, javax.swing.GroupLayout.PREFERRED_SIZE))
.addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 348, Short.MAX_VALUE)
.addGroup(layout.createSequentialGroup()
.addComponent(jLabel1)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(selDate, javax.swing.GroupLayout.PREFERRED_SIZE, 236, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(months, javax.swing.GroupLayout.PREFERRED_SIZE, 34, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(years, javax.swing.GroupLayout.DEFAULT_SIZE, 34, Short.MAX_VALUE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 146, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jLabel1)
.addComponent(selDate, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGap(17, 17, 17))
);
}//

private void yearsItemStateChanged(java.awt.event.ItemEvent evt) {
createCalendarData();
}

private void monthsItemStateChanged(java.awt.event.ItemEvent evt) {
createCalendarData();
}

private void calTableMouseClicked(java.awt.event.MouseEvent evt) {
if(getSelectedDate()!=null)
selDate.setText(getSelectedDate().toString());
else
selDate.setText("No date selected");
}

// Variables declaration - do not modify
private javax.swing.JTable calTable;
private javax.swing.JLabel jLabel1;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JComboBox months;
private javax.swing.JTextField selDate;
private javax.swing.JComboBox years;
// End of variables declaration

}