using SimpleDateFormat with a JFormattedTextField

Discussion in 'Java' started by Andreas Leitgeb, Oct 20, 2008.

  1. 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.
     
    Andreas Leitgeb, Oct 20, 2008
    #1
    1. Advertising

  2. Lew <> 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?)
     
    Andreas Leitgeb, Oct 20, 2008
    #2
    1. Advertising

  3. Lew <> 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)
     
    Andreas Leitgeb, Oct 20, 2008
    #3
  4. In article <>,
    Andreas Leitgeb <> wrote:

    > Lew <> 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(b
    > >oolean)>
    > > 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)


    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/
     
    John B. Matthews, Oct 20, 2008
    #4
  5. Andreas Leitgeb

    Mark Space Guest

    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 );
    }
    }
    }
     
    Mark Space, Oct 21, 2008
    #5
  6. Mark Space <> 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, Oct 21, 2008
    #6
  7. Andreas Leitgeb <> wrote:
    > Then, addPropertyChangeListener is called on m_timeTxt,


    I forgot to mention, that the Property being watched is
    "value".
     
    Andreas Leitgeb, Oct 21, 2008
    #7
  8. In article <>,
    Andreas Leitgeb <> wrote:

    > Mark Space <> 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.


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

    --
    John B. Matthews
    trashgod at gmail dot com
    http://home.roadrunner.com/~jbmatthews/
     
    John B. Matthews, Oct 21, 2008
    #8
  9. Lew <> 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, Oct 21, 2008
    #9
  10. Andreas Leitgeb <> 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.
     
    Andreas Leitgeb, Oct 21, 2008
    #10
  11. In article <>,
    Andreas Leitgeb <> wrote:

    > Andreas Leitgeb <> 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.



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

    --
    John B. Matthews
    trashgod at gmail dot com
    http://home.roadrunner.com/~jbmatthews/
     
    John B. Matthews, Oct 21, 2008
    #11
  12. John B. Matthews <> wrote:
    > Andreas Leitgeb <> 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.
     
    Andreas Leitgeb, Oct 21, 2008
    #12
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. wolfgang wagner

    JFormattedtextField - formatting user input

    wolfgang wagner, Jul 9, 2003, in forum: Java
    Replies:
    0
    Views:
    464
    wolfgang wagner
    Jul 9, 2003
  2. Mack Attack
    Replies:
    1
    Views:
    10,841
    Christophe Vanfleteren
    Sep 15, 2003
  3. Pawel Poplawski
    Replies:
    0
    Views:
    680
    Pawel Poplawski
    Dec 3, 2003
  4. Webby
    Replies:
    1
    Views:
    3,058
    Webby
    Apr 21, 2004
  5. Philipp
    Replies:
    1
    Views:
    950
    Chris Uppal
    Mar 31, 2006
Loading...

Share This Page