DateTimeFormater with optional section - java

I'm writing an application to manipulate text data, Which will change the content of input string and create new output String based on the format of input string.
I encounter some problem with recognized the date time string. Based on the document the input date time may have some optional section, here the sample pattern:
yyyy[MM[dd[HHmm]]][Z]
So after some digging on the web, my first attempt to use the parseBest function.
public boolean checkFormatDate(string input){
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy[MM[dd[HHmm]]][Z]");
try {
TemporalAccessor temporalAccessor = formatter.parseBest(input, ZonedDateTime::from, LocalDateTime::from, LocalDate::from);
return true;
} catch (Exception e) {
return false;
}
}
But the code above failed with these case:
1900
190001
190001011440
My suspect is that the queries that parse the parseBest method is not correct.
Can someone help me with this.
Edit:
Here is the exception log:
java.time.format.DateTimeParseException: Text '190001011440' could not be parsed at index 0
at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1947)
at java.time.format.DateTimeFormatter.parseBest(DateTimeFormatter.java:1895)

The problem seems to be the pattern yyyy, which is creating a formatter as follows (System.out.println(formatter)):
Value(YearOfEra,4,19,EXCEEDS_PAD)[Value(MonthOfYear,2)[Value(DayOfMonth,2)[Value(HourOfDay,2)Value(MinuteOfHour,2)]]][Offset(+HHMM,'+0000')]
Note the 4,19 in the first part - minimum width of 4 and max of 19. Build the formatter as follows and it should work:
DateTimeFormatterBuilder b = new DateTimeFormatterBuilder();
formatter = b.appendValue(ChronoField.YEAR_OF_ERA, 4, 4, SignStyle.EXCEEDS_PAD).appendPattern("[MM[dd[HHmm]]][Z]").toFormatter();

Related

How to check if String like "08-Nov-2011" is a valid date using java? [duplicate]

This question already has answers here:
I have a date in(string) in dd-mon-yyyy format and I want to compare this date with system date
(4 answers)
Closed 3 years ago.
I want to check if input string is valid date or not.
String be like :-
"08-Nov-2011"
"21 Mar 2019"
java code :-
boolean checkFormat;
String input = "08-Nov-2011";
if (input.matches("([0-9]{2})/([0-9]{2})/([0-9]{4})"))
checkFormat=true;
else
checkFormat=false;
System.out.println(checkFormat);
I am thinking of splitting and then check by its length like if first split word be of length 2, second split word be of length 3 and last word be of length 4.
But if Input String be like :-
AB-000-MN89
Then here it will fails.
Please help me to Solve this.
As stated in several comments, the best way to find out if your date is valid is to try to parse it with a java.time.format.DateTimeFormatter to a date object of type LocalDate.
You can support several patterns and/or use built-in ones from the DateTimeFormatter class:
public static void main(String[] args) {
// provide some patterns to be supported (NOTE: there are also built-in patterns!)
List<String> supportedPatterns = new ArrayList<>();
supportedPatterns.add("dd.MMM.yyyy");
supportedPatterns.add("dd MMM yyyy");
supportedPatterns.add("dd-MMM-yyyy");
supportedPatterns.add("dd/MMM/yyyy");
supportedPatterns.add("ddMMMyyyy");
// define some test input
String input = "08-Nov-2011";
// provide a variable for each, pattern and the date
String patternThatWorked = null;
LocalDate output = null;
// try to parse the input with the supported patterns
for (String pattern : supportedPatterns) {
try {
output = LocalDate.parse(input, DateTimeFormatter.ofPattern(pattern));
// until it worked (the line above this comment did not throw an Exception)
patternThatWorked = pattern; // store the pattern that "made your day" and exit the loop
break;
} catch (DateTimeParseException e) {
// no need for anything here but telling the loop to do the next try
continue;
}
}
// check if the parsing was successful (output must have a value)
if (output != null) {
System.out.println("Successfully parsed " + input
+ " to " + output.format(DateTimeFormatter.ISO_LOCAL_DATE) // BUILT-IN pattern!
+ " having used the pattern " + patternThatWorked);
}
}
This outputs
Successfully parsed 08-Nov-2011 to 2011-11-08 having used the pattern dd-MMM-yyyy
You can use SimpleDateFormat with the following pattern: dd-MMM-yyyy. Follow the link to see possible patterns.
SimpleDateFormat may throw ParseException where argument is invalid. So, you can wrap that invocation with a try-catch block.
As an example:
private final String pattern = "dd-MMM-yyyy";
private final SimpleDateFormat sdf = new SimpleDateFormat(pattern);
public boolean validateDate(String date) {
try {
sdf.parse(date);
return true;
} catch (ParseException e) {
return false;
}
}
If you have different formats as 08-Nov-2011 and 08 Nov 2011, try to unify them (by removing dashes from the first one, for instance).
A very crude regex for this would be:
\d{2}[- ]\w{3}[- ]\d{4}
08-Nov-2011
21 Mar 2019
Example here: https://regex101.com/r/01vslq/1.
However, it would probably be better to get a regex that don't allow dates like 99-Nov-9999, and so you could try a more thorough one here. However, even better, probably using Java date parsing -- this isn't a great use case for regex if you need to do legitimate date parsing -- for example, February shouldn't allow the numbers 29, 30, 31, and lots of other nuances. Use java.time.DateTimeFormatter (mentioned in the comment above).

Java 8 DateTimeFormatterBuilder().appendOptional not working

My requirement is to validate that a date String is in the correct format based on a set of valid formats specified.
Valid formats:
MM/dd/yy
MM/dd/yyyy
I created a simple test method that uses the Java 8 DateTimeFormatterBuilder to create a flexible formatter that supports multiple optional formats. Here is the code:
public static void test() {
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendOptional(DateTimeFormatter.ofPattern("MM/dd/yy"))
.appendOptional(DateTimeFormatter.ofPattern("MM/dd/yyyy"))
.toFormatter();
String dateString = "10/30/2017";
try {
LocalDate.parse(dateString, formatter);
System.out.println(dateString + " has a valid date format");
} catch (Exception e) {
System.out.println(dateString + " has an invalid date format");
}
}
When I run this, here is the output
10/30/2017 has an invalid date format
As you see in the code, the valid date formats are MM/dd/yy and MM/dd/yyyy.
My expectation was that the date 10/30/2017 should be valid as it matches MM/dd/yyyy. However, 10/30/2017 is being reported as invalid.
What is going wrong ? Why is this not working ?
I also tried
.appendOptional(DateTimeFormatter.ofPattern("MM/dd/yy[yy]"))
in place of
.appendOptional(DateTimeFormatter.ofPattern("MM/dd/yy"))
.appendOptional(DateTimeFormatter.ofPattern("MM/dd/yyyy"))
but still had the same issue.
This code runs as expected if I use:
String dateString = "10/30/17";
in place of
String dateString = "10/30/2017";
I have 2 questions
What is going wrong here ? Why is it not working for "10/30/2017" ?
Using Java 8, how to correctly create a flexible Date formatter (a formatter that supports multiple optional formats) ? I know the use of [] to create optional sections in the pattern string itself. I'm looking for something more similar to what I am trying (avoiding [] inside the pattern string and using separate optional clauses for each separate format string)
The formatter does not work the way you expect, the optional part means
if there is nothing extra attached to the first pattern (e.g., "MM/dd/yy"), that is fine,
if there is something extra, it needs to match the second pattern (e.g, "MM/dd/yyyy")
To make it a bit clearer, try to run the sample code below to understand it better:
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendOptional(DateTimeFormatter.ofPattern("MM/dd/yy"))
.appendOptional(DateTimeFormatter.ofPattern("MM/dd/yyyy"))
.toFormatter();
String[] dateStrings = {
"10/30/17", // valid
"10/30/2017", // invalid
"10/30/1710/30/2017", // valid
"10/30/201710/30/17" // invalid
};
for (String dateString : dateStrings) {
try {
LocalDate.parse(dateString, formatter);
System.out.println(dateString + " has a valid date format");
} catch (Exception e) {
System.err.println(dateString + " has an invalid date format");
}
}
==
10/30/17 has a valid date format
10/30/1710/30/2017 has a valid date format
10/30/2017 has an invalid date format
10/30/201710/30/17 has an invalid date format
==
This is only a simple solution, if performance is of your concern, the validation by catching the parsing exception should be the last resort
you may check the string by length or regex first before doing the date string parsing
you may also replace the stream with a method containing a simple for loop, etc.
String[] patterns = { "MM/dd/yy", "MM/dd/yyyy" };
Map<String, DateTimeFormatter> formatters = Stream.of(patterns).collect(Collectors.toMap(
pattern -> pattern,
pattern -> new DateTimeFormatterBuilder().appendOptional(DateTimeFormatter.ofPattern(pattern)).toFormatter()
));
String dateString = "10/30/17";
boolean valid = formatters.entrySet().stream().anyMatch(entry -> {
// relying on catching parsing exception will have serious expense on performance
// a simple check will already improve a lot
if (dateString.length() == entry.getKey().length()) {
try {
LocalDate.parse(dateString, entry.getValue());
return true;
}
catch (DateTimeParseException e) {
// ignore or log it
}
}
return false;
});
The builder's appendValueReduced() method was designed to handle this case.
When parsing a complete value for a field, the formatter will treat it as an absolute value.
When parsing an partial value for a field, the formatter will interpret it relative to a base that you specify. For example, if you want two-digit years to be interpreted as being between 1970 and 2069, you can specify 1970 as your base. Here's an illustration:
LocalDate century = LocalDate.ofEpochDay(0); /* Beginning Jan. 1, 1970 */
DateTimeFormatter f = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ofPattern("MM/dd/"))
.appendValueReduced(ChronoField.YEAR, 2, 4, century)
.toFormatter();
System.out.println(LocalDate.parse("10/30/2017", f)); /* 2017-10-30 */
System.out.println(LocalDate.parse("10/30/17", f)); /* 2017-10-30 */
System.out.println(LocalDate.parse("12/28/1969", f)); /* 1969-12-28 */
System.out.println(LocalDate.parse("12/28/69", f)); /* 2069-12-28 */

Elegant solution to parse date

I want to parse some text into a date. However, there is no guarantee that the text has the desired format. It may be 2012-12-12 or 2012 or even .
Currently, I am down the path to nested try-catch blocks, but that's not a good direction (I suppose).
LocalDate parse;
try {
parse = LocalDate.parse(record, DateTimeFormatter.ofPattern("uuuu/MM/dd"));
} catch (DateTimeParseException e) {
try {
Year year = Year.parse(record);
parse = LocalDate.from(year.atDay(1));
} catch (DateTimeParseException e2) {
try {
// and so on
} catch (DateTimeParseException e3) {}
}
}
What's an elegant solution to this problem? Is it possible to use Optionals which is absent in case a exception happened during evaluation? If yes, how?
This can be done in an elegant fashion using DateTimeFormatter optional sections. An optional section is started by the [ token and is ended by the ] token.
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("[yyyy[-MM-dd]]");
System.out.println(formatter.parse("2012-12-12")); // prints "{},ISO resolved to 2012-12-12"
System.out.println(formatter.parse("2012")); // prints "{Year=2012},ISO"
System.out.println(formatter.parse("")); // prints "{},ISO"
The DateTimeFormatterBuilder class contains the building blocks to make this work:
LocalDate now = LocalDate.now();
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
.appendPattern("[uuuu[-MM[-dd]]]")
.parseDefaulting(ChronoField.YEAR, now.getYear())
.parseDefaulting(ChronoField.MONTH_OF_YEAR, now.getMonthValue())
.parseDefaulting(ChronoField.DAY_OF_MONTH, now.getDayOfMonth())
.toFormatter();
System.out.println(LocalDate.parse("2015-06-30", fmt));
System.out.println(LocalDate.parse("2015-06", fmt));
System.out.println(LocalDate.parse("2015", fmt));
System.out.println(LocalDate.parse("", fmt));
The parseDefaulting() method allows a default value to be set for a specific field. In all cases, a LocalDate can be parsed from the result, because enough information is available.
Note also the use of "[...]" sections in the pattern to define what is optional.

Java SimpleDateFormat is parsing wrong dates

I have written the following code snippet:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.setLenient(false);
currentString = currentString.trim();
try{
Date date = sdf.parse(currentString);
} catch (java.text.ParseException e) {
return "";
}
I am expecting it to parse the date in format yyyy-MM-dd ie. it should parse date like 2013-10-28.
Though it is working fine, it is also parsing wrong inputs like 2013-10-28aaab. Ideally it should throw the exception when such kind of illegal date is given.
How can I restrict such illegal Date Patterns?
use a regex to match the input
something like
"/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/"
Just check the String length. BTW, you should set lenient to true, otherwise non valid dates (2013-02-31) will be allowed.

ignore any white space or new line in java date compare

I am trying to compare two dates in java. While the following code works fine, I would like to handle situations where there may be some alterations in the date format of the input dates.
For example, in the below code, the date format of the two dates are as yyyy/mm/dd hh:mm:ss am. But sometimes there are some additional white space/new line characters found in the input date and this causes exception.
java.text.ParseException: Unparseable date: "02/14/2013
07:00:00 AM"
The following is the code am trying to execute.
try
{
Date date1 = (Date)DATE_FORMAT_yyyy_mm_dd_hh_mm_ss.parse(slaTime); // usually the data comes as 2013/02/03 09:09:09 AM
Date date2 = (Date)DATE_FORMAT_yyyy_mm_dd_hh_mm_ss.parse(actualTime);// usually the data comes as 2013/02/03 09:06:09 AM
// a error occurs
if(date1.before(date2))
{
return "True";
}
else
{
return "False";
}
}
catch (ParseException e)
{
e.printStackTrace();
}
how to handle this?
One of the simplest solutions is to strip all whitespace from the String version of the date before you parse it. Alter your date format to not include any spaces (yyyy/MM/ddhh:mm:ssaaa), and use this to parse the stripped string.
final DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/ddhh:mm:ssaaa");
final String dateStr = "02/14/2013 07:00:00" +
"\n AM";
Date failingDate = dateFormat.parse(dateStr);
Date passingDate = dateFormat.parse(dateStr.replaceAll("\\s",""));
For Month in year Use M instead of m
Correct date format would be yyyy/MM/dd hh:mm:ss aaa. And If there is any additional space or new line then you must remove it other wise it will failed to parse your string to date, your should exact match with format .
I would suggest you to remove all space and new line character then parse it.
you can use format like - yyyy/MM/ddhh:mm:ssaaa where there is no space. And replaceAll your space and new Line with empty String.
Date date = new SimpleDateFormat("yyyy/MM/ddhh:mm:ssaaa").parse("2013/02/1407:00:00AM");
and you actual code could be like -
dateString = dateString.replaceAll("\\s","");
SimpleDateFormat("yyyy/MM/ddhh:mm:ssaaa").parse(dateString);

Categories

Resources