using SimpleDateFormat with a JFormattedTextField

A

Andreas Leitgeb

In some GUI there is a JFormattedTextField for entering a
time of day. Typically the format (for the user to type
and see) should be "15:42".
The text field is created like this:
new JFormattedTextField(new SimpleDateFormat("HH:mm"));

If the user now also would like to type just "1542" and have it
recognized and canonified to "15:42" upon commit, what effort
would it take to make that possible? Can it be done with a
SimpleDateFormat, some other *DateFormat, or does that require
rolling my own CustomDateFormat-subclass?

PS: I have very little experience with GUI programming so far.
 
A

Andreas Leitgeb

Lew said:
A 'SimpleDateFormat' with the format string "HHmm" will parse that.

Sorry, I was probably unclear in some points:

The user should be allowed to input any of the styles: "1542" or "15:42"

After "commit" (this is a JFormattedTextField-related term, and
typically happens upon a FocusOut event, unless differently set up)
The current value of the field is replaced by a canonical text-form
of the internal Date value (or cleared, if the parse failed).
e.g.: "9:00" -> "09:00", or "8:92" -> "09:32", or "foo" -> ""

I'd now like to have it *also* recognize "900" and read it as
"9 o'clock in the morning" and canonicalize the text to "09:00"
after commit. Just like telling the SimpleDateFormat, that the
colon is optional on input(text->date), but should be included
on output(date->text).

Also I'd like to change as little as necessary from the original
code to set up the text field:
new JFormattedTextField(new SimpleDateFormat("HH:mm"));

