The below code is executing without any problems. But logically it seems to be incorrect. Why is it so?
import java.sql.Date;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
DateFormat df =new SimpleDateFormat("MM/dd/yyyy");
new Date(df.parse("09/01/3~34").getTime()); // Produces '09/01/3'
new Date(df.parse("09/01/100000").getTime()); // Produces ' 000-09-01'
First question:
new Date(df.parse("09/01/3~34").getTime()); // Produces '09/01/3'
According to DateFormat#parse() JavaDoc:
Parses text from the beginning of the given string to produce a date. The method may not use the entire text of the given string.
Because of that, after it parses the value from the String, it stops searching on it. When it found the ~ sign, it parsed the 3 value to the year and stopped looking on the String.
Second question:
new Date(df.parse("09/01/100000").getTime()); // Produces ' 000-09-01'
It's not producing '000-09-01'. The following code:
DateFormat df = new SimpleDateFormat("MM/dd/yyyy");
java.util.Date parsedDate = df.parse("09/01/100000");
System.out.println(parsedDate);
System.out.println(df.format(parsedDate));
Outputs:
Fri Sep 01 00:00:00 BRT 100000
09/01/100000
However, it appears to be a bug with the java.sql.Date#toString method on JDK. To present this java.sql.Date correctly, try passing it to your DateFormat#format method:
java.sql.Date sqlDt = new java.sql.Date(df.parse("09/01/10000").getTime());
System.out.println(df.format(sqlDt));
Output: 09/01/100000
About first question:
The class SimpleDateFormat is VERY lenient and just stops when some invalid chars are hit. Even when you explicitly instruct it to parse strict via df.setLenient(false); you will notice here the same output and no exception. JodaTime, JSR-310 or my time library would correctly reject the input containing invalid chars in strict mode. So here we have a clear bug in JDK.
About second question:
Well, you use java.sql.Date instead of java.util.Date. The sql-version is not designed for years > 9999. This is even specified in its javadoc:
"date milliseconds since January 1, 1970, 00:00:00 GMT not to exceed the milliseconds representation for the year 8099"
Not quite clear in javadoc but Oracle speaks about year offset of 1900 so finally java.sql.Date only supports the year range up to 9999. This is conform with ANSI-SQL, so no that year limitation itself is not a bug, but necessary for interoperability with SQL. Instead another aspect is bad and in my opinion a bug, namely: If you feed the constructor with an invalid year then you don`t get any exception but internally the state will be silently set to something silly and unpredictable.
Conclusion:
Avoid to use java.sql.Date for anything else than in JDBC layer. It is not for the application layer. Furthermore, java.util.Date and also SimpleDateFormat (which are the "standard" in Java-pre8) are more or less horribly broken. Good alternatives are JodaTime, in Java 8 the new JSR-310-API or my library if it reaches non-alpha-state one day.
Related
I am trying to convert a string to LocaleDateTime object in Java8 as below :
DateTimeFormatter globalFormat = DateTimeFormatter.ofPattern("yyyyMMddhhmmssSS");
String input = "2019082905020425";
LocalDateTime currentDateTime = LocalDateTime.parse(input, globalFormat);
But I am getting below exception, if someone can help me with a solution on the same :
Exception in thread "main" java.time.format.DateTimeParseException:
Text '2019082905020425' could not be parsed at index 0 at
java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1947)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1849)
at java.time.LocalDateTime.parse(LocalDateTime.java:492)at test.main(Test.java:20)
It’s a bug in Java 8.
Workaround for Java 8
DateTimeFormatter globalFormat = new DateTimeFormatterBuilder()
.appendPattern("yyyyMMddHHmmss")
.appendValue(ChronoField.MILLI_OF_SECOND, 3)
.toFormatter();
String input = "2019082905020425";
String adaptedInput = input + "0";
LocalDateTime currentDateTime = LocalDateTime.parse(adaptedInput, globalFormat);
System.out.println("Parsed date and time: " + currentDateTime);
Output from this snippet is (tested on jdk-1.8.0_121):
Parsed date and time: 2019-08-29T05:02:04.250
Java 8 cannot separate an integer field like ss and a fractional fields like SS without any separator between them. The workaround is to parse the fraction as an integer too. Your string includes 100ths of seconds, and no integer field for those is built in. So I append an extra 0 (zero) so that we’ve got milliseconds, and then use ChronoField.MILLI_OF_SECOND for parsing.
Whether it was really a bug can maybe be debated. There never was any strict promise in the docs that it should work, but it seemed to be the expectation of many, and in any case they fixed it in Java 9.
I have made one more correction, and you will want to check whether this is the correction you want: Lowercase hh is for hour within AM or PM from 01 through 12. If you intended this, you need to specify whether you want AM or PM. Instead I assumed that 05 was an hour of day from 00 through 23. Use uppercase HH for parsing this.
Edit: use a regular expression? #josejuan advocates a regular expression over the above. It’s an option, and can save us of the explicit formatter completely:
String input = "2019082905020425";
String adaptedInput = input.replaceFirst(
"^(\\d{4})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})$",
"$1-$2-$3T$4:$5:$6.$7");
LocalDateTime currentDateTime = LocalDateTime.parse(adaptedInput);
The result is the same as before. For my part I find the latter code quite a lot harder to read and maintain. Also once you migrate to Java 9 or higher, I think that the first snippet above lends itself more directly to going back to the code from which you started, which is what you want in the end. Pick the solution that you prefer.
The code is working on Java 9 and later
On Java 9 and later the change from hh to HH is all we need for the code in the question to work fine.
Links
Java bug DateTimeFormatter won't parse dates with custom format "yyyyMMddHHmmssSSS" in the bug database
Question Is java.time failing to parse fraction-of-second? about the bug
Question Comparing two times in android about hh in a format pattern string
I have found that SimpleDateFormat::parse(String source)'s behavior is (unfortunatelly) defaultly set as lenient: setLenient(true).
By default, parsing is lenient: If the input is not in the form used by this object's format method but can still be parsed as a date, then the parse succeeds.
If I set the leniency to false, the documentation said that with strict parsing, inputs must match this object's format. I have used paring with SimpleDateFormat without the lenient mode and by mistake, I had a typo in the date (letter o instead of number 0). (Here is the brief working code:)
// PASSED (year 199)
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.mm.yyyy");
System.out.println(simpleDateFormat.parse("03.12.199o"));
simpleDateFormat.setLenient(false);
System.out.println(simpleDateFormat.parse("03.12.199o")); //WTF?
In my surprise, this has passed and no ParseException has been thrown. I'd go further:
// PASSED (year 1990)
String string = "just a String to mess with SimpleDateFormat";
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.mm.yyyy");
System.out.println(simpleDateFormat.parse("03.12.1990" + string));
simpleDateFormat.setLenient(false);
System.out.println(simpleDateFormat.parse("03.12.1990" + string));
Let's go on:
// FAILED on the 2nd line
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.mm.yyyy");
System.out.println(simpleDateFormat.parse("o3.12.1990"));
simpleDateFormat.setLenient(false);
System.out.println(simpleDateFormat.parse("o3.12.1990"));
Finally, the exception is thrown: Unparseable date: "o3.12.1990". I wonder where is the difference in the leniency and why the last line of my first code snippet has not thrown an exception? The documentation says:
With strict parsing, inputs must match this object's format.
My input clearly doesn't strictly match the format - I expect this parsing to be really strict. Why does this (not) happen?
Why does this (not) happen?
It’s not very well explained in the documentation.
With lenient parsing, the parser may use heuristics to interpret
inputs that do not precisely match this object's format. With strict
parsing, inputs must match this object's format.
The documentation does help a bit, though, by mentioning that it is the Calendar object that the DateFormat uses that is lenient. That Calendar object is not used for the parsing itself, but for interpreting the parsed values into a date and time (I am quoting DateFormat documentation since SimpleDateFormat is a subclass of DateFormat).
SimpleDateFormat, no matter if lenient or not, will accept 3-digit year, for example 199, even though you have specified yyyy in the format pattern string. The documentation says about year:
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.
DateFormat, no matter if lenient or not, accepts and ignores text after the parsed text, like the small letter o in your first example. It objects to unexpected text before or inside the text, as when in your last example you put the letter o in front. The documentation of DateFormat.parse says:
The method may not use the entire text of the given string.
As I indirectly said, leniency makes a difference when interpreting the parsed values into a date and time. So a lenient SimpleDateFormat will interpret 29.02.2019 as 01.03.2019 because there are only 28 days in February 2019. A strict SimpleDateFormat will refuse to do that and will throw an exception. The default lenient behaviour can lead to very surprising and downright inexplicable results. As a simple example, giving the day, month and year in the wrong order: 1990.03.12 will result in August 11 year 17 AD (2001 years ago).
The solution
VGR already in a comment mentioned LocalDate from java.time, the modern Java date and time API. In my experience java.time is so much nicer to work with than the old date and time classes, so let’s give it a shot. Try a correct date string first:
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd.mm.yyyy");
System.out.println(LocalDate.parse("03.12.1990", dateFormatter));
We get:
java.time.format.DateTimeParseException: Text '03.12.1990' could not
be parsed: Unable to obtain LocalDate from TemporalAccessor:
{Year=1990, DayOfMonth=3, MinuteOfHour=12},ISO of type
java.time.format.Parsed
This is because I used your format pattern string of dd.mm.yyyy, where lowercase mm means minute. When we read the error message closely enough, it does state that the DateTimeFormatter interpreted 12 as minute of hour, which was not what we intended. While SimpleDateFormat tacitly accepted this (even when strict), java.time is more helpful in pointing out our mistake. What the message only indirectly says is that it is missing a month value. We need to use uppercase MM for month. At the same time I am trying your date string with the typo:
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
System.out.println(LocalDate.parse("03.12.199o", dateFormatter));
We get:
java.time.format.DateTimeParseException: Text '03.12.199o' could not
be parsed at index 6
Index 6 is where is says 199. It objects because we had specified 4 digits and are only supplying 3. The docs say:
The count of letters determines the minimum field width …
It would also object to unparsed text after the date. In short it seems to me that it gives you everything that you had expected.
Links
DateFormat.setLenient documentation
Oracle tutorial: Date Time explaining how to use java.time.
Leniency is not about whether the entire input matches but whether the format matches. Your input can still be 3.12.1990somecrap and it would work.
The actual parsing is done in parse(String, ParsePosition) which you could use as well. Basically parse(String) will pass a ParsePosition that is set up to start at index 0 and when the parsing is done the current index of that position is checked.
If it's still 0 the start of the input didn't match the format, not even in lenient mode.
However, to the parser 03.12.199 is a valid date and hence it stops at index 8 - which isn't 0 and thus the parsing succeeded. If you want to check whether everything was parsed you'd have to pass your own ParsePosition and check whether the index is matches to the length of the input.
If you use setLenient(false) it will still parse the date till the desired pattern is meet. However, it will check the output date is a valid date or not. In your case, 03.12.199 is a valid date, so it will not throw an exception. Lets take an example to understand where the setLenient(false) different from setLenient(true)/default.
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.MM.yyyy");
System.out.println(simpleDateFormat.parse("31.02.2018"));
The above will give me output: Sat Mar 03 00:00:00 IST 2018
But the below code throw ParseException as 31.02.2018 is not a valid/possible date:
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.MM.yyyy");
simpleDateFormat.setLenient(false);
System.out.println(simpleDateFormat.parse("31.02.2018"));
I would appreciate any help with finding bug for this exception:
java.text.ParseException: Unparseable date: "2007-09-25T15:40:51.0000000Z"
and following code:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
Date date = sdf.parse(timeValue);
long mills = date.getTime();
this.point.time = String.valueOf(mills);
It throws expcetion with Date date = sdf.parse(timeValue); .
timeValue = "2007-09-25T15:40:51.0000000Z"; , as in exception.
Thanks.
Z represents the timezone character. It needs to be quoted:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
(Answer now extensively revised, thanks for the corrections in the comments)
In Java 7 you can use the X pattern to match an ISO8601 timezone, which includes the special Z (UTC) value.
The X pattern also supports explicit timezones, e.g. +01:00
This approach respects the timezone indicator correctly, and avoids the problem of treating it merely as a string, and thus incorrectly parsing the timestamp in the local timezone rather than UTC or whatever.
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
Date date = sdf.parse("2007-09-25T15:40:51Z");
Date date2 = sdf.parse("2007-09-25T15:40:51+01:00");
This can also be used with milliseconds:
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX");
Date date3 = sdf2.parse("2007-09-25T15:40:51.500Z");
However, as others have pointed out, your format has 7-digit fractional seconds, which are presumably tenth-microseconds. If so, SimpleDateFormat cannot handle this, and you will get incorrect results, because each 0.1 microsecond will be interpreted as a millisecond, giving a potential overall error of up to 10,000 seconds (several hours).
In the extreme case, if the fractional second value is 0.9999999 seconds, that will be incorrectly interpreted as 9999999 milliseconds, which is about 167 minutes, or 2.8 hours.
// Right answer, error masked for zero fractional seconds
Date date6 = sdf2.parse("2007-09-25T15:40:51.0000000Z");
// Tue Sep 25 15:40:51 GMT 2007
// Error - wrong hour
// Should just half a second different to the previous example
Date date5 = sdf2.parse("2007-09-25T15:40:51.5000000Z");
// Tue Sep 25 17:04:11 GMT 2007
This error is hidden when the fractional seconds are zero, as in your example, but will manifest whenever they are nonzero.
This error can be detected in many cases, and its impact reduced, by turning off "lenient" parsing which by default will accept a fractional part of more than one second and carry it over to the seconds/minutes/hours parts:
sdf2.setLenient(false);
sdf2.parse("2007-09-25T15:40:51.5000000Z");
// java.text.ParseException: Unparseable date: "2007-09-25T15:40:51.5000000Z"
This will catch cases where the millis value is more than 999, but does not check the number of digits, so it is only a partial and indirect safeguard against millis/microseconds mismatches. However, in many real-world datasets this will catch a large number of errors and thus indicate the root problem, even if some values slip through.
I recommend that lenient parsing is always disabled unless you have a specific need for it, as it catches a lot of errors that would otherwise be silently hidden and propagated into downstream data.
If your fractional seconds are always zero, then you could use one of the solutions here, but with the risk that they will NOT work if the code is later used on non-zero fractional seconds. You may wish to document this and/or assert that the value is zero, to avoid later bugs.
Otherwise, you probably need to convert your fractional seconds into milliseconds, so that SimpleDateFormat can interpret them correctly. Or use one of the newer datetime APIs.
java.time
I recommend that you use java.time, the modern Java date and time API, for your date and time work. Your string is in ISO 8601 format and can be directly parsed by the java.time.Instant class without us specifying any formatter:
String timeValue = "2007-09-25T15:40:51.0000000Z";
Instant i = Instant.parse(timeValue);
long mills = i.toEpochMilli();
String time = String.valueOf(mills);
System.out.println(time);
Output:
1190734851000
May use a formatter for output if desired
If we know for a fact that the millisecond value will never be negative, java.time can format it into a string for us. This saves the explicit conversion to milliseconds first.
private static final DateTimeFormatter EPOCH_MILLI_FORMATTER
= new DateTimeFormatterBuilder().appendValue(ChronoField.INSTANT_SECONDS)
.appendValue(ChronoField.MILLI_OF_SECOND, 3)
.toFormatter(Locale.ROOT);
Now formatting is trivial:
assert ! i.isBefore(Instant.EPOCH) : i;
String time = EPOCH_MILLI_FORMATTER.format(i);
And output is still the same:
1190734851000
In particular if you need to format Instant objects to strings in more places in your program, I recommend the latter approach.
What went wrong in your code?
First of all, there is no way that SimpleDateFormat can parse 7 decimals of fraction of second correctly. As long as the fraction is zero, the result will happen to come out correct anyway, but imagine a time that is just one tenth of a second after the full second, for example, 2007-09-25T15:40:51.1000000Z. In this case SimpleDateFormat would parse the fraction into a million milliseconds, and your result would be more than a quarter of an hour off. For greater fractions the error could be several hours.
Second as others have said format pattern letter Z does not match the offset of Z meaning UTC or offset zero from UTC. This caused the exception that you observed. Putting Z in quotes as suggested in the accepted answer is wrong too since it will cause you to miss this crucial information from the string, again leading to an error of several hours (in most time zones).
Link
Oracle tutorial: Date Time explaining how to use java.time.
I have date in string format and I want to parse that into util date.
var date ="03/11/2013"
I am parsing this as :
new SimpleDateFormat("MM/dd/yyyy").parse(date)
But the strange thing is that, if I am passing "03-08-201309 hjhkjhk" or "03-88-2013" or 43-88-201378", it does not throw error , it parses it.
For this now, I have to write regex pattern for checking whetehr input of date is correct or not.
but why is it so ??
Code :
scala> val date="03/88/201309 hjhkjhk"
date: java.lang.String = 03/88/201309 hjhkjhk
scala> new SimpleDateFormat("MM/dd/yyyy").parse(date)
res5: java.util.Date = Mon May 27 00:00:00 IST 201309
You should use DateFormat.setLenient(false):
SimpleDateFormat df = new SimpleDateFormat("MM/dd/yyyy");
df.setLenient(false);
df.parse("03/88/2013"); // Throws an exception
I'm not sure that will catch everything you want - I seem to remember that even with setLenient(false) it's more lenient than you might expect - but it should catch invalid month numbers for example.
I don't think it will catch trailing text, e.g. "03/01/2013 sjsjsj". You could potentially use the overload of parse which accepts a ParsePosition, then check the current parse index after parsing has completed:
ParsePosition position = new ParsePosition(0);
Date date = dateFormat.parse(text, position);
if (position.getIndex() != text.length()) {
// Throw an exception or whatever else you want to do
}
You should also look at the Joda Time API which may well allow for a stricter interpretation - and is a generally cleaner date/time API anyway.
Jon Skeet’s answer is correct and was a good answer when it was written in 2013.
However, the classes you use in your question, SimpleDateFormat and Date, are now long outdated, so if someone got a similar issue with them today, IMHO the best answer would be to change to using the modern Java date & time API.
I am sorry I cannot write Scala code, so you will have to live with Java. I am using
private static DateTimeFormatter parseFormatter
= DateTimeFormatter.ofPattern("MM/dd/yyyy");
The format pattern letters are the same as in your question, though the meaning is slightly different. DateTimeFormatter takes the number of pattern letters literally, as we shall see. Now we try:
System.out.println(LocalDate.parse(date, parseFormatter));
Results:
"03/11/2013" is parsed into 2013-03-11 as expected. I used the modern LocalDate class, a class that represents a date without time-of-day, exactly what we need here.
Passing "03/88/2013 hjhkjhk" gives a DateTimeParseException with the message Text '03/88/2013 hjhkjhk' could not be parsed, unparsed text found at index 10. Pretty precise, isn’t it? The modern API has methods to parse only part of a string if that is what we want, though.
"03/88/201309" gives Text '03/88/201309' could not be parsed at index 6. We asked for a 4 digit year and gave it 6 digits, which leads to the objection. Apparently it detects and reports this error before trying to interpret 88 as a day of month.
It does object to a day of month of 88 too, though: "03/88/2013" gives Text '03/88/2013' could not be parsed: Invalid value for DayOfMonth (valid values 1 - 28/31): 88. Again, please enjoy how informative the message is.
"03-08-2013" (with hyphens instead of slashes) gives Text '03-08-2013' could not be parsed at index 2, not very surprising. Index 2 is where the first hyphen is.
Jon Skeet explained that the outdated SimpleDateFormat can be lenient or non-lenient. This is true for DateTimeFormatter too, in fact it has 3 instead of 2 resolver styles, called ‘lenient’, ‘smart’ and ‘strict’. Since many programmers are not aware of this, though, I think they made a good choice of not making ‘lenient’ the default (‘smart’ is).
What if we wanted to make our formatter lenient?
private static DateTimeFormatter parseFormatter
= DateTimeFormatter.ofPattern("MM/dd/yyyy")
.withResolverStyle(ResolverStyle.LENIENT);
Now it also parses "03/88/2013", into 2013-05-27. I believe this is what the old class would also have done: counting 88 days from the beginning of March gives May 27. The other error messages are still the same. In other words it still objects to unparsed text, to a 6 digit year and to hyphens.
Question: Can I use the modern API with my Java version?
If using at least Java 6, you can.
In Java 8 and later the new API comes built-in.
In Java 6 and 7 get the ThreeTen Backport, the backport of the new classes (that’s ThreeTen for JSR-310, where the modern API was first defined).
On Android, use the Android edition of ThreeTen Backport. It’s called ThreeTenABP, and I think that there’s a wonderful explanation in this question: How to use ThreeTenABP in Android Project.
I want to save a Date object to a readable string (for example 22/10/2009 21:13:14) that is also parsable back to a Date object.
I have tried many things and the best I could find was to use DateFormater for parsing and formating but it has a setback. When you format a date you lose seconds information. I tried to find if there is an option to format it and display the seconds (even better would be to the millisecond level since that's the resolution the Date object allows you to have) but I came up short.
Any ideas?
Take a look at java.text.SimpleDateFormat
SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss.SSS");
Date dt = new Date();
String S = sdf.format(dt); // formats to 09/23/2009 13:53:28.238
Date dt2 = sdf.parse(S); // parses back
SimpleDateFormat can format and parse a date based on a very simple pattern system that include second and even milliseconds.
Other answers are all good.
But when doing this kind of thing please pick a format that sorts properly when coded as a string.... "yyyy/MM/dd HH:mm:ss" is fine. It always astounds me when software engineers pick a date format which doesn't sort in the obvious, convenient way.
You'll save your fellow developers a lot of pain at some distant point in the future - think of it as good karma :-)
ISO 8601
Use ISO 8601 format.
It’s flexible, it includes seconds and fraction of second if there are any, but you may also leave them out if they are 0.
It’s standard, so more and more tools format and parse it. Great for serialization for storage or data interchange.
It goes like 2009-10-22T21:13:14, I should say it’s pretty human-readable (though the T in the middle that denotes the start of the time part may feel unusual at first).
The strings sort properly, as mikera requested in another answer, as long as the years are in the four-digit range from 1000 through 9999.
The classes of java.time, the modern Java date and time API, as well as those of Joda Time parse ISO 8601 as their default, that is, without any explicit formatter, and produce the same format from their toString methods.
A modest demonstration of using java.time:
LocalDateTime dateTime = LocalDateTime.of(2009, 10, 22, 21, 13, 14);
String readableString = dateTime.toString();
System.out.println(readableString);
LocalDateTime parsedBack = LocalDateTime.parse(readableString);
System.out.println(parsedBack);
This prints two identical lines:
2009-10-22T21:13:14
2009-10-22T21:13:14
The latter System.out.println() call implicitly calls toString() once more, so this shouldn’t surprise.
A little off-topic, but I always feel the need to remind people that DateFormat and SimpleDateFormat are not thread safe! The Sun documentation clearly states this, but I keep finding code out in the wild where people stick a SimpleDateFormat in a static ...
If you want to do it a little simpler, and be spared from making your own DateFormat that most other Answers involve, you can leverage the default format in java.time.Instant:
(new Date()).toInstant.toString();