问题
Am using SimpleDateFormat to format or validate the dates, but I would like to make it thread-safe by using java 8 DateTimeFormatter. I am having trouble to achieve some requirement.
My application will accept only three types of formats. "yyyy-MM-dd", "yyyy-MM", "yyyy"
Existing Code gives me desired output:
/*simple date format to process yyyy-MM-dd format
SimpleDateFormat simpleDateFormat1 = new SimpleDateFormat("yyyy-MM-dd")
/*simple date format to process yyyy-MM format
SimpleDateFormat simpleDateFormat2 = new SimpleDateFormat("yyyy-MM")
/*simple date format to process yyyy format
SimpleDateFormat simpleDateFormat3 = new SimpleDateFormat("yyyy")
/* to parse input
simpleDateFormat.parse(input)
/* to format
simpleDateFormat.format(simpleDateFormat1)
Here is the input and expected output:
input expected
'2018-03-19' '2018-03-19'
'2018-03' '2018-03'
'2018' '2018'
'2017-02-54' '2017-02'
'2016-13-19' '2016'
How can I achieve same result in java 8 DateTimeForma
enter code here
tter?/* java 8 date time formatter DateTimeFormatter dateTimeFormatter = new DateTimeFormatter("yyyy-MM-dd")
The above snippet works when all year and month and date values are correct. Any help would be highly appreciated.
回答1:
Am using SimpleDateFormat to format or validate the dates
Never use SimpleDateFormat
.
The terrible date-time classes bundled with the earliest versions of Java were years ago supplanted by the modern java.time classes defined in JSR 310.
thread-safe by using java 8 DateTimeFormatter
Yes, unlike the legacy date-time classes, the java.time classes use immutable objects and are thread-safe by design.
Here is the input and expected output:
Some of your inputs could be detected simply by their length.
// Ten-digits long, assume ISO 8601 date.
LocalDate ld = LocalDate.parse( "2018-03-19" ) ;
// Seven digits long, assume ISO 8601 year-month.
YearMonth ym = YearMonth.parse( "2018-03" ) ;
// Four digits, assume year.
Year y = Year.parse( "2018" ) ;
Notice that the above inputs all comply with ISO 8601. The java.time classes use ISO 8601 formats by default when parsing/generating strings. So no need to specify a formatting pattern. And therefore no need for an explicit DateTimeFormatter
object.
'2017-02-54' '2017-02'
This example puzzles me. If you mean "When encountering a date with invalid day-of-month, just use the year and month while ignoring the day", I suppose you might be able to do that. Look into "lenient" mode on a DateTimeFormatter
. Perhaps use DateTimeFormatterBuilder
to build a flexible DateTimeFormatter
. But frankly, I would reject such data as faulty inputs. It should be the job of the publisher of the data to produce reliable data, not the job of the consumer to guess the intention behind faulty data.
input expected
'2016-13-19' '2016'
Again, trying to guess the valid parts of invalid inputs in a dangerous game I would not play. If the month and day are invalid, how do you know the year is valid? Even worse, if the publisher of this data can emit such erroneous data, how do you know an apparently valid 2018-03-19
input is actually correct? If month 13
is a mistake, how do know an input with month of 03
is not a mistake too?
Teach the publisher of this problematic data about the ISO 8601 standard, and ask them to fix their bugs.
回答2:
Like Basil Bourque in the other answer I am not necessarily convinced that what you are asking for is also what will serve you the best. In any case, as a small supplement to that good answer I would like to present an approach to handling the last two cases, the invalid dates, the way you said.
For validation of the format I am first parsing the strings without validating the numbers. This will reject strings that are not in any of your three formats, for example 2016-03-19T12:00
or 2016 and some nonsense
. The DateTimeFormatter.parseUnresolved
method takes care of this part. In case of a parsing error, this method sets an error index in the ParsePosition
object that we passed to it and returns null
(so does not throw any exception). So I check whether null
is returned.
private static DateTimeFormatter yearMonthFormatter = DateTimeFormatter.ofPattern("uuuu-MM")
.withResolverStyle(ResolverStyle.STRICT);
private static DateTimeFormatter yearFormatter = DateTimeFormatter.ofPattern("uuuu")
.withResolverStyle(ResolverStyle.STRICT);
public static Temporal parse(String input) {
// First try the three formats, uuuu-MM-dd, uuuu-MM, uuuu, in turn without resolving
TemporalAccessor parsed = null;
for (DateTimeFormatter formatter : Arrays.asList(DateTimeFormatter.ISO_LOCAL_DATE,
yearMonthFormatter, yearFormatter)) {
ParsePosition position = new ParsePosition(0);
TemporalAccessor parseAttempt = formatter.parseUnresolved(input, position);
if (parseAttempt != null && position.getIndex() == input.length()) {
// Success; exit loop
parsed = parseAttempt;
break;
}
}
if (parsed == null) { // didn’t match any of the three formats
throw new IllegalArgumentException("Invalid format: " + input);
}
// Try resolving to either LocalDate, YearMonth or Year
try {
return LocalDate.of(parsed.get(ChronoField.YEAR),
parsed.get(ChronoField.MONTH_OF_YEAR),
parsed.get(ChronoField.DAY_OF_MONTH));
} catch (DateTimeException dteRLd) {
try {
return YearMonth.of(parsed.get(ChronoField.YEAR),
parsed.get(ChronoField.MONTH_OF_YEAR));
} catch (DateTimeException dteRYm) {
return Year.of(parsed.get(ChronoField.YEAR));
}
}
}
Let’s try your examples:
String[] inputs = {
"2018-03-19",
"2018-03",
"2018",
"2017-02-54",
"2016-13-19"
};
for (String input : inputs) {
Temporal parsed = parse(input);
System.out.format("%-10s %-10s %s%n", input, parsed, parsed.getClass().getName());
}
Output is:
2018-03-19 2018-03-19 java.time.LocalDate 2018-03 2018-03 java.time.YearMonth 2018 2018 java.time.Year 2017-02-54 2017-02 java.time.YearMonth 2016-13-19 2016 java.time.Year
来源:https://stackoverflow.com/questions/57893772/how-to-process-yyyy-mm-and-yyyy-in-java8-using-datetimeformatter