My problem can be seen as either a DateFormat-problem (as in: how do
I get SDF to not require the colon?), or as a GUI-problem (as in: how
can I interfere, before the SDF parses the field's text and rejects it?)
 
A

Andreas Leitgeb

Lew said:
I remembered reading that the 'parse()' method was rather forgiving, but I
cannot find evidence for how forgiving it can be. The method 'setLenient()'
<http://java.sun.com/javase/6/docs/api/java/text/DateFormat.html#setLenient(boolean)>
increases the range of parsable input formats for a given instance if set 'true'.

I tried setLenient(true), already, but it didn't change anything.
probably the format-string given to SimpleDateFormat turns off any
leniency. I guess it is not used with the SimpleDateParser, and
I'm quite lost on how to create a (Simple)DateFormat
for "either HH:mm or HHmm but nothing else".

The other part of the problem is, that the text field class
integrates so well with the formatter/parser, that all I see
is m_textfield.getValue() which then returns an Object
castable to Date or null. I can't seem to get to the text,
before the parser sees it. That's the GUI-aspect of my problem.

Maybe I should just have another fresh look at it tomorrow
morning and hope that it will be evident to me, then.
Try the most likely 'DateFormat#parse()' first...

If only I knew how to apply it, without replacing the
currently assigned SDF entirely. If I did replace it,
I'd at least first need to know how to limit a
(not-Simple)DateFormat to time-specs (to prevent it from
accepting date-specs as well)
 
J

John B. Matthews

Andreas Leitgeb said:
I tried setLenient(true), already, but it didn't change anything.
probably the format-string given to SimpleDateFormat turns off any
leniency. I guess it is not used with the SimpleDateParser, and
I'm quite lost on how to create a (Simple)DateFormat
for "either HH:mm or HHmm but nothing else".

The other part of the problem is, that the text field class
integrates so well with the formatter/parser, that all I see
is m_textfield.getValue() which then returns an Object
castable to Date or null. I can't seem to get to the text,
before the parser sees it. That's the GUI-aspect of my problem.

Maybe I should just have another fresh look at it tomorrow
morning and hope that it will be evident to me, then.


If only I knew how to apply it, without replacing the
currently assigned SDF entirely. If I did replace it,
I'd at least first need to know how to limit a
(not-Simple)DateFormat to time-specs (to prevent it from
accepting date-specs as well)

Andreas: I usually just catch the ParseException thrown by commitEdit(),
but you might look at attaching a FormattedTextFieldVerifier, as
suggested here:

<http://java.sun.com/javase/6/docs/api/javax/swing/JFormattedTextField.ht
ml#commitEdit()>

I'm guessing you could yield focus if adding a colon would make it a
valid date.
 
M

Mark Space

Andreas said:
I tried setLenient(true), already, but it didn't change anything.
probably the format-string given to SimpleDateFormat turns off any
leniency. I guess it is not used with the SimpleDateParser, and
I'm quite lost on how to create a (Simple)DateFormat
for "either HH:mm or HHmm but nothing else".


I think what everyone is saying is that you have to use two, and check
them both. Or did I not follow and that was already obvious to you?

Uncomment the logger lines to see that an exception is really thrown...



package dateparsetest;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

public class DateParseTest {

public static void main(String[] args) {
List<SimpleDateFormat> validTimes = new
ArrayList<SimpleDateFormat>();
validTimes.add( new SimpleDateFormat("HH:mm" ));
validTimes.add( new SimpleDateFormat( "HHmm" ));
String[] testInput = { "12:34", "1234" };
Date time=null;

for( String test : testInput ) {
validationLoop:
for( SimpleDateFormat format : validTimes ) {
try {
time = format.parse( test );
break validationLoop;
}
catch( ParseException ex ) {
// Logger.getLogger( DateParseTest.class.getName() ).
// log( Level.SEVERE, null, ex );
}
}
System.out.println( "Time for " + test + " is " + time );
}
}
}
 
A

Andreas Leitgeb

Mark Space said:
I think what everyone is saying is that you have to use two, and check
them both. Or did I not follow and that was already obvious to you?

That's also what I read from the posts.
Unfortunately I don't yet see, *how* to do that.

As I wrote, this is someone else's code, and the original
author doesn't have any inclination to add support for
HHmm input format, and I don't really understand it.

The JFormattedTextField is created such:
m_timeTxt = new JFormattedTextField(new SimpleDateFormat("HH:mm"));

Then, addPropertyChangeListener is called on m_timeTxt,
and the interface-method that is installed does roughly
this:
Date l_time = (Date)m_timeTxt.getValue();
if ( l_time != null ) { ... } // else nothing;
For an input like "1542", l_time turns out to be null.
I don't even see, how I would get to deal with the
entered String, to do any of the re-parsing suggested.

I don't see, where and how to add any other parser to it.
I did read (and understand) your sample, but it didn't
involve the JFormattedTextField-integration.

Probably I miss things, that are blatantly obvious
to everyone used to swing programming.
 
J

John B. Matthews

Andreas Leitgeb said:
That's also what I read from the posts.
Unfortunately I don't yet see, *how* to do that.

As I wrote, this is someone else's code, and the original
author doesn't have any inclination to add support for
HHmm input format, and I don't really understand it.

The JFormattedTextField is created such:
m_timeTxt = new JFormattedTextField(new SimpleDateFormat("HH:mm"));

Then, addPropertyChangeListener is called on m_timeTxt,
and the interface-method that is installed does roughly
this:
Date l_time = (Date)m_timeTxt.getValue();
if ( l_time != null ) { ... } // else nothing;
For an input like "1542", l_time turns out to be null.
I don't even see, how I would get to deal with the
entered String, to do any of the re-parsing suggested.

I don't see, where and how to add any other parser to it.
I did read (and understand) your sample, but it didn't
involve the JFormattedTextField-integration.

Probably I miss things, that are blatantly obvious
to everyone used to swing programming.

Combining Mark Space's and Lew's suggestions with my verifier proposal:

<code>
import java.awt.EventQueue;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.swing.Box;
import javax.swing.InputVerifier;
import javax.swing.JComponent;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.text.DateFormatter;
import javax.swing.text.DefaultFormatterFactory;
import javax.swing.text.MaskFormatter;

/** @author John B. Matthews */
public class FormattedDate {

public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
new FormattedDate();
}
});
}

FormattedDate() {
Box form = Box.createVerticalBox();

form.add(new JLabel("Date & Time:"));
DateTimeField dtField = new DateTimeField();
dtField.setValue(new Date());
form.add(dtField);

form.add(new JLabel("Amount:"));
JFormattedTextField amtField = new JFormattedTextField(
NumberFormat.getCurrencyInstance());
amtField.setValue(new Integer(100000));
form.add(amtField);

JFrame frame = new JFrame();
frame.add(form);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}

class DateTimeField extends JFormattedTextField {

public DateTimeField () {
this.setFormatterFactory(new DefaultFormatterFactory(
new DateFormatter(DateTimeVerifier.getDefaultFormat())));
this.setInputVerifier(new DateTimeVerifier(this));
}
}

class DateTimeVerifier extends InputVerifier {

private static List<SimpleDateFormat> validForms =
new ArrayList<SimpleDateFormat>();
static {
validForms.add(new SimpleDateFormat("dd-MMM-yy HH:mm"));
validForms.add(new SimpleDateFormat("dd-MMM-yy HHmm"));
}
private JFormattedTextField tf;
private Date date;

public DateTimeVerifier(JFormattedTextField tf) {
this.tf = tf;
}

public boolean verify(JComponent input) {
boolean result = false;
if (input == tf) {
String text = tf.getText();
for( SimpleDateFormat format : validForms ) {
try {
date = format.parse(text);
result |= true;
} catch (ParseException pe1) {
result |= false;
}
}
}
return result;
}

public boolean shouldYieldFocus(JComponent input) {
if (verify(input)) {
tf.setValue(date);
return true;
} else return false;
}

public static SimpleDateFormat getDefaultFormat() {
return validForms.get(0);
}
}
</code>

It took me a while to meet the "no side effects" dictum in verify().
 
A

Andreas Leitgeb

Lew said:
Use a custom DateFormat that composes several DateFormat instances under the hood.

rattle rattle hummmmmm... *bing*

That's it. Thanks a lot!

PS:
m_timeFormat = new SimpleDateFormat( "HH:mm" ) {
public Date parse(String source, ParsePosition pos) {
Date d=super.parse(source,pos);
if (d==null) d=new SimpleDateFormat("Hmm").parse(source,pos);
if (d==null) d=new SimpleDateFormat("HHmm").parse(source,pos);
return d;
}
};
I need two alternative parsers, because the "HHmm" one
still misinterpreted three-digit numbers.

Efficiency is a non-issue, so I do not really care about
create&use&forget all the extra SimpleDateFormat-instances ...
But then, maybe I factor it out as a standalone class, and
create the other two instances statically.

Anyway, it works - Problem solved :)
 
A

Andreas Leitgeb

Andreas Leitgeb said:
PS:
m_timeFormat = new SimpleDateFormat( "HH:mm" ) {
public Date parse(String source, ParsePosition pos) {
Date d=super.parse(source,pos);
if (d==null) d=new SimpleDateFormat("Hmm").parse(source,pos);
if (d==null) d=new SimpleDateFormat("HHmm").parse(source,pos);
return d;
}
};
I need two alternative parsers, because the "HHmm" one
still misinterpreted three-digit numbers.
Anyway, it works - Problem solved :)

Just for the record: no it did not. Since each of these two
extra SDF's can handle (and usually wrongly) almost any number
of digits (*).
I finally resorted to check source.length() and depending
on its length() call one of the two alternatives. That all
only if the first parse() failed, of course.

This now works for those strings I'm likely to ever type in,
and will probably still give funny effects for certain
others, but I won't go any further into this.

*: Format string "Hmm" on "1234" makes "1" for hour and "234" for minutes.
Format string "HHmm" on "123" makes "12" for hour and "3" for minutes.
Somehow not really surprising, but rendering it almost useless.
 
J

John B. Matthews

Andreas Leitgeb said:
Just for the record: no it did not. Since each of these two
extra SDF's can handle (and usually wrongly) almost any number
of digits (*).
I finally resorted to check source.length() and depending
on its length() call one of the two alternatives. That all
only if the first parse() failed, of course.

This now works for those strings I'm likely to ever type in,
and will probably still give funny effects for certain
others, but I won't go any further into this.

*: Format string "Hmm" on "1234" makes "1" for hour and "234" for minutes.
Format string "HHmm" on "123" makes "12" for hour and "3" for minutes.
Somehow not really surprising, but rendering it almost useless.


Interesting. While tinkering with my implementation using "dd-MMM-yy
HH:mm" xor "dd-MMM-yy HHmm", I see that changing 15:30 to 39:30 changes
the field to 15:30 the next day. Pretty lenient:)
 
A

Andreas Leitgeb

These 234 minutes were already the explanation. I actually got something
like 04:54 from it, which was explainable as 1h 234m, 234==3*60+54
Interesting. While tinkering with my implementation using "dd-MMM-yy
HH:mm" xor "dd-MMM-yy HHmm", I see that changing 15:30 to 39:30 changes
the field to 15:30 the next day. Pretty lenient:)
That's just ok for some uses - in contrast to the examples above.
My point was the interpretation if digits, not the rolling over.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,011
Latest member
AjaUqq1950

Latest Threads

Top