Java Instant.parse cannot validate date - java

I have an instant field and trying to validate the values in this field using the following approach:
Instant.parse("2021-09-29 09:35:07.531")
However, it throws the error:
java.time.format.DateTimeParseException: Text '2021-09-29
09:35:07.531' could not be parsed at index 10.
So, how can I test if the given Instant date in String format is a valid Instant date?
I am implementing an Import feature and I need to check the date cells. So, the date is normally kept in Instant format in db and for this reason I need to check if the cell date value is a valid Instant date. So, if there is a more proper way, of course I can follow that approach.
I do not use the value, I just need to check if the date is a valid date.

Your Date-Time string does not have timezone information and therefore you can not parse it into an Instant without introducing a timezone. I recommend you parse it into LocalDateTime and use the same for DB work if it is supposed to be used independent of timezones.
Demo:
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class Main {
public static void main(String args[]) {
DateTimeFormatter dtfInput = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss.SSS", Locale.ENGLISH);
LocalDateTime ldt = LocalDateTime.parse("2021-09-29 09:35:07.531", dtfInput);
System.out.println(ldt);
}
}
Output:
2021-09-29T09:35:07.531
ONLINE DEMO
How to use LocalDateTime in JDBC?
Given below is a sample code to insert a LocalDateTime into columnfoo (which is of TIMESTAMP type):
PreparedStatement st = conn.prepareStatement("INSERT INTO mytable (columnfoo) VALUES (?)");
st.setObject(1, ldt);
st.executeUpdate();
st.close();
Given below is a sample code to retrieve a LocalDateTime from columnfoo:
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery("SELECT * FROM mytable WHERE <some condition>");
while (rs.next()) {
// Assuming the column index of columnfoo is 1
LocalDateTime ldt = rs.getObject(1, LocalDateTime.class));
System.out.println(ldt);
}
rs.close();
st.close();
In case you want to parse the given Date-Time string into Instant:
As described above, you need to introduce a timezone in order to parse it into an Instant.
Demo:
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class Main {
public static void main(String args[]) {
// Change the ZoneId to the applicable one e.g. ZoneId.of("Etc/UTC")
DateTimeFormatter dtfInput = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss.SSS", Locale.ENGLISH)
.withZone(ZoneId.systemDefault());
Instant instant = Instant.from(dtfInput.parse("2021-09-29 09:35:07.531"));
System.out.println(instant);
}
}
Output in my timezone, Europe/London:
2021-09-29T08:35:07.531Z
ONLINE DEMO
Learn more about the modern Date-Time API* from Trail: Date Time.
* If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring. Note that Android 8.0 Oreo already provides support for java.time.

A reference to the time zone is mandatory if you are trying to use the Instant class. So, let's try this:
LocalDateTime.from(DateTimeFormatter.ofPattern("yyyy-MM-dd H:mm:ss.SSS").parse("2021-09-29 09:35:07.351")).atZone(ZoneId.systemDefault()).toInstant();

So, how can I test if the given Instant date in String format is a
valid Instant date?
It is not.
An instant is a (unique) point in time. Your string holds a date and time of day. Without knowing the time zone this may denote some point within a range of 24 or 27 hours — so pretty far from being one point in time.
Edit: I understand that you are importing the string from somewhere, that you cannot decide the format or content of the string, and you need to validate it. You can validate it as a date and time. You basically cannot convert it to an Instant, or at least you only can under assumptions the validity of which I can’t know. For validation I suggest this formatter:
private static final DateTimeFormatter PARSER = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.appendLiteral(' ')
.append(DateTimeFormatter.ISO_LOCAL_TIME)
.toFormatter(Locale.ROOT);
The formatter reuses built-in formatters and accepts a variable number of decimals in the seconds, which I think will make sense in many contexts. You know better whether it does in yours.
Validate like this:
String importedDateTimeString = "2021-09-29 09:35:07.531";
try {
LocalDateTime.parse(importedDateTimeString, PARSER);
System.out.format("Valid date and time: %s%n", importedDateTimeString);
} catch (DateTimeParseException dtpe) {
System.out.format("Not a valid date and time: %s. Validation error: %s%n",
importedDateTimeString, dtpe);
}
Output:
Valid date and time: 2021-09-29 09:35:07.531
Original recommendation: So instead use a format that includes offset from UTC. In particular the ISO 8601 format for an instant in UTC is a recommended option for several purposes, like 2021-09-29T01:35:07.531Z.
Link: Wikipedia article: ISO 8601

Related

Issue in fetching Local time with Java 8 Time API with particular pattern of date

I am having difficulty in finding the current time in some pattern with JAVA 8 time api.
I am trying below code for this, my goal is to find time in this pattern only -> yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
LocalDate localDate = LocalDate.now();
String localDateString = localDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"));
System.out.println(localDateString);
I am getting below issue :
Exception in thread "main" java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: HourOfDay
at java.time.LocalDate.get0(LocalDate.java:680)
at java.time.LocalDate.getLong(LocalDate.java:659)
at java.time.format.DateTimePrintContext.getValue(DateTimePrintContext.java:298)
at java.time.format.DateTimeFormatterBuilder$NumberPrinterParser.format(DateTimeFormatterBuilder.java:2551)
at java.time.format.DateTimeFormatterBuilder$CompositePrinterParser.format(DateTimeFormatterBuilder.java:2190)
at java.time.format.DateTimeFormatter.formatTo(DateTimeFormatter.java:1746)
at java.time.format.DateTimeFormatter.format(DateTimeFormatter.java:1720)
at java.time.LocalDate.format(LocalDate.java:1691)
Can anyone please help me on this ?
Instant.now().toString()
Your Question is confused. But I will guess that your goal is getting the current moment as seen in UTC. If so, you are working too hard.
Instant instant = Instant.now() ;
Create text in standard ISO 8601 format.
String output = instant.toString() ;
2022-01-06T12:34:56.123456Z
The Z on the end means +00:00, an offset from UTC of zero hours-minutes-seconds. Pronounced “Zulu”.
Parse text in standard ISO 8601 format.
Instant instant = Instant.parse( "2022-01-06T12:34:56.123456Z" ) ;
As you can see in the example code, there is no need to specify a formatting pattern. The java.time classes use ISO 8601 formats by default when parsing/generating text.
As #Chaosfire mentioned LocalDate instances do not contain any time part. Thus it can convert itself to a pattern that has time in it.
You either use LocalDateTime to get the exact time or if you are not bothered about time i.e it can be 00:00:00 for each instance then you can use LocalDate.atStartOfDay().
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS");
LocalDate localDate = LocalDate.now();
String localDateString = localDate.atStartOfDay(ZoneOffset.UTC).format(formatter);
//Output: 2022-06-06T00:00:00.000
You are trying to format a date like a date-time. A date obviously does not have hour of day, minute, etc. Are you perhaps trying to use LocalDateTime?
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
public class Temp {
public static void main(String[] args) throws Exception {
LocalDateTime now = LocalDateTime.now(ZoneOffset.UTC);
String localDateString = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS"));
System.out.println(localDateString);
}
}

How to format Date object to match #JsonFormat?

I have a json formatted response Date to the controller that is like this:
#JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = "America/Chicago")
private Date date;
So when I make a post call, it would look like:
"date": "2021-08-20 14:17:43"
So the response string would look something like this {"date":"2021-05-21 14:23:44"}. In JUnit, I am manually creating a response object and setting the Date object so I can then use Gson to turn it into a string and then assert that the two are equal.
I am trying to match this in my SpringMVC JUnit test case by trying to do:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("America/Chicago"));
String formattedDate = sdf.format(new Date());
LocalDate localDate = LocalDate.parse(formattedDate);
Date date = Date.from(localDate.atStartOfDay(ZoneId.of("America/Chicago")).toInstant());
But it is having an error parsing it because of the space between yyyy-MM-dd and HH:mm:ss:
java.time.format.DateTimeParseException: Text '2021-08-20 14:23:44' could not be parsed, unparsed text found at index 10
I think I may be doing this inefficiently, so I was wondering if there was a more simple way to make a Date object that would match the format of #JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = "America/Chicago")
I'm trying to match the response body so it passes via mockito.
Do not mix the modern and the legacy Date-Time API
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class Main {
public static void main(String[] args) {
String strDate = "2021-08-20 14:17:43";
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("u-M-d H:m:s", Locale.ENGLISH);
LocalDateTime ldt = LocalDateTime.parse(strDate, dtf);
System.out.println(ldt);
// Get the required Instant
ZonedDateTime zdtUtc = ldt.atZone(ZoneOffset.UTC);
ZonedDateTime zdtChicago = zdtUtc.withZoneSameInstant(ZoneId.of("America/Chicago"));
Instant instant = zdtChicago.toInstant();
System.out.println(instant);
}
}
Output:
2021-08-20T14:17:43
2021-08-20T14:17:43Z
ONLINE DEMO
java.time
The java.util Date-Time API and their formatting API, SimpleDateFormat are outdated and error-prone. It is recommended to stop using them completely and switch to the modern Date-Time API*. However, for any reason, if you need to convert this object of Instant to an object of java.util.Date, you can do so as follows:
Date date = Date.from(instant);
Learn more about the modern Date-Time API from Trail: Date Time.
* For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.
you could be missing date deserializer
#JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")
#JsonDeserialize(using = LocalDateTimeDeserializer.class)
Posting this to only try and satisfy what you're trying to achieve. But you should follow #Arvind's answer:
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.TimeZone;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("America/Chicago"));
String formattedDate = sdf.format(new Date());
// Updated the lines below
LocalDateTime localDateTime = LocalDateTime.parse(formattedDate, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
Date date = Date.from(localDateTime.atZone(ZoneId.of("America/Chicago")).toInstant());
Best is if you can skip the Date class completely and in your response use Instant or ZonedDateTime from java.time, the modern Java date and time API.
If you cannot avoid using the outdated Date class
… I was wondering if there was a more simple way to make a Date object
that would match the format of #JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = "America/Chicago")
Essential edit: It depends very much on what you mean by match the format. A Date can neither have a format nor a time zone. The string in your JSON has got the format mentioned. The Date has not since this would no be possible. The time zone, America/Chicago, is not present neither in JSON nor in the Date. It is only used for converting between the two. Two Date objects are equal if they denote the same point in time, there is nothing more to it. When you ask about formatting the Date to match the #JsonFormat, this necessarily means formatting into a string.
To convert a string like 2021-08-20 14:23:44 into an old-fashioned Date object I would first define the format and time zone statically:
private static final DateTimeFormatter FORMATTER
= DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss", Locale.ROOT);
private static final ZoneId ZONE = ZoneId.of("America/Chicago");
And then do:
String responseDateString = "2021-08-20 14:23:44";
Instant inst = LocalDateTime.parse(responseDateString, FORMATTER)
.atZone(ZONE)
.toInstant();
Date oldfashionedDate = Date.from(inst);
System.out.println(oldfashionedDate);
Output in my time zone is:
Fri Aug 20 21:23:44 CEST 2021
If I set my time zone to America/Chicago before running, it’s easier to see that the result is correct:
Fri Aug 20 14:23:44 CDT 2021
What went wrong in your code?
First you are correct that formatting a Date into a string only to parse it back is over-complicating things. Second you noticed that your exception came from this line:
LocalDate localDate = LocalDate.parse(formattedDate);
A LocalDate is a date without time of day. So its one-arg parse method expects only 2021-08-20 in the string, nothing more. It was complaining about the space, not because it was a space but just because there were more characters after the expected ones at all.

How to parse UTC timestamp of format yyyy-MM-ddTHH:mm:ss.SSS+ZZZZ

I have timestamp as 2020-12-03T05:35:59.398+0000 in String format being recieved in a streaming batch.
I want only 2020-12-03 05:35:59 as java.sql.Timestamp instance in order to be able to compare it with other Timestamp instance.
Getting following error with Timestamp.valueOf() function:
Exception in thread "main" java.time.format.DateTimeParseException : Text '2020-12-03T05:35:59.398+0000' could not be parsed at index 23
I tried the answer given here , and conversion did happen but the time was changed to 2020-12-03 11:05:59
I have tried changing between the formats given here but still no solution.
Is there even a format for timestamp with wierd + in between 398+0000?
java.time
I recommend that you use java.time, the modern Java date and time API, for your date and time work.
DateTimeFormatter isoFormatter = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSxx");
DateTimeFormatter sqlTimestampFormatter = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.appendLiteral(' ')
.append(DateTimeFormatter.ISO_LOCAL_TIME)
.toFormatter();
String aTimestampString = "2020-12-03T05:35:59.398+0000";
String anotherTimestampString = "2020-12-04 06:43:58.556385";
Instant anInstant = isoFormatter.parse(aTimestampString, Instant::from);
Instant anotherInstant = LocalDateTime.parse(anotherTimestampString, sqlTimestampFormatter)
.atOffset(ZoneOffset.UTC)
.toInstant();
if (anInstant.isBefore(anotherInstant)) {
System.out.println(aTimestampString + " is earlier");
} else {
System.out.println(anotherTimestampString + " is earlier");
}
Output from this example is:
2020-12-03T05:35:59.398+0000 is earlier
The +0000 in the former string above is an offset from UTC — an offset of 00 hours 00 minutes. Since it is zero, we know that the time is in UTC. I don’t know the time zone or UTC offset of the other string. You need to know, or you will get incorrect results. In the code above I have assumed that the other string is in UTC too.
Don’t use Timestamp
I tried the answer given here, and conversion did happen but the time
was changed to 2020-12-03 11:05:59
This is how confusing the Timestamp class is. You got the correct timestamp value. What happens when you print the Timestamp object, is that you are (implicitly or explicitly) calling its toString method. Timestamp.toString() confusingly uses the default time zone of your JVM for rendering the string. So if your timestamp is equal to 2020-12-03T05:35:59.398 UTC and your time zone is, say, Asia/Kolkata, then time is converted to Asia/Kolkata time zone and the string 2020-12-03 11:05:59 is returned and printed.
You have nothing good to use the old-fashioned java.sql.Timestamp class for. It was originally meant for transferring timestamp values with and without time zone to and from SQL databases. Since JDBC 4.2 we prefer OffsetDateTime, Instant and LocalDateTime for that purpose. So just forget about the Timestamp class.
Link
Oracle tutorial: Date Time explaining how to use java.time.
Is there even a format for timestamp with wierd + in between 398+0000?
The 398 part is fraction-of-second (millisecond) while the +0000 part is the zone offset part.
You can parse 2020-12-03T05:35:59.398+0000 into an OffsetDateTime using the format pattern, uuuu-MM-dd'T'HH:mm:ss.SSSX.
Demo:
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
public class Main {
public static void main(String[] args) {
String text = "2020-12-03T05:35:59.398+0000";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSX");
OffsetDateTime odt = OffsetDateTime.parse(text, formatter);
System.out.println(odt);
}
}
Output:
2020-12-03T05:35:59.398Z
Check the DateTimeFormatter documentation page to learn more about the letters used for formatting.
You can use isBefore and isAfter functions of OffsetDateTime to compare its two instances.
Demo:
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
public class Main {
public static void main(String[] args) {
String text = "2020-12-03T05:35:59.398+0000";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSX");
OffsetDateTime odt = OffsetDateTime.parse(text, formatter);
OffsetDateTime odtNow = OffsetDateTime.now();
System.out.println(odtNow.isBefore(odt));
System.out.println(odtNow.isAfter(odt));
}
}
Output:
false
true
Learn more about the modern date-time API at Trail: Date Time. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.
The date-time API of java.util and their formatting API, SimpleDateFormat are outdated and error-prone. It is recommended to stop using them completely and switch to the modern date-time API. Since java.sql.Timestamp extends java.util.Date, it is recommended to stop using that as well. However, for any reason, if you still want to use conversion between the modern and the legacy date-time API, use Instant as the bridge.
import java.sql.Timestamp;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
public class Main {
public static void main(String[] args) {
String text = "2020-12-03T05:35:59.398+0000";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSX");
OffsetDateTime odt = OffsetDateTime.parse(text, formatter);
Instant instant = odt.toInstant();
Timestamp timestamp = new Timestamp(instant.toEpochMilli());
System.out.println(timestamp);
}
}
Output:
2020-12-03 05:35:59.398
You can use a custom DateFormatter for non-standard formats. Here is a working example for your use case.
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import static java.time.temporal.ChronoField.*;
public class Main {
private static final DateTimeFormatter INPUT_NON_STANDARD_FORMAT;
static {
INPUT_NON_STANDARD_FORMAT =
new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.appendLiteral('T')
.appendValue(HOUR_OF_DAY, 2)
.appendLiteral(':')
.appendValue(MINUTE_OF_HOUR, 2)
.optionalStart()
.appendLiteral(':')
.appendValue(SECOND_OF_MINUTE, 2)
.optionalStart()
.appendLiteral('.')
.appendValue(MILLI_OF_SECOND, 3)
.appendLiteral("+0000")
.toFormatter();
}
public static void main(String[] args) {
String timestamp = "2020-12-03T05:35:59.398+0000";
final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss");
LocalDateTime localDateTime = LocalDateTime.parse(timestamp, INPUT_NON_STANDARD_FORMAT);
System.out.println(localDateTime.format(dateTimeFormatter));
}
}
Output
2020-12-03 05:35:59

Get the Gregorian date from a java.sql.Timestamp

i have executed a query with entity manager.
it’s returning data from multiple tables. in that case i am getting list of object arrays. so i have written a loop as below.
for (Object[] row : rows) {
row[0]; //row[0] has the date from database as timestamp.
}
here row[0] is a sql Timestamp.
if we evaluate with debugging, i am getting Timestamp with result: 2020-05-27 18:37:39.0.
i can see cdate, a private variable which has value as 2020-05-27T18:37:39.000+0530. it’s the Gregorian date.
i can't use it because it’s a private variable and it is of type BaseCalendar.Date.
if you do something and convert it in to UTC, i am getting as 2020-05-27T13:07:39Z
BUT i want it as 2020-05-27T18:37:39.000+0530
I too recommend that you use java.time, the modern Java date and time API. If you can, modify your query not to return an old-fashioned Timestamp object but rather an Instant or at least a LocalDateTime.
Then you may do for example:
// Example row
Object[] row = { Instant.parse("2020-05-27T13:07:39.000Z") };
System.out.println(row[0]);
ZonedDateTime zdt = ((Instant) row[0]).atZone(ZoneId.systemDefault());
System.out.println(zdt);
Output from this example is, when running in Asia/Kolkata time zone:
2020-05-27T13:07:39Z
2020-05-27T18:37:39+05:30[Asia/Kolkata]
If you can’t change the return type from your query, the correct conversion is:
// Example row
Object[] row = { Timestamp.from(Instant.parse("2020-05-27T13:07:39.000Z")) };
System.out.println(row[0]);
ZonedDateTime zdt = ((Timestamp) row[0]).toInstant()
.atZone(ZoneId.systemDefault());
System.out.println(zdt);
Output is:
2020-05-27 18:37:39.0
2020-05-27T18:37:39+05:30[Asia/Kolkata]
Please note: Don’t use any strings for the conversion, a more direct conversion exists.
I have assumed that you were after that value, not necessarily after the same format that you saw in your debugger. In case you did want that format: What you saw was the result of BaseCalendar.Date.toString(), and it’s a variant of ISO 8601 format, the international standard. To obtain it, use a formatter. For example:
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.appendLiteral('T')
.appendPattern("HH:mm:ss.SSSxx")
.toFormatter();
String formattedDateTime = zdt.format(formatter);
System.out.println(formattedDateTime);
2020-05-27T18:37:39.000+0530
Links
Oracle tutorial: Date Time explaining how to use java.time.
Wikipedia article: ISO 8601
Use the modern date/time API as follows:
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
public class Main {
public static void main(String args[]) {
// Define the format
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
// Parse the date/time string using the defined format
OffsetDateTime odt = OffsetDateTime.parse("2020-05-27T18:37:39.000+0530", formatter);
// Display
System.out.println(odt);
}
}
You can make use of java.time, which enables you to parse the datetime without considering a zone or offset but provides the possibility of adding one afterwards without changing or adjusting the values parsed:
public static void main(String[] args) {
// let's assume you have a timestamp with the following value
Timestamp ts = Timestamp.valueOf("2020-05-27 18:37:39");
// then you can parse it to a datetime that doesn't consider a zone or offset
LocalDateTime ldt = LocalDateTime.parse(ts.toString(),
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.S"));
// print it once
System.out.println(ldt);
// then add an offset without changing / adjusting the time values
OffsetDateTime odt = ldt.atOffset(ZoneOffset.of("+05:30"));
// and print that
System.out.println(odt);
// or use a default format to exactly meet your requirements
System.out.println(odt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX")));
}
This outputs
2020-05-27T18:37:39
2020-05-27T18:37:39+05:30
2020-05-27T18:37:39.000+0530

What I am doing wrong when parsing an ISO8601 date time?

I am expecting the test below to pass. Could someone tell me what I am doing wrong here? Most likely I am using a wrong pattern but I can't see what is wrong.
#Test
public void parseDateTest() {
DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.sss'Z'");
DateTime dt = formatter.parseDateTime("1983-03-06T05:00:03.000Z");
assertEquals("1983-03-06T05:00:03.000Z", dt.toString());
}
FYI dt.toString() does print: 1983-03-06T05:00:00.000Z
Thanks!
P.S.
Please note that on this snippet I rely on the default timezone. This is not production code and how to handle correctly the timezone based on needs is covered by many other questions.
The default time zone is derived from the system property user.timezone. If that is null or is not a valid identifier, then the value of the JDK TimeZone default is converted. If that fails, UTC is used.
This checks to see if "1983-03-06T05:00:03.000Z" is equal to dt.toString(). You say that dt.toString() is equal to "1983-03-06T05:00:00.000Z".
"1983-03-06T05:00:03.000Z" !== "1983-03-06T05:00:00.000Z"
Now the problem is why dt.toString() does not have the correct amount of seconds. Let's look at your DateTimeFormat pattern:
"yyyy-MM-dd'T'HH:mm:ss.sss'Z'"
According to the docs, s is for "second of minute" and S is for "fraction of second" (note the case). This means in your input string, both 03 and 000 are being parsed as seconds (when the later should be fractions) and your DateTime's seconds are being overridden with 00. Try updating this format string:
#Test
public void parseDateTest() {
DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
DateTime dt = formatter.parseDateTime("1983-03-06T05:00:03.000Z");
assertEquals("1983-03-06T05:00:03.000Z", dt.toString());
}
java.time
Quoted below is a notice from the home page of Joda-Time:
Note that from Java SE 8 onwards, users are asked to migrate to java.time (JSR-310) - a core part of the JDK which replaces this project.
Solution using java.time API: java.time API is based on ISO 8601 and therefore you do not need a DateTimeFormatter to parse a date-time string which is already in ISO 8601 format (e.g. your date-time string, 1983-03-06T05:00:03.000Z).
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
class Main {
public static void main(String[] args) {
String strModifiedDate = "1983-03-06T05:00:03.000Z";
Instant instant = Instant.parse(strModifiedDate);
System.out.println(instant);
// It can also be directly parsed into a ZonedDateTime
ZonedDateTime zdt = ZonedDateTime.parse(strModifiedDate);
System.out.println(zdt);
// or even into an OffsetDateTime
OffsetDateTime odt = OffsetDateTime.parse(strModifiedDate);
System.out.println(odt);
}
}
Output:
1983-03-06T05:00:03Z
1983-03-06T05:00:03Z
1983-03-06T05:00:03Z
Learn more about the modern Date-Time API from Trail: Date Time.
Don't use dt.toString(), use formatter.format(dt) instead. That's what the formatter is for:
#Test
public void parseDateTest() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
LocalDateTime dt = formatter.parse("1983-03-06T05:00:03.000Z", LocalDateTime::from);
assertEquals("1983-03-06T05:00:03.000Z", formatter.format(dt));
}

Categories

Resources