Solved

SWPM seeks pretty...

Posted on 2000-05-03
11
267 Views
Last Modified: 2008-02-01
way to validate/convert dates in a JTextField.
(Sorry I couldnt resist)

Using JDK1.3rc3.
Right now I have a pretty ugly method.
I use a generic filter to make sure input only includes specific characters (ie: 0123456789/.-) .  I then use a focusLost() event to attempt to parse the input string into a date format. Once I have that I can format the date into the string I need for the output.
When focus is lost I want to convert it to MM/dd/yyyy.  No problem there once I have a java.util.Date.  My problem is taking any known input and converting it to a java.util.Date.  Some examples:
12/31/00  123100  12312000  12.31.00  12.31.2000 12-31-00  12-31-2000  
which is by no means an exhaustive list.
DateFormat doesnt seem to have a way to test if a given string is a compatible date unless you try to parse which throws an exception if its not like the input pattern.  Ive thought of nested try/catch blocks (but like I said im looking for a prettier way).

I know everyone (and their dog) has probably done this at one time or another, and what I have works for most things (But its real darn ugly). Any one have a method they could share?

(An extra 10 points for the definition of SWPM :))
0
Comment
Question by:conick
  • 6
  • 5
11 Comments
 
LVL 3

Expert Comment

by:kostello
Comment Utility
Well, I don't know how "pretty" it is, but the following seems to work:

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

public class GetDate {
    public static void main(String[] args) {
        String date;
        if (args.length >= 1) {
            date = args[0];
        } else {
            date = new String("12/31/00");
        }
        if (date.length() != 6 && date.length() != 8 && date.length() != 10) {
            System.out.println("Invalid length");
            System.exit(0);
        }
        // Convert to the same separator - easier for parsing
        date = date.replace('-', '/');
        date = date.replace('.', '/');
        // We have 4 basic formats - MMDDYY, MMDDYYYY, MM/DD/YY, and MM/DD/YYYY
        Integer month = null;
        Integer day = null;
        Integer year = null;
        if (date.length() == 6) {
            // Case MMDDYY
            if (date.indexOf('/') != -1) {
                System.out.println("Invalid string");
                System.exit(0);
            }
            month = new Integer(date.substring(0, 2));
            day = new Integer(date.substring(2, 4));
             // You'll probably need something more intelligent for this
            year = new Integer("20" + date.substring(4, 6));
        } else if (date.length() == 8) {
            if (date.indexOf('/') == -1) {
                // Case MMDDYYYY
                month = new Integer(date.substring(0, 2));
                day = new Integer(date.substring(2, 4));
                year = new Integer(date.substring(4, 8));
            } else if (date.indexOf('/') != 2 ||
                       date.indexOf('/', 3) != 5 ||
                       date.indexOf('/', 6) != -1) {
                System.out.println("Invalid string");
                System.exit(0);
            } else {
                // Case MM/DD/YY
                month = new Integer(date.substring(0, 2));
                day = new Integer(date.substring(3, 5));
                // You'll probably need something more intelligent for this
                year = new Integer("20" + date.substring(6, 8));
            }
        } else {
            if (date.indexOf('/') != 2 ||
                date.indexOf('/', 3) != 5 ||
                date.indexOf('/', 6) != -1) {
                System.out.println("Invalid string");
                System.exit(0);
            }
            // Case MM/DD/YYYY
            month = new Integer(date.substring(0, 2));
            day = new Integer(date.substring(3, 5));
            year = new Integer(date.substring(6, 10));
        }
        GregorianCalendar cal = new GregorianCalendar();
        // Probably want some validity checks on the month & day
        // month is 0-based
        cal.set(year.intValue(), month.intValue()-1, day.intValue());
        Date d = cal.getTime();
        System.out.println("Year is " + year + " Month is " + month + " Day is " + day);
        System.out.println("Date is " + d);
    }
}


I haven't put enough error-checking in here, but this seems to work for the test cases you've given.  Also, of course, you'll probably want to do something other than exit on error.  Hope this helps.

BTW, SWPM - Single, Wonderful Programming Machine?
0
 
LVL 7

Author Comment

by:conick
Comment Utility
I didnt think of switching "." or "-" to "/".
I had branching logic for only input string length.If I used both separator characters AND length. I wouldnt have to cycle through patterns
(I was just cycling through all my known input patterns and ran DateFormat.parse() on them until one of them succeeded (or it got to the end and I gave a warning message)).

I think I still want to use DateFormat.parse().  So I can have a java.util.Date to play with.  But you cut down my branching logic by one-half.

Im going to reject this answer for 1 day.  (I wish you would have commented). If someone comes up with something better, Ill start a new question for 100 points for you, since you posted a solution that was better than my own.
If noone posts a better solution by tommorrow I'll give you the full 200 * 4 +10. (I was actually looking for the literal meaning of SWPM (I had to look it up after I saw it) but I like yours much better)
Thanks kostello.
0
 
LVL 3

Expert Comment

by:kostello
Comment Utility
Sorry, I'm not real big on comments.  Note that my previous code did create a Date, although after looking at the code using the DateFormat, using the parse does make it easier & removes the need for some error checking.  (I'm relatively new at Java, so missed this).  I modified the code to use the SimpleDateFormat and it follows.  The basic methodology is fairly simple - there are 4 basic formats, 2 of which are unique with length.  For 8 byte long strings, we either have separators or not.  Create a formatter for the proper case, then create the date.  

import java.util.Date;
import java.text.SimpleDateFormat;

public class GetDate {
    public static void main(String[] args) {
        String date;
        if (args.length >= 1) {
            date = args[0];
        } else {
            date = new String("12/31/00");
        }
        if (date.length() != 6 && date.length() != 8 && date.length() != 10) {
            System.out.println("Invalid length");
            System.exit(0);
        }

        // Convert to the same separator - easier for parsing
        date = date.replace('-', '/');
        date = date.replace('.', '/');

        // We have 4 basic formats - MMDDYY, MMDDYYYY, MM/DD/YY, and MM/DD/YYYY
        SimpleDateFormat df = null;
        if (date.length() == 6) {
            df = new SimpleDateFormat("MMDDyy");
        } else if (date.length() == 8) {
            if (date.indexOf('/') == -1) {
                df = new SimpleDateFormat("MMDDyyyy");
            } else {
                df = new SimpleDateFormat("MM/DD/yy");
            }
        } else {
            df = new SimpleDateFormat("MM/DD/yyyy");
        }
        try {
            Date d = df.parse(date);
            System.out.println("Date is " + d);
        } catch (java.text.ParseException e) {
            System.out.println("Invalid string");
        }
    }
}


0
 
LVL 3

Expert Comment

by:kostello
Comment Utility
kostello changed the proposed answer to a comment
0
 
LVL 3

Expert Comment

by:kostello
Comment Utility
Sorry about the last post - didn't really realize what the diff between an answer and a comment was.  I thought you meant comment within my answer, not post my answer as a comment.
0
Highfive + Dolby Voice = No More Audio Complaints!

Poor audio quality is one of the top reasons people don’t use video conferencing. Get the crispest, clearest audio powered by Dolby Voice in every meeting. Highfive and Dolby Voice deliver the best video conferencing and audio experience for every meeting and every room.

 
LVL 7

Author Comment

by:conick
Comment Utility
The cases I stated weren't very exhaustive.  Some cases I didn't mention (that I didnt think about until running my testing code was):
1/1/00  1/01/00  01/1/00
Hence the problem with separating with length.
6 chars could be 1/1/00 or 010100.
Any others? (Without getting international -- that opens up a whole new can of worms)

What I meant by pretty was that I didn't want to go case by case (unless I really needed to).
I may need to use looping try/catch blocks attempting different patterns to the input text to handle all known date formats.

This is more of a "I want to learn" question than a "I need this now" question.

I feel bad that I didnt lay all my test cases out on the table to begin with.
The 200 points tomorrow still apply.
0
 
LVL 7

Author Comment

by:conick
Comment Utility
Heres the best I came up with to work with all my known test cases.  
I didnt include the PlainDocument (it basically limits input to valid chars and size).
Notice how much your replacing of separator characters eliminated the need for more inFormats[] in my code.

import java.awt.*;
import java.awt.event.*;
import java.text.*;
import javax.swing.*;
import java.util.*;

public class JDateField extends JTextField {

      private static DateFormat outFormat = new SimpleDateFormat("MM/dd/yyyy");
      private static DateFormat inFormats[] = { new SimpleDateFormat("MMddyy"), new SimpleDateFormat("MM/dd/yy"),new SimpleDateFormat("MM/dd/yyyy")};
      
      Date date;
      String finalText;
     
      public JDateField() {
            super(10);
            setDocument(new JTextFieldFilter(JTextFieldFilter.DATE,10));
            
            addFocusListener(new FocusAdapter()  {
                  public void focusLost(FocusEvent e)  {
                        if (!e.isTemporary() && isEnabled() ) {
                              String inText= getText();
                              String outText="";

                              if (inText == null || inText.equals("")) return;
                              
                          inText = inText.replace('-', '/');
                          inText = inText.replace('.', '/');

                              for (int i=0;i<inFormats.length;i++)  {
                                    try  {
                                          date = inFormats[i].parse(inText);
                                          break;
                                    }
                                    catch(ParseException ex)  {
                                          if (i == inFormats.length-1)  {
                                                blowUp(inText);                                          
                                                return;
                                          }
                                    }
                              }
                              outText = outFormat.format(date);
                              setText(outText);
                        }
                  }
            });
      }
     
     private void blowUp(String text)  {
          Toolkit.getDefaultToolkit().beep();
          System.out.println("Illegal Text: " + text);
          SwingUtilities.invokeLater(new Runnable()  {
               public void run()  {
                    requestFocus();
                    selectAll();
               }
          });
     }

     public static void main(String args[])  {

          JFrame f= new JFrame("TextField Test");
          f.addWindowListener(new WindowAdapter()  {
               public void windowClosing(WindowEvent e)  {
                    System.exit(0);
               }
          });
          JPanel panel= new JPanel();
          panel.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
          panel.setLayout(new GridLayout(0,2,10,10));
         
          JDateField tf1;
          JLabel l1;

          l1 = new JLabel("Date:");
          tf1 = new JDateField();
          panel.add(l1);
          panel.add(tf1);
          panel.add(new JButton("OK"));

          f.setContentPane(panel);
          f.pack();
          f.setVisible(true);
     }
}
0
 
LVL 3

Expert Comment

by:kostello
Comment Utility
Hmmm, requirements creep.  OK, I think a decent solution may lie in a combination of my first 2 posts.  You need to parse apart the string to get the components, then put these components together in a known way.  I think this is better than trying to exhaustively figure out all of your cases beforehand and creating formatters for them - there's too much room for error in this method in general.  As you note, for internationalization, changes need to be made because of the difference in order of the substrings.  I'm not sure if the local timezone aspects of the date formatter will take care of this - I doubt this, since you're giving it a set string.  So, you still need to take care of this separately.  I don't have 1.3 installed, so I can't test this with your example.

import java.util.Date;
import java.util.StringTokenizer;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;

public class GetDate {
    public static void main(String[] args) {
        String date;
        if (args.length >= 1) {
            date = args[0];
        } else {
            date = new String("12/31/00");
        }

        // Convert to the same separator - easier for parsing
        date = date.replace('-', '/');
        date = date.replace('.', '/');

        SimpleDateFormat sdf = null;
        if (date.indexOf('/') == -1) {
            // Without separators, I think you'll have to assume 1 of 2 forms
            if (date.length() == 6) {
                sdf = new SimpleDateFormat("MMddyy");
            } else if (date.length() == 8) {
                sdf = new SimpleDateFormat("MMddyyyy");
            }
        } else {
            // Convert the date to a 1 of 2 known types
            StringTokenizer st = new StringTokenizer(date, "/");
            // This won't catch 2 separators in a row, but...
            if (st.countTokens() != 3) {
                System.out.println("Invalid string");
                System.exit(0);
            }
            Integer month = new Integer(st.nextToken());
            Integer day = new Integer(st.nextToken());
            // We still have to handle the year as a special case
            String yearString = st.nextToken();
            Integer year = new Integer(yearString);
            DecimalFormat df = new DecimalFormat("00");
            DecimalFormat df2 = new DecimalFormat("0000");
            if (yearString.length() <= 2) {
                date = df.format(month.intValue()) + "/" +
                       df.format(day.intValue()) + "/" +
                       df.format(year.intValue());
                sdf = new SimpleDateFormat("MM/dd/yy");
            } else {
                date = df.format(month.intValue()) + "/" +
                       df.format(day.intValue()) + "/" +
                       df2.format(year.intValue());
                sdf = new SimpleDateFormat("MM/dd/yyyy");
            }
        }
        try {
            Date d = sdf.parse(date);
            System.out.println("Date is " + d);
        } catch (java.text.ParseException e) {
            System.out.println("Invalid string in format");
        }
    }
}
0
 
LVL 3

Accepted Solution

by:
kostello earned 310 total points
Comment Utility
Allright, sometimes I'm stupid & don't read documentation enough.  Basically, the parser in SimpleDateFormat will try to match your string best to the string it's given as a format.  Thus, if you only give it "M", it will try to match the string to a month, no matter how long the string is.  So, a little (ahem) better & simpler solution is:

import java.util.Date;
import java.text.SimpleDateFormat;

public class GetDate {
    public static void main(String[] args) {
        String date;
        if (args.length >= 1) {
            date = args[0];
        } else {
            date = new String("12/31/00");
        }

        date = date.replace('.', '/');
        date = date.replace('-', '/');
        SimpleDateFormat sdf = null;
        if (date.indexOf('/') == -1) {
            // This will assume first 2 are month, second 2 are day, all rest are year
            sdf = new SimpleDateFormat("MMddyy");
        } else {
            // Using only 1 pattern element allows 1 or more characters between the separators
            sdf = new SimpleDateFormat("M/d/y");
        }
        try {
            Date d = sdf.parse(date);
            System.out.println("Date is " + d);
        } catch (java.text.ParseException e) {
            System.out.println("Invalid string in format");
        }
    }
}

Look before you leap, I guess is the lesson learned here.  Still don't think it handles i8n well, but it's certainly better than my last solution.
0
 
LVL 7

Author Comment

by:conick
Comment Utility
Adjusted points from 200 to 310
0
 
LVL 7

Author Comment

by:conick
Comment Utility
Perfect.
Thats exactly what I was looking for.

For my own sanity I was hoping the solution would be a LITTLE harder than it was.  I read the docs before posting, but I misunderstood what it was saying and glossed over the rest.

Thanks a lot for your help and patience.
0

Featured Post

IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

Join & Write a Comment

An old method to applying the Singleton pattern in your Java code is to check if a static instance, defined in the same class that needs to be instantiated once and only once, is null and then create a new instance; otherwise, the pre-existing insta…
Java contains several comparison operators (e.g., <, <=, >, >=, ==, !=) that allow you to compare primitive values. However, these operators cannot be used to compare the contents of objects. Interface Comparable is used to allow objects of a cl…
Viewers will learn about the different types of variables in Java and how to declare them. Decide the type of variable desired: Put the keyword corresponding to the type of variable in front of the variable name: Use the equal sign to assign a v…
This tutorial will introduce the viewer to VisualVM for the Java platform application. This video explains an example program and covers the Overview, Monitor, and Heap Dump tabs.

728 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

9 Experts available now in Live!

Get 1:1 Help Now