Why SimpleDateFormat("MM/dd/yyyy") parses date to 10/20/20128? - java

I have this code:
DateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy");
dateFormat.setLenient(false);
Date date = dateFormat.parse("10/20/20128");
and I would expect the dateFormat.parse call to throw ParseException since the year I'm providing is 5 characters long instead of 4 like in the format I defined. But for some reason even with the lenient set to false this call returns a Date object of 10/20/20128.
Why is that? It doesn't make much sense to me. Is there another setting to make it even more strict?

20128 is a valid year and Java hopes the world to live that long I guess.
if the number of pattern letters is more than 2, the year is
interpreted literally, regardless of the number of digits.
Reference.
If you want to validate if a date is in limit, you can define one and check-
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
Date maxDate = sdf.parse("01/01/2099"); // This is the limit
if(someDate.after(maxDate)){
System.out.println("Invalid date");
}

See the javadoc
Year: If the formatter's Calendar is the Gregorian calendar, the
following rules are applied.
For formatting, if the number of pattern letters is 2, the year is truncated to 2 digits; otherwise it is interpreted as a number.
For parsing, if the number of pattern letters is more than 2, the year is interpreted literally, regardless of the number of digits.
So using the pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12
A.D.
For parsing with the abbreviated year pattern ("y" or "yy"), SimpleDateFormat must interpret the abbreviated year relative to some
century. It does this by adjusting dates to be within 80 years before
and 20 years after the time the SimpleDateFormat instance is created.
For example, using a pattern of "MM/dd/yy" and a SimpleDateFormat
instance created on Jan 1, 1997, the string "01/11/12" would be
interpreted as Jan 11, 2012 while the string "05/04/64" would be
interpreted as May 4, 1964. During parsing, only strings consisting
of exactly two digits, as defined by Character.isDigit(char), will be
parsed into the default century. Any other numeric string, such as a
one digit string, a three or more digit string, or a two digit string
that isn't all digits (for example, "-1"), is interpreted literally.
So "01/02/3" or "01/02/003" are parsed, using the same pattern, as
Jan 2, 3 AD. Likewise, "01/02/-3" is parsed as Jan 2, 4 BC.
Otherwise, calendar system specific forms are applied. For both formatting and parsing, if the number of pattern letters is 4 or
more, a calendar specific long form is used. Otherwise, a calendar
specific short or abbreviated form is used.
Therefore, it will read all the characters that come after the last / as the year.

See java.text.SimpleDateFormat API, pattern letter y: For parsing, if the number of pattern letters is more than 2, the year is interpreted literally, regardless of the number of digits.

Using SimpleDateFormat in java, you can achieve a number of human readable formats. Consider this code snippet:
Date curDate = new Date();
SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd");
String DateToStr = format.format(curDate);
System.out.println(DateToStr);
format = new SimpleDateFormat("dd-M-yyyy hh:mm:ss");
DateToStr = format.format(curDate);
System.out.println(DateToStr);
format = new SimpleDateFormat("dd MMMM yyyy zzzz", Locale.ENGLISH);
DateToStr = format.format(curDate);
System.out.println(DateToStr);
format = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss z");
DateToStr = format.format(curDate);
System.out.println(DateToStr);
Look at the various outputs generated by the various formats:
2014/05/11
11-5-2014 11:11:51
11 May 2014 Eastern European Summer Time
Sun, 11 May 2014 23:11:51 EEST
Feel free to modify the format string and you might get the desired result.

java.time
I always recommend that you use java.time, the modern Java date and time API, for your date work. In addition it so happens that java.time will give you the exception you asked for.
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("MM/dd/uuuu")
.withResolverStyle(ResolverStyle.LENIENT);
LocalDate date = LocalDate.parse("10/20/20128", dateFormatter);
Result:
Exception in thread "main" java.time.format.DateTimeParseException:
Text '10/20/20128' could not be parsed at index 6
The mentioned index 6 is where the 5-digit year is in your string.
This said, the range check suggested by others could still be a good idea. With java.time this is easy. Say for example that we don’t want to accept any future date:
LocalDate date = LocalDate.parse("10/20/8012", dateFormatter);
if (date.isAfter(LocalDate.now(ZoneId.systemDefault()))) {
System.err.println("Date must not be in the future, was " + date);
}
Date must not be in the future, was 8012-10-20
Tutorial link: Trail: Date Time (The Java™ Tutorials) explaining how to use java.time.

Related

Convert or format string to date

I am struggling with this ..
I have an input string - like this: 2021-10-13 11:33:16.000-04
Using Java.
I need to get a Date object from it.
which formatting pattern can I use ?
I try with these
SimpleDateFormat inFormatter = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss.SSS'-'ZZ");
and
SimpleDateFormat inFormatter = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss.SSSZZ");
and I keep getting
java.text.ParseException: Unparseable date: "2021-10-13 11:33:16.000-04"
at java.base/java.text.DateFormat.parse(DateFormat.java:396)
at com.dima.tests.DatesConversions.main(DatesConversions.java:24)
Please, help !!
Don't use Date as it is outdated. Use the classes in the java.time
OffsetDateTime odt = OffsetDateTime.parse(str,
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSX"));
System.out.println(odt);
Prints
2021-10-13T11:33:16-04:00
java.time
Even though you need to give an old-fashionede Date object to a legacy API beyond your control, I still recommend that you use java.time, the modern Java date and time API, in your own code. The final conversion to Date is pretty straight-forward.
I’d use this formatter for maximum reuse of existing formatters:
private static final DateTimeFormatter PARSER = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.appendLiteral(' ')
.append(DateTimeFormatter.ISO_LOCAL_TIME)
.appendOffset("+HHmm", "+00")
.toFormatter(Locale.ROOT);
Then we parse and convert like this:
String input = "2021-10-13 11:33:16.000-04";
OffsetDateTime dateTime = OffsetDateTime.parse(input, PARSER);
System.out.println(dateTime);
Instant i = dateTime.toInstant();
Date oldfashionedDate = Date.from(i);
System.out.println(oldfashionedDate);
Output in my time zone, Europe/Copenhagen:
2021-10-13T11:33:16-04:00
Wed Oct 13 17:33:16 CEST 2021
Denmark is at offset +02:00 at this time of year, so 6 hours ahead of the UTC offset -04 from your string. Therefore Date.toString() confusingly prints a clock hour that is 6 hours ahead of the original time of day.
Note: if your forward service accepts anything else than an old-fashioned Date, you should not be using that class. For example, if a String is required, the OffsetDateTime that we got can be formatted into a new string using a second DateTimeFormatter (or in lucky cases, its toString method).
What went wrong in your code?
First, a UTC offset can have positive or negative sign. Instead of -04 you could have had for example +09. Formatters are designed for to take the sign, + or -, as part of the offset. Therefore hardcoding the minus sign as a literal, as in your first attempt, is bound to fail. In your second attempt, yyyy-MM-dd HH:mm:ss.SSSZZ, you are already closer. However, ZZ is for an offset with sign and four digits (like +0530 or -0400; hour and minute), so does not work for a two-digit offset like -04. Your SimpleDateFormat expected more digits where your string ended and therefore threw the exception that you saw.
Link
Oracle tutorial: Date Time explaining how to use java.time.
Since you are using ISO 8601 time zone timezone, you have the use the below pattern.
SimpleDateFormat inFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSX");
And then, to get the date:
Date date = inFormatter.parse("2021-10-13 11:33:16.000-04");
Always check the documentation.

java: ParseException: Unparseable date [duplicate]

This question already has answers here:
ParseException when parsing 3 character abbreviated month using SimpleDateFormat
(5 answers)
Closed 4 years ago.
i have a SimpleDateFormat format = new SimpleDateFormat("d M y H:m"); and i try to parse the String "8 Jan 2019 16:47" with it, but i get a ParseException. Did i create it the wrong way?
According to docs.oracle.com the M should recognize 3-letter-months.
Can anyone help me?
The official documentation: (https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html)
You probably missed out this little note here:
Month: If the number of pattern letters is 3 or more, the month is interpreted as text; otherwise, it is interpreted as a number.
Based on your example input, the following works:
SimpleDateFormat format = new SimpleDateFormat("dd MMM yyyy HH:mm");
java.time
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d MMM y H:mm", Locale.ENGLISH);
String stringToParse = "8 Jan 2019 16:47";
LocalDateTime dateTime = LocalDateTime.parse(stringToParse, formatter);
System.out.println(dateTime);
The output from this snippet is:
2019-01-08T16:47
What went wrong in your code?
SimpleDateFormat and Date are poorly designed and long outdated, the former in particular notoriously troublesome. I recommend you don’t use them in 2019.
As others have said you need three M for month abbreviation (no matter if you are using the outdated SimpleDateFormat or the modern DateTimeFormatter). One M will match a month number in 1 or 2 digits, for example 1 for January.
You should also specify a locale for your formatter. I took Jan to be English so specified Locale.ENGLISH. If you don’t specify locale, the JVM’s default locale will be used, which may work well on some JVMs and suddenly break some day when the default locale has been changed or you are trying to run your program on a different computer.
Link
Oracle tutorial: Date Time explaining how to use java.time.

Java 8 Date time formatter misbehaving

I have the following piece of code that is throwing a DateTimeParseException:
String dateString = "Jul 20 09:32:46"
DateTimeFormatter formatter=DateTimeFormatter.ofPattern("L d HH:mm:ss");
return ZonedDateTime.parse(dateString, formatter);
According to the documentation, you will observe that Jul is the example for character L.
However, the exception message is:
java.time.format.DateTimeParseException: Text 'Jul' could not be parsed at index 0
What am I missing?
You have some issues here:
To correctly parse 'Jul' you have to use MMM instead of L (here explains why).
Your date string doesn't have a year. You can't create a ZonedDateTime without the year.
If is a Zoned date time, it has to include the time zone information too, which is not in your date string. You can use a LocalDateTime if you don't want to work with time zones.
Here are some alternatives:
With timezone:
String dateString = "Jul 20 2018 09:32:46+0000";
DateTimeFormatter formatter= DateTimeFormatter.ofPattern("MMM dd y H:mm:ssZ");
return ZonedDateTime.parse(dateString, formatter);
Without timezone:
String dateString = "Jul 20 2018 09:32:46";
DateTimeFormatter formatter= DateTimeFormatter.ofPattern("MMM dd y H:mm:ss");
return LocalDateTime.parse(dateString, formatter);
The answer by Juan Carlos Mendoza is correct. I will give my suggestions as a supplement: either improve your string to include year and time zone, or build a formatter that can parse your current string without them.
Improving your string
String dateString = "Jul 20 2018 09:32:46 America/Argentina/La_Rioja";
DateTimeFormatter formatter
= DateTimeFormatter.ofPattern("LLL d uuuu HH:mm:ss VV", Locale.ROOT);
System.out.println(ZonedDateTime.parse(dateString, formatter));
This prints
2018-07-20T09:32:46-03:00[America/Argentina/La_Rioja]
The same formatter will also parse Jul 20 2018 09:32:46 -08:30 into a ZonedDateTime of 2018-07-20T09:32:46-08:30.
First potential issue is the locale. If “Jul” is in English, give an English-speaking locale, or parsing will likely fail on computers with a language where the month of July is called something else. I recommend you always specify locale with your formatter. Even if you end up going for Locale.getDefault(). It will still tell the reader (and yourself) that you have made a conscious choice.
Next the documentation says that both M and L can give month as number/text and gives examples 7; 07; Jul; July; J. So this line is clearly relevant: “Number/Text: If the count of pattern letters is 3 or greater, use the Text rules above. Otherwise use the Number rules above.” Since “Jul” is text, you need 3 pattern letters or greater. “Less than 4 pattern letters will use the short form.” “Jul” is short, so we need exactly three letters.
The code above works with Java 9.0.4 no matter if I use MMM or LLL in the format pattern string. In jdk1.8.0_131 it works with MMM but funnily fails with LLL, this may be a bug (tested on a Mac). See Juan Carlos Mendoza’s for a treatment of the intended difference between M and L.
Build a formatter that works
String dateString = "Jul 20 09:32:46";
ZoneId zone = ZoneId.of("America/Argentina/La_Rioja");
DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendPattern("LLL d HH:mm:ss")
.parseDefaulting(ChronoField.YEAR, Year.now(zone).getValue())
.toFormatter(Locale.ROOT)
.withZone(zone);
System.out.println(ZonedDateTime.parse(dateString, formatter));
This will parse the string from your question into 2018-07-20T09:32:46-03:00[America/Argentina/La_Rioja]. Please substitute your desired default time zone if it didn’t happen to coincide with the one I picked at random. Also substitute your desired year if you don’t want the current year.
Again my Java 8 requires MMM rather than LLL.

Java Date Formatting ParseException

I have a String as Friday, August 01, 2014. I want to format this and show as 2014-08-01.
I have tried this. but this gave java.text.ParseException: Unparseable date: "Friday, August 01, 2014"
SimpleDateFormat sdf = new SimpleDateFormat("E, MM d, yyyy");
String dateInString = "Friday, August 01, 2014";
Date date = sdf.parse(dateInString);
System.out.println(date);
How can i do this ?
You need to read the SimpleDateFormat API as it's all well explained there.
Note this explanation from the API:
Text: For formatting, if the number of pattern letters is 4 or more, the full form is used; otherwise a short or abbreviated form is used if available. For parsing, both forms are accepted, independent of the number of pattern letters.
Number: For formatting, the number of pattern letters is the minimum number of digits, and shorter numbers are zero-padded to this amount. For parsing, the number of pattern letters is ignored unless it's needed to separate two adjacent fields.
So for instance, MM corresponds to a numeric month, not the month name. For the complete month name, I'd use MMMM, and for the complete week name, I'd use EEEE. I'd use dd for a two digit date such as 01.
e.g.,
SimpleDateFormat sdf = new SimpleDateFormat("EEEE, MMMM dd, yyyy");

timezone inconsistency when parsing datetime Strings

I convert two types of Strings to an ISO format using SimpleDateFormat for parsing and org.apache.commons.lang.time.DateFormatUtils for formatting (since they provide a ISO formatter out-of-the-box).The pattern Strings for parsing are M/d/y H:m and d.M.y H:m. A typical String to convert may look either like 4/14/2009 11:22 or 4.14.2009 11:22. I initialize the parsers as follows:
SimpleDateFormat SLASH = new SimpleDateFormat(PATTERN_S, Locale.getDefault());
SimpleDateFormat DOT = new SimpleDateFormat(PATTERN_D, Locale.getDefault());
I get the the formatter:
FastDateFormat isoFormatter = DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT
After creating a Date from the parsed String:
Date date = FORMAT_SLASH.parse(old);
it is formatted for output:
isoFormatter.format(date)
The strange thing is : when a String with slashes was converted, the output looks like 2009-04-14T11:42:00+01:00 (which is correct) but when a String with dots was converted, the output looks like 2010-02-14T11:42:00+02:00, shifting my timezone to somewhere between Finland and South Africa, the year to 2010 and the month to february
What is going wrong here and why?
EDIT : changed the output strings to match real output (damn you, cut-n-paste). The reason was the interchanged M and d in the pattern strings that I failed to notice. 14 seems to be a perfecty valid month - its next year's february and even non-lenient settings can't force the formatter to reject it. The timeshift issue is resolved and the reason for the TimeZone change is provided by Jim Garrison. Thanks Ahmad and Jim
Your dot pattern is d.M.y H:m while your example shows that you meant M.d.y H:m, I supposed this would throw a ParseException, but it doesn't and it causes timezone issues.

Categories