How do I validate the correct date format pattern? Format patterns, not dates from strings

Andre Sianipar :

I use Spring in my project and the property file looks like this.

reportPRM.field=\
TrxDateTime   |6    |yyMMdd;\
TrxDateTime   |6    |HHmmss;\
MerchantID    |16   |Space;\

I have one property with multiple lines. Users can change the date format pattern for TrxDateTime. I need to validate the correctness of the pattern. How to do it?

I have tried the code below.

public static boolean validatePattern(String template) {
    try {
        new SimpleDateFormat(template);

        return true;
    } catch (IllegalArgumentException e) {
        logger.error(ThrowableHelper.getMessage(e));
    }

    return false;
}

But it turns out to giving true for input "0".

Edit. Added unit test.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AppConfig.class)
@TestPropertySource(properties = "scheduler.enable=false")
public class DateFormatTest {

    @Test
    public void testDateFormat() {
        assertTrue(StringHelper.validatePattern("0"));
        assertTrue(StringHelper.validatePattern("abcde"));

        assertTrue(StringHelper.validatePattern("yyyyMMdd"));
        assertTrue(StringHelper.validatePattern("HHmmss"));
    }

}

For input "0", "yyyyMMdd", "HHmmss" returns true and for "abcde" returns false. But I don't think this is enough. Date format patterns must comply to general standard patterns.

Edit 2. This is the complete properties.

reportPRM.field=\
TrxDateTime                                     |6  |yyMMdd;\
TrxDateTime                                     |6  |HHmmss;\
MerchantID                                      |16 |Space;\
CompanyName                                     |25 |Space;\
                                                |2  |ID;\
CardNo                                          |19 |0;\
                                                |4  |CPAY;\
Amount                                          |15 |0;\
{P: RespDesc, R: RespDescReversal}              |6  |0;\
{P: DSPRetCodePayment, R: DSPRetCodeReversal}   |3  |0;

I need to iterate through each line and check if the value in column 3 is pattern for date format. But the function above gives true for "0".

Ole V.V. :

Skip the SimpleDateFormat class and its friends like Date. Those classes were poorly designed and are now long outdated. Instead use DateTimeFormatter and other classes from java.time, the modern Java date and time API.

Your exact requirements are not clear. I present code for the following requirements. The date pattern should be a valid pattern with DateTimeFormatter and should contain at least one pattern letter for dates, for example yyMMdd or just d. Era (BC/AD) alone is not enough. Letters in single quotes do not count since they don’t work as pattern letters but as literals. The time pattern should similarly contain at least one pattern letter for time of day, not time zone, and fraction of second or nano of second is not enough.

For checking the validity the try/catch block you’ve got is fine. The only issue with it is that for example 0 works nicely as a valid pattern, but you want to reject it. So in addition I use a regular expression to check that at least one relevant pattern letter is present outside single quotes. The regular expression accepts any run of characters with an even number of single quotes before and after the letter.

private static final String DATE_PATTERN_LETTERS = "uyDMLdgQqYwWEecF";
private static final Pattern DATE_PATTERN_PATTERN = requireAtLeastOne(DATE_PATTERN_LETTERS);
private static final String TIME_PATTERN_LETTERS = "ahKkHmsAN";
private static final Pattern TIME_PATTERN_PATTERN = requireAtLeastOne(TIME_PATTERN_LETTERS);

/**
 * @return a pattern that matches a string that contains
 *          at least one of the letters in {@code requiredLetters} not within single quotes
 */
private static Pattern requireAtLeastOne(String requiredLetters) {
    return Pattern.compile("[^']*(?:'[^']*'[^']*)*[" + requiredLetters + "][^']*(?:'[^']*'[^']*)*");
}

public static boolean validateDatePattern(String template) {
    if (! isValidPattern(template)) {
        return false;
    }
    return DATE_PATTERN_PATTERN.matcher(template).matches();
}

public static boolean validateTimePattern(String template) {
    if (! isValidPattern(template)) {
        return false;
    }
    return TIME_PATTERN_PATTERN.matcher(template).matches();
}

private static boolean isValidPattern(String template) {
    try {
        DateTimeFormatter.ofPattern(template);
        return true;
    } catch (IllegalArgumentException iae) {
        return false;
    }
}

Let’s try it with some date patterns:

    System.out.println(validateDatePattern("0"));
    System.out.println(validateDatePattern("abcde"));
    System.out.println(validateDatePattern("yyyyMMdd"));
    System.out.println(validateDatePattern("HHmmss"));
    System.out.println(validateDatePattern("'yMd in quotes'"));
    System.out.println(validateDatePattern("yMd 'outside quotes'"));

Output is, with my comments:

false  // 0 fails because it doesn’t contain a pattern letters for a date
false  // abcde fails because b is not a valid pattern letter
true   // yyyyMMdd is fine
false  // HHmmss fails because it’s for times, not dates
false  // 'yMd in quotes' fails because yMd are in quotes
true   // yMd 'outside quotes' is fine

Let’s see a few time pattern strings too:

    System.out.println(validateTimePattern("0"));
    System.out.println(validateTimePattern("yyyyMMdd"));
    System.out.println(validateTimePattern("HHmmss"));

Output:

false
false
true

Can you give a brief explanation of the regex you provided?

Sure. [^']* matches 0 or more characters that are not ' (single quote). Next (?:'[^']*'[^']*)* matches 0 or more sequences where each sequence is one single quote, 0 or more non-single-quotes, one single quote and 0 or more non-single-quotes. This makes sure that there is an even number of single quotes (and since they come in pairs, this in turn makes sure that the following pattern letter is not within single quotes). It also matches no single quotes at all since there may be 0 such sequences. Next I put the required letters inside [ and ] to require just one of them. Finally I repeat [^']*(?:'[^']*'[^']*)*, the same as before the letter.

Link: Oracle tutorial: Date Time explaining how to use java.time.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=147108&siteId=1