Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

using SimpleDateFormat with a JFormattedTextField

226 views
Skip to first unread message

Andreas Leitgeb

unread,
Oct 20, 2008, 12:15:10 PM10/20/08
to
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.

Lew

unread,
Oct 20, 2008, 2:25:50 PM10/20/08
to
Andreas Leitgeb wrote:
> 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

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

How it is canonicalized on commit depends on whether you're storing a String
or a Date subclass. If you store a Date subclass, it's meaningless to talk
about the presence or absence of a colon.

> would it take to make that possible? Can it be done with a

Use a Date type for storing and the issue goes away.

> SimpleDateFormat, some other *DateFormat, or does that require
> rolling my own CustomDateFormat-subclass?

Use 'SimpleDateFormat' to parse Strings to Dates and to format Dates as Strings.

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

This is not a GUI issue but a Date-to-String conversion issue.

--
Lew

Andreas Leitgeb

unread,
Oct 20, 2008, 3:16:37 PM10/20/08
to
Lew <no...@lewscanon.com> wrote:
> Andreas Leitgeb wrote:
>> 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
>
> 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?)

Lew

unread,
Oct 20, 2008, 3:28:38 PM10/20/08
to
Andreas Leitgeb wrote:
> 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).

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'.

When working on screens that had to recognize any of several possibly
ill-formed date formats, I've used several 'DateFormat' instances, each
recognizing a different pattern. I recall having to test the boundaries for
each format; even with 'setLenient( true )' there are limits, but I don't
remember right now where those limits are.

Try the most likely 'DateFormat#parse()' first. If it can't handle the input,
try the next, and the next at need, etc. If none of the parsers can handle
the input, deem it illegal input. Otherwise, keep the resulting 'Date' from
the successful 'parse()', and 'format()' it back to a 'String' using the
'DateFormat' instance that has your canonical pattern. Commit that 'String'.

--
Lew

Andreas Leitgeb

unread,
Oct 20, 2008, 3:59:09 PM10/20/08
to
Lew <no...@lewscanon.com> wrote:
> 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)

John B. Matthews

unread,
Oct 20, 2008, 4:41:15 PM10/20/08
to
In article <slrngfpoo...@gamma.logic.tuwien.ac.at>,
Andreas Leitgeb <a...@gamma.logic.tuwien.ac.at> wrote:

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.

--
John B. Matthews
trashgod at gmail dot com
http://home.roadrunner.com/~jbmatthews/

Mark Space

unread,
Oct 20, 2008, 7:56:36 PM10/20/08
to
Andreas Leitgeb wrote:

> 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 );
}
}
}

Andreas Leitgeb

unread,
Oct 21, 2008, 6:55:31 AM10/21/08
to
Mark Space <mark...@sbcglobal.net> wrote:
> Andreas Leitgeb wrote:
>> 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?

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.

Andreas Leitgeb

unread,
Oct 21, 2008, 7:00:37 AM10/21/08
to
Andreas Leitgeb <a...@gamma.logic.tuwien.ac.at> wrote:
> Then, addPropertyChangeListener is called on m_timeTxt,

I forgot to mention, that the Property being watched is
"value".

Lew

unread,
Oct 21, 2008, 8:17:54 AM10/21/08
to
Andreas Leitgeb wrote:
> Mark Space <mark...@sbcglobal.net> wrote:
>> Andreas Leitgeb wrote:
>>> 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?
>
> 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.

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

--
Lew

John B. Matthews

unread,
Oct 21, 2008, 9:57:31 AM10/21/08
to
In article <slrngfrd9...@gamma.logic.tuwien.ac.at>,
Andreas Leitgeb <a...@gamma.logic.tuwien.ac.at> wrote:

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().

Andreas Leitgeb

unread,
Oct 21, 2008, 11:50:36 AM10/21/08
to
Lew <no...@lewscanon.com> wrote:
> Andreas Leitgeb wrote:
>> Unfortunately I don't yet see, *how* to do that.
> 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 :-)

Andreas Leitgeb

unread,
Oct 21, 2008, 3:27:01 PM10/21/08
to
Andreas Leitgeb <a...@gamma.logic.tuwien.ac.at> wrote:
> 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.

John B. Matthews

unread,
Oct 21, 2008, 3:57:23 PM10/21/08
to
In article <slrngfsb8...@gamma.logic.tuwien.ac.at>,
Andreas Leitgeb <a...@gamma.logic.tuwien.ac.at> wrote:


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:-)

Andreas Leitgeb

unread,
Oct 21, 2008, 5:02:24 PM10/21/08
to
John B. Matthews <nos...@nospam.invalid> wrote:
> Andreas Leitgeb <a...@gamma.logic.tuwien.ac.at> wrote:
>> *: 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.
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.

0 new messages