5

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.

GhostCat
  • 137,827
  • 25
  • 176
  • 248
J. Shupperd
  • 63
  • 1
  • 6
  • 1
    Those questions makes me angry and very sad. If it is a date it should be a date. If it is not a date it should be `null`. – Grim Aug 10 '16 at 16:36
  • Good luck guessing what 1/2/06 means. :) – Eiko Aug 11 '16 at 07:51
  • Very simple when you know it: `LocalDate.parse(yourDateString, DateTimeFormatter.ofPattern("M/d/uu"))` will accept all your four format variants and will throw a `DateTimeParseException` if the string is invalid. – Ole V.V. Nov 07 '19 at 20:55
  • For the record, this question was previously closed as a duplicate of [Java: Check the date format of current string is according to required format or not \[duplicate\]](https://stackoverflow.com/questions/20231539/java-check-the-date-format-of-current-string-is-according-to-required-format-or). I have reopened. – Ole V.V. Nov 09 '19 at 02:16

4 Answers4

7

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;
}
paulsm4
  • 114,292
  • 17
  • 138
  • 190
  • 1
    @PeterRader Threading does not appear to be an issue here. The `SimpleDateFormat format ` here is a method local variable. – bradimus Aug 10 '16 at 16:22
  • The one problem with that: the really expensive part is the **creation** of that formatter object. And you keep doing that all the time. So, when you have m formats; and n incoming strings ... that gives you **n*m** new formatters. If m is large; boy, you are burning a lot of cpu cycles; and possible creating a lot of "garbage" here. – GhostCat Aug 10 '16 at 16:27
6

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.
Community
  • 1
  • 1
GhostCat
  • 137,827
  • 25
  • 176
  • 248
4

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
}
Susannah Potts
  • 837
  • 1
  • 12
  • 32
  • 2
    Keep in mind this will not check if the actual values are valid. You could easily pass 99/99/99 and it would say it's valid. – Susannah Potts Aug 10 '16 at 16:19
  • 00 is not a valid month – Grim Aug 10 '16 at 16:20
  • 2
    And the person is just trying to validate the slashes and length, not the actual content. The downvotes are thus unnecessary as this adequately accomplishes the task. – Susannah Potts Aug 10 '16 at 16:21
  • 2
    @SusannahPotts That isn't really clear. But your statement made me rethink my answer and come with "the best of both worlds"; a solution that uses a "cheap" approach to tell you if a date is "fully" valid. – GhostCat Aug 10 '16 at 16:36
  • @GhostCat The existing code suggests it, as the function is specifically named `IsDateFormatValid` so my thinking is that validation of the date existing is to occur in a different function. But yes, your answer is thorough, and certainly addresses doing both format and content verification. Though, doing content verification of a variable length date format via Regex is nasty and would be horrible so I'd take your option one any day haha – Susannah Potts Aug 10 '16 at 16:39
  • @SusannahPotts No comment, i have GhostCat to fight for me :D – Grim Aug 10 '16 at 16:40
  • 1
    @SusannahPotts Well, one could go really crazy on that. Like thinking about a "max length" for each format; and doing a simple length-check upfront; before applying any of the regexes. And you are correct, the name of function is actually giving a precise meaning. – GhostCat Aug 10 '16 at 16:41
  • @PeterRader I think we're discussing the semantics of a question...not fighting. Especially when I openly say his solution covers more than mine and that mine is the quick and dirty solution. – Susannah Potts Aug 10 '16 at 16:41
  • 2
    @PeterRader I am Zen, I am peace. I am no fighting person. Ask the 10 newbies that I welcome here every day with close requests and downvotes. – GhostCat Aug 10 '16 at 16:44
  • @SusannahPotts You are right, its not a fight, its a discussion. – Grim Aug 10 '16 at 16:44
  • @GhostCat The Tao is great and there is harmony in the world. – Grim Aug 10 '16 at 16:46
  • @PeterRader Well, not necessarily. But there will be eternal peace upon me visiting that place. – GhostCat Aug 10 '16 at 16:51
0

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

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161