J. Shupperd
J. Shupperd

Reputation: 53

How to validate a string if it is a correct format in Java

I am currently writing a validation method in Java to check if a string is in one of a few different format to be changed into a date.

The formats that I want it to accept are the following: MM/DD/YY , M/DD/YY, MM/D/YY, and M/D/YY.

I was testing the first format and every time it was telling me it was not valid even when I entered in a valid date.

Here is what my current code looks like:

public class IsDateFormatValid
{
   public boolean isValid(String date)
   {
      boolean result = true;
      if(date.length()>8||date.length()<6)
      {
         result= false;
      }
      if(date.length()==8)
      {
         if((Character.toString(date.charAt(2))!= "/")||(Character.toString(date.charAt(5))!="/"))
         {
            result=false;
         }   
      }
      if(date.length()==7)
      {
         if((Character.toString(date.charAt(2))!="/"&&Character.toString(date.charAt(1))!="/") ||(Character.toString(date.charAt(3))!="/"&&Character.toString(date.charAt(4))!= "/"))
         {
            result=false;
         }   
      }

      return result;   
   }
}   

I still need to put in the conditions for the last format case. I did a debug method and saw that the part that always returning false was the line that said: if((Character.toString(date.charAt(2))!= "/")||(Character.toString(date.charAt(5))!="/"))

The main point of this question is trying to check it against multiple formats not just a singular one how most other questions on here ask about.

Upvotes: 4

Views: 8288

Answers (4)

Anonymous
Anonymous

Reputation: 86232

TL;DR

  1. Your formats are (variants of) the same format.
  2. Use a formatter for validating whether a date string is valid.
    • In particular use DateTimeFormatter from java.time.
  3. Don’t use != for comparing strings.

They are variants of the same format

Unless I completely misunderstood, the variation of formats are that the month can be one or two digits and the day of month can be one or two digits. We can handle that as one single format. Like two other answers I believe that for most purposes it’s better to perform a full validation of whether we’ve got a valid date. Not just a validation of the number of digits and the positions of the slashes (which, as the answer by Susannah Potts says, a regex can do). So what we need to do is:

  1. Define a formatter
  2. Try to parse; if an exception is thrown, the string wasn’t a valid date string in any of your format variants.

1. Define a formatter

    DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("M/d/uu")
            .withResolverStyle(ResolverStyle.STRICT);

One pattern letter M will match a month number in either one or two digits. Similar for d for day of month.

Let’s see this in action first.

    System.out.println(LocalDate.parse("12/28/66", dateFormatter));
    System.out.println(LocalDate.parse("11/06/37", dateFormatter));
    System.out.println(LocalDate.parse("11/2/58", dateFormatter));
    System.out.println(LocalDate.parse("03/14/35", dateFormatter));
    System.out.println(LocalDate.parse("05/04/68", dateFormatter));
    System.out.println(LocalDate.parse("09/3/06", dateFormatter));
    System.out.println(LocalDate.parse("5/23/99", dateFormatter));
    System.out.println(LocalDate.parse("1/06/00", dateFormatter));
    System.out.println(LocalDate.parse("2/8/76", dateFormatter));

Output is:

2066-12-28
2037-11-06
2058-11-02
2035-03-14
2068-05-04
2006-09-03
2099-05-23
2000-01-06
2076-02-08

2.Try to parse and trap for exception

    String dateString = "12/28/66";
    try {
        LocalDate.parse(dateString, dateFormatter);
        System.out.println(dateString + " is valid");
    } catch (DateTimeParseException dtpe) {
        System.out.println(dateString + " is not valid: " + dtpe.getMessage());
    }
12/28/66 is valid

Try it with a date string that doesn’t follow any of the formats:

    String dateString = "ab/cd/ef";
ab/cd/ef is not valid: Text 'ab/cd/ef' could not be parsed at index 0

java.time

I am recommending java.time, the modern Java date and time API. The classes Date and SimpleDateFormat used in one answer are poorly designed and long outdated, the latter in particular notoriously troublesome. java.time is so much nicer to work with.

What went wrong in your code?

There are two problems.

One, as you said, this if condition always evaluates to true, which causes your result to be set to false:

         if((Character.toString(date.charAt(2))!= "/")||(Character.toString(date.charAt(5))!="/"))

Comparing strings with != doesn’t work the way you expect. This tests whether the two are the same string object, not whether the strings are unequal. Character.toString() creates a new string object, so this will always be another object than "/". So even though the string from Character.toString() contains a slash too, != will evaluate to true.

Two, you are not validating that the characters before, between and after the slashes are digits.

Links

Upvotes: 0

GhostCat
GhostCat

Reputation: 140427

A simple approach that probably is expensive; but somehow against good practices goes like this:

  1. Create a list of Formatter objects (one for each allowed pattern).
  2. Iterate that list; and try if you can parse your date string using each formatter (with lenient set to false!). If you get one that doesn't throw an exception, you know that the incoming string conforms to a valid format.

For parsing with formats, you can checkout this question.

As Peter is pointing out, this solution isn't threadsafe. So you would need to look into that question to deal with that.

On the other hand, when doing it like paulsm4 suggests; you avoid the threading issue ... but unfortunately, you are then creating a lot of formatter objects; that you immediately throw away afterwards. Talk about wasting CPU cycles and creating "memory garbage" there.

Option 2; less "expensive" is to come up with one (or more) several regular expressions that would match strings of the given format. But of course, it isn't as easy as the one suggested from Susannah; as you know, you really would want to reject a string like "55/66/77" which perfectly matches a simple regex that just checks for "two digits dash two digits dash two digits".

So, yes, option 1 is expensive; so the question here is: how good should your validation be? Do you want to reject dates that are syntactically "correct", but that are "semantically" wrong, like "02/29/15" (2015 not being a leap year!)?!

Update: thinking about this, a nice solution goes like:

  1. Create a Map<Regex, String> where the value would be a string that can be used as "formatter input"; and the corresponding key is a regex that "matches" that format
  2. Iterate the map keys
  3. If no key matches: done; you know that your input has an unknown/invalid format
  4. If a key matches: fetch the map value for that key and use it to create a non-lenient formatter object. Now you can check if the formatter can parse your input. If so: input is a valid date in one of your formats.

Upvotes: 5

paulsm4
paulsm4

Reputation: 121649

You might want to iterate through possible formats, like this:

EXAMPLE:

private static String[] date_formats = {
        "yyyy-MM-dd",
        "yyyy/MM/dd",
        "dd/MM/yyyy",
        "dd-MM-yyyy",
        "yyyy MMM dd",
        "yyyy dd MMM",
        "dd MMM yyyy",
        "dd MMM yyyy"
};

/**
 * A brute-force workaround for Java's failure to accept "any arbitrary date format"
 */
public static Date tryDifferentFormats (String sDate) {
    Date myDate = null;
    for (String formatString : date_formats) {
        try {
            SimpleDateFormat format = new SimpleDateFormat(formatString);
            format.setLenient(false);
            myDate = format.parse(sDate);
            break;
        }
        catch (ParseException e) {
            // System.out.println("  fmt: " + formatString + ": FAIL");
        }
    }
    return myDate;
}

Upvotes: 6

Susannah Potts
Susannah Potts

Reputation: 827

Try matching against a regex, it will reduce the work you're doing.

if(date.matches("\\d{1-2}\\\\d{1-2}\\\\d{1-2}")){
    // ..do something
}

Upvotes: 3

Related Questions