Solved

SWPM seeks pretty...

Posted on 2000-05-03
11
280 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
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 6
  • 5
11 Comments
 
LVL 3

Expert Comment

by:kostello
ID: 2773869
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
ID: 2774143
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
ID: 2774259
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
Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
LVL 3

Expert Comment

by:kostello
ID: 2774395
kostello changed the proposed answer to a comment
0
 
LVL 3

Expert Comment

by:kostello
ID: 2774400
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
 
LVL 7

Author Comment

by:conick
ID: 2774483
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
ID: 2774544
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
ID: 2774767
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
ID: 2774949
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
ID: 2775055
Adjusted points from 200 to 310
0
 
LVL 7

Author Comment

by:conick
ID: 2775056
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

Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

Title # Comments Views Activity
spring jars download 1 68
Java string replace 11 79
JAVA API design with micro service cloud in mind 1 115
How to access inner class in java (android)?? 13 22
This was posted to the Netbeans forum a Feb, 2010 and I also sent it to Verisign. Who didn't help much in my struggles to get my application signed. ------------------------- Start The idea here is to target your cell phones with the correct…
Introduction This article is the first of three articles that explain why and how the Experts Exchange QA Team does test automation for our web site. This article explains our test automation goals. Then rationale is given for the tools we use to a…
The viewer will learn how to implement Singleton Design Pattern in Java.
This tutorial explains how to use the VisualVM tool for the Java platform application. This video goes into detail on the Threads, Sampler, and Profiler tabs.
Suggested Courses

752 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