Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JSpinner.DateEditor must include year even though start and end is the same year

I have a JSpinner using a SpinnerDateModel which has a start at Jan 1, 2010 00:00:00.000 the end date is Jan 1, 2010 00:12:34.217. I would like my JSpinner.DateEditor to use the format HH:mm:ss.SSS but the spinner doesn't spin with this format. It only spins when "yyyy" is added to the format. How can I get around this?

import java.awt.GridLayout;
import java.util.*;
import javax.swing.*;

public class T extends JPanel {

    public T() {
        super(new GridLayout(2, 2));
        init();
    }

    private void init() {
        Calendar start = GregorianCalendar.getInstance();
        Calendar end = GregorianCalendar.getInstance();
        start.clear();
        end.clear();
        start.set(Calendar.YEAR, 2010);
        end.set(Calendar.YEAR, 2010);
        end.add(Calendar.HOUR_OF_DAY, 12);
        SpinnerDateModel m1 =
                new SpinnerDateModel(start.getTime(), start.getTime(),
                end.getTime(), Calendar.MILLISECOND);
        SpinnerDateModel m2 =
                new SpinnerDateModel(start.getTime(), start.getTime(),
                end.getTime(), Calendar.MILLISECOND);
        JSpinner workingSpinner = new JSpinner(m1);
        workingSpinner.setEditor(
                new JSpinner.DateEditor(workingSpinner,
                "yyyy HH:mm:ss.SSS"));
        JSpinner notWorkingSpinner = new JSpinner(m2);
        notWorkingSpinner.setEditor(
                new JSpinner.DateEditor(notWorkingSpinner,
                "HH:mm:ss.SSS"));
        add(new JLabel("Working"));
        add(workingSpinner);
        add(new JLabel("!Working"));
        add(notWorkingSpinner);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                createAndShowGUI();
            }
        });
    }

    private static void createAndShowGUI() {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new T());
        frame.pack();
        frame.setVisible(true);
    }
}
like image 777
initialZero Avatar asked Oct 06 '10 17:10

initialZero


1 Answers

After a good bit of digging around in the JRE source I discovered the the spinner is backed by a text value instead of a true date. When you hit the up and down spin buttons, the value is parsed and then compared to your min and max values. Because your format does not have a year the dates are parsed with the year always being 1970 which is year offset 0 from the epoch. This causes the spinner to always return an out of range error when you try to spin it.

The quickest solution is to simply use 1970 as your year instead of 2010. However, if your initial date is at the end of 1970 the spinner won't let your users roll over into January of 1971 (instead it may jump back to the beginning of 1970).

The other solution can accomodate dates that span calendar year boundaries. However, it is not as simple (or pretty). In the JRE when the DateFormatter parses the date string, it instantiates a class dynamically using a single String parameter constructor. This string is the date from the spinner. By default, this class is either Date or some subclass of it. We can have the formatter instantiate our own Date class which fixes the year before any date comparisons are performed.


Date class that adds the year:

public static class DateThatAddsYear extends Date {
 public DateThatAddsYear( String time ) {
  super( time );
  Calendar cal = GregorianCalendar.getInstance();
  cal.setTime( this );
  // Jump back to 2010, this needs to be implemented more thoroughly in order 
  // to support dates crossing calendar year boundaries
  cal.set( Calendar.YEAR, 2010 );
  setTime( cal.getTimeInMillis() );
 }
}

Manually set up the spinner, using our date fix:

JSpinner notWorkingSpinner = new JSpinner(m2);
JSpinner.DateEditor dateEditor = new JSpinner.DateEditor(notWorkingSpinner);
DateFormatter formatter = new DateFormatter( format );
notWorkingSpinner.setEditor(dateEditor);
dateEditor.getTextField().setFormatterFactory( new DefaultFormatterFactory( formatter ) );
formatter.setValueClass( DateThatAddsYear.class ); // Tell it to use a different value class!

Ugly, but it works.

Also, If you want to poke around in the JRE source I suggest looking at the public method stringToValue(String text) of InternationalFormatter (superclass of DateFormatter).

like image 164
Andy Avatar answered Sep 28 '22 10:09

Andy