I'm updating my code to use java.time.format.DateTimeFormatter
instead of java.text.SimpleDateFormat
and now a test fails which didn't before. When I parse the String
2010-12-31 00:00:00
, which in my understanding is in format
yyyy-MM-dd HH:mm:ss
I get
2010-12-31T00:00
where the seconds are missing. This is the problem, because for example creating an Instant
from it fails with
java.time.DateTimeException: Unable to obtain Instant from TemporalAccessor: 2010-12-31T00:00 of type java.time.LocalDateTime
but regardless, I need the seconds too in the parsing result. I don't understand why they are missing.
I believe that the format I use, yyyy-MM-dd HH:mm:ss
, is correct as per the JavaDoc of DateTimeFormatter
. I see it in many examples, like https://mkyong.com/java8/java-8-how-to-parse-date-with-localdatetime/
I tried the same pattern with java.text.SimpleDateFormat
, expecting it to fail too because I hoped I had made some mistake in the pattern, but it succeeded.
I tried with a pattern where I actually removed the seconds, that is yyyy-MM-dd HH:mm
, expecting that the result was different from before, instead both patterns produce the same result.
I tried replacing with a T
the space before the time:
2010-12-31T00:00:00
but the parsing results in
java.time.format.DateTimeParseException: Text '2010-12-31T00:00:00' could not be parsed at index 10
This runnable example illustrates the issue:
package main;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class Test_yyyy_MM_dd_HH_mm_ss {
public static void main(String[] args) {
String toParse, pattern; LocalDateTime parsed;
//
toParse = "2010-12-31 00:00:00";
pattern = "yyyy-MM-dd HH:mm:ss";
parsed = LocalDateTime.parse(toParse, DateTimeFormatter.ofPattern(pattern)); // Gives "2010-12-31T00:00", why ?
System.out.println("Parsing \"" + toParse + "\" with pattern \"" + pattern + "\" returns " + parsed);
//
toParse = "2010-12-31 00:00";
pattern = "yyyy-MM-dd HH:mm";
parsed = LocalDateTime.parse(toParse, DateTimeFormatter.ofPattern(pattern)); // Gives "2010-12-31T00:00"
System.out.println("Parsing \"" + toParse + "\" with pattern \"" + pattern + "\" returns " + parsed);
//
toParse = "2010-12-31T00:00:00";
pattern = "yyyy-MM-dd HH:mm:ss";
parsed = LocalDateTime.parse(toParse, DateTimeFormatter.ofPattern(pattern)); // DateTimeParseException: Text '2010-12-31T00:00:00' could not be parsed at index 10
}
The output is:
Parsing "2010-12-31 00:00:00" with pattern "yyyy-MM-dd HH:mm:ss" returns 2010-12-31T00:00
Parsing "2010-12-31 00:00" with pattern "yyyy-MM-dd HH:mm" returns 2010-12-31T00:00
Exception in thread "main" java.time.format.DateTimeParseException:
Text '2010-12-31T00:00:00' could not be parsed at index 10
You're confused - your code indicates you fundamentally misunderstand a few things.
For example, it's parsing just fine.
Your error lies in appending the expression parsed
to a string and expecting that to be a sensible operation.
LocalDateTime
represents time in human reckoning terms. They always have seconds. They can't not. They don't have a format either. They are just a fairly simplistic, "dumb" vehicle for the fields int year, month, day, hour, minute, second
and no more than that.
They do not have a format. They do not know how to print themselves in a useful fashion. They have a toString() implementation which is what you get and which is evidently confusing you.
The fix is this: Never call the toString()
, if you want to render one to human eyeballs, you throw them through a DateTimeFormatter
with the format
method.
Replace the + parsed
in System.out.println("Parsing \"" + toParse + "\" with pattern \"" + pattern + "\" returns " + parsed);
With DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(parsed)
instead.
A combo of year/month/day/hour/minute/second cannot be converted to an instant. Period. The fact that you said 'I want to do that' is therefore ringing the alarm bells.
LDTs don't represent a moment in time. Instants only represent a moment in time. You can't convert an apple into a pear.
This is the usual way to get there:
someLocalDateTimeObject.atZone(ZoneId.of("Europe/Amsterdam"))
- this gets you a ZonedDateTime
.Instant
.There are hacky ways that you should never do because the above is fundamentally how it works. Think about it: If I tell you: "Please tell me the exact Instant, in epoch-millis-in-UTC, that matches the moment "24 minutes past 3 PM, on June 25th, 2025"?
If you answer that question you are wrong. Because the only correct response to my request is ".. I do not know. What time zone are you in?". A time zone is fundamentally a part of this conversion - and therefore in your code you should make that clear. If you want to go with the system default timezone, fantastic - but you still write this in your code so everybody that reads this sees that this is happening. For example, call .atZone(ZoneId.systemDefault())
.
yyyy
is almost never what you want. You want uuuu
instead. (in yyyy
, the year before the year '1' is the year '-1'. That means doing 'math' with years BC and years AD will be off by 1. uuuu
says the year before '1' is the year '0'. This isn't equal to the year in 'BC' style (there is no 0AD and 0 BC; the year before the year 1 AD is the year 1 BC. "Before Christ" isn't referring to a whole year, it's referring to a single moment. It's.. confusing, I guess). It tends not to matter, except when it does. Best to just get in the habit of never using yyyy
. Use uuuu
. But, whatever you do, NEVER use YYYY
, that is dangerous. It's a bug that tests tend not to find (YYYY is weekyear. Which matches year except on certain days very close to jan 1st, hence why tests won't find it).
Generally writing patterns is surprisingly full of landmines you can step on. Hence, it's usually a much better plan to either use one of the constants defined in the DateTimeFormatter class, or to use the .ofLocalizedDateTime
methods.
Also, you should get in the habit of always explicitly passing a Locale when making DTF objects. Different cultures have different styles of writing dates and times. If you have a specific style in mind, you should make sure the code contains this assumption in writing.