问题
Following is the code snippet which is throwing an exception:
SimpleDateformat dateFormatter = new SimpleDateFormat("yyyyMMddHHmm");
Date date = dateFormatter.parse("201710010200");
The code above threw exception for all the dates after 2:00 A.M. It ran well till 01:30 A.M.
DayLight saving time was configured (I'm using Australia/Sydney
timezone).
After that, I could see logs of 3:00 A.M. Time between 2:00 A.M. and 3:00 A.M. is not logged too.
Log:
01 Oct 03:02:01 ERROR : Unparseable date: "201710010200"
Caused by: java.text.ParseException: Unparseable date: "201710010200" at java.text.DateFormat.parse(DateFormat.java:357)
What could be the fix of the problem of the date string "201710010200"
not getting parsed, with the right date format specified?
回答1:
You're trying to parse a date/time that didn't occur.
We now know that this was in the Sydney time zone. At 2am on October 1st 2017 in Sydney, the clocks went forward to 3am. If you were looking at a clock every minute you'd see:
- 01:58
- 01:59
- 03:00
- 03:01
So any date/time between 2am (inclusive) and 3am (exclusive) simply didn't occur in that time zone. We don't know what produced the values you're trying to parse, but:
- If they're timestamps, it would almost certainly be better to both format and parse in UTC. Keep an offset from UTC and potentially a time zone ID if the time zone in which they were produced is important for future analysis.
- If they're date/time values which aren't linked to any particular time zone, don't parse them as if they were in a time zone. Ideally, use Java 8's
java.time
package and parse them asLocalDateTime
values
回答2:
tl;dr
LocalDateTime.parse( // Parse a string lacking any indicator of time zone or offset-from-UTC into a `LocalDateTime` object.
"201710010200" , // Parse string in “basic” ISO 8601 format.
DateTimeFormatter.ofPattern( "uuuuMMddHHmm" ) // Define a formatting pattern to match the input. If you used expanded ISO 8601 format instead of “basic” you would not need to bother with this step.
).atZone( // Place the inexact unzoned value (`LocalDateTime` object) into the context of a time zone. Produces a `ZonedDateTime` object.
ZoneId.of( "Australia/Sydney" )
).toString() // Generate a string in standard expanded ISO 8601 format. This class extends the standard to wisely append the name of the zone in square brackets.
2017-10-01T03:00+11:00[Australia/Sydney]
If that time-of-day on that date in than zone is invalid, ZonedDateTime
class adjusts.
If you fear faulty input, trap for DateTimeParseException.
Details
The Answer by Jon Skeet is correct. Per his comment: There is no instant in time which had a local time of 2am on October 1st 2017 in Sydney.
java.time
The modern solution is the java.time classes that supplant the troublesome old legacy date-time classes.
Parsing
Define a formatting pattern to match your input.
String input = "201710010200" ;
DateTimeFormatter f = DateTimeFormatter.ofPattern( "uuuuMMddHHmm" ) ;
LocalDateTime
Your input lacks an indicator of offset-from-UTC or time zone. So parse as a LocalDateTime
.
LocalDateTime ldt = LocalDateTime.parse( input , f ) ;
ldt.toString(): 2017-10-01T02:00
Without the context of a time zone or offset-from-UTC, this value has no real meaning. It does not represent a moment, a point on the timeline. It is only a vague idea about possible moments over a range of about 26-27 hours.
To determine a moment, we need to place this value within the context of a time zone or offset.
You claim to know that value was intended to represent a moment in Australia/Sydney
time zone.
ZoneId z = ZoneId.of( "Australia/Sydney" ) ;
2017-10-01T03:00+11:00[Australia/Sydney]
The result is 3 AM. The offset-from-UTC used by the people of that region changed on that date at that time, a cut-over in Daylight Saving Time (DST) silliness. When the clock in that part of Australia was about to strike 2 AM, the clock jumped to 3 AM. The 2 AM hour never existed in that land. The ZonedDateTime class has a policy for automatically adjusting such invalid moment. Be sure to read the doc to see if you understand enter link description hereand agree with its algorithm.
Because this wall-clock time never existed, I suspect you are incorrect about that log data representing a moment in the zone of Australia/Sydney
.
UTC
The bigger solution here is to learn to think, work, log, and exchange data all in UTC. Consider UTC to be the One True Time, and all other zones are but a mere variation of that theme. Forget about your own parochial time zone while at work programming/administrating.
In java.time, the basic class for UTC values is Instant
. The Instant class represents a moment on the timeline in UTC with a resolution of nanoseconds (up to nine (9) digits of a decimal fraction).
This class can capture the current moment in UTC.
Instant instant = Instant.now() ; // Capture the current moment in UTC.
ISO 8601
The Instant
class also can parse/generate strings in standard ISO 8601 format.
String output = Instant.now().toString() ;
"2018-01-23T12:34:56.734591Z"
The Z
on the end is short for Zulu
and means UTC. This is the crux of your original problem: You stored date-time values without such an indicator of zone/offset.
Parsing:
Instant instant = Instant.parse( "2018-01-23T12:34:56.734591Z" )
I strongly recommending using the ISO 8601 formats for all such storage/exchange of textual date-time values. Be sure to:
- include offset/zone when storing/exchanging moments, precise points on the timeline.
- Use expanded formats, rather than these “basic” formats that minimize the use of delimiters. These are hard to read by humans, less obvious as to the type of information, and are not used by default in java.time. The java.time classes use the expanded formats by default.
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
Using a JDBC driver compliant with JDBC 4.2 or later, you may exchange java.time objects directly with your database. No need for strings nor java.sql.* classes.
Where to obtain the java.time classes?
- Java SE 8, Java SE 9, and later
- Built-in.
- Part of the standard Java API with a bundled implementation.
- Java 9 adds some minor features and fixes.
- Java SE 6 and Java SE 7
- Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
- Android
- Later versions of Android bundle implementations of the java.time classes.
- For earlier Android, the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
来源:https://stackoverflow.com/questions/46581387/dst-changes-caused-an-java-text-parseexception-unparseable-date