I saw some strange behavior in my production application; org.apache.commons.lang3.time.DateUtils
is throwing a java.text.ParseException
.
So I thought it was some code issue, but it turned out it's only having issues with March 9th, 2025, 2am time stamps. So I tried running some tests and found DateUtils.parseDateWithLeniency
is unable to parse the date but DateUtils.parseDate
is working.
All the dates are working before and after that one hour.
Not sure if it's a bug in the Java Calendar. I observed areAllFieldsSet
in the false
state in the Calendar
class. Does anyone has any insight into this?
Test code tried locally
import java.text.ParseException;
import java.util.Date;
import org.apache.commons.lang3.time.DateUtils;
public class Test11 {
public static void main(String[] args) {
String dateString = "2025-03-09T02:37:51.742";
try {
Date date = DateUtils.parseDateStrictly(dateString,new String[]{
"yyyy-MM-dd'T'HH:mm:ss.SSS"});
System.out.println(date);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
Unable to parse the date: 2025-03-09T02:37:51.742
at org.apache.commons.lang3.time.DateUtils.parseDateWithLeniency(DateUtils.java:391)
at org.apache.commons.lang3.time.DateUtils.parseDateStrictly(DateUtils.java:333)
at org.apache.commons.lang3.time.DateUtils.parseDateStrictly(DateUtils.java:311)
at com.test.Test11.main(Test11.java:17)
A few failed dates for reference:
2025-03-09T02:52:51.261
2025-03-09T02:45:25.032
2025-03-09T02:08:01.013
2025-03-09T02:59:06.913
2025-03-09T02:57:20.466
2025-03-09T02:19:46.928
2025-03-09T02:45:19.957
2025-03-09T02:49:34.463
2025-03-09T02:13:09.896
2025-03-09T02:04:15.99
2025-03-09T02:09:53.982
2025-03-09T02:10:24.47
2025-03-09T02:24:27.696
2025-03-09T02:17:03.064
2025-03-09T02:05:58.489
2025-03-09T02:24:05.871
2025-03-09T02:41:06.302
2025-03-09T02:04:24.478
2025-03-09T02:32:36.959
2025-03-09T02:26:20.384
2025-03-09T02:40:51.959
2025-03-09T02:33:41.93
2025-03-09T02:58:00.669
2025-03-09T02:48:26.187
I tried running the sample strings and observed the issue for a one hour period.
java.time.LocalDateTime // Represents a date with time-of-day but no zone or offset.
.parse ( "2025-03-09T02:37:51.742" ) // Parsing standard ISO 8601 format. Returns a `LocalDateTime` object.
.atOffset ( ZoneOffset.UTC ) // Applies an offset from UTC of zero hours-minutes-seconds. Returns a `OffsetDateTime` object.
You’re using terribly flawed date-time classes that are now legacy, supplanted by the modern java.time classes built into Java 8 and later.
LocalDateTime
Your string input represents a date with time of day, but lacks the context of a time zone or offset from UTC. So, parse as a LocalDateTime
.
LocalDateTime ldt = LocalDateTime.parse ( "2025-03-09T02:37:51.742" ) ;
Be aware that this object does not represent a moment, is not a point on the timeline. Without a zone or offset, we have no way of knowing if this is 2 AM in Tokyo Japan, 2 AM in Toulouse France, or 2 AM in Toledo Ohio US — three different moments several hours apart.
ZonedDateTime
If you are certain your input was meant to represent a moment as seen in a particular time zone, specify that zone to generate a ZonedDateTime
.
ZoneId z = ZoneId.of ( "America/New_York" ) ;
ZonedDateTime zdt = ldt.atZone ( z ) ;
Run this code at Ideone.com.
2025-03-09T03:37:51.742-04:00[America/New_York]
OffsetDateTime
Perhaps your input represents a moment “in UTC”, meaning an offset from UTC of zero hours-minutes-seconds. The constant ZoneOffset.UTC
holds an offset of zero.
For offsets rather than time zone, use OffsetDateTime
class rather than ZonedDateTime
class. (A time zone is a named history of the past, present, and future changes to the offset used by people of a particular region as determined by their politicians.)
OffsetDateTime odt = ldt.atOffset( ZoneOffset.UTC ) ;
Note that your particular date and time is special, at least in the United States and Canada. At 2:00 on that date was the “Spring Ahead” cutover of Daylight Saving Time. The clock jumped from 02:00 🕑 to 03:00 🕒. So there was no two o’clock hour. That March 9th was only 23 hours long.
This DST cutover may explain the problem seen in the Question. The org.apache.commons.lang3.time.DateUtils
utility may have tried to apply such a time zone with such a DST cutover. But that problem is moot as you should no longer be using those classes. And you won’t see such a problem with java.time.
Java comes bundled with a copy of the tzdata set of time zone rules. So the ZonedDateTime
class uses those rules to automatically adjust the hour from 02
to 03
, with an offset of -04:00
rather than -05:00
used in standard time outside of DST.