Java Spring deserializing ISO Zulu time error - java

I am trying to parse dates with the format "yyyy-MM-dd'T'HH:mm:ssZ". I have a controller with a POST endpoint which takes an object. In that object is a date field of object type "Instant". it has the following annotations:
#JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssZ")
#JsonSerialize(using = InstantSerializer.class)
#JsonDeserialize(using = DefaultInstantDeserializer.class)
private Instant endTime;
I had to create the DefaultInstantDeserializer because using the provided one gave me an error because InstantDeserializer (from the jackson-datatype library) has no no-args constructor.
This is that class
public class DefaultInstantDeserializer extends InstantDeserializer<Instant> {
public DefaultInstantDeserializer() {
super(Instant.class, DateTimeFormatter.ISO_INSTANT,
Instant::from,
a -> Instant.ofEpochMilli(a.value),
a -> Instant.ofEpochSecond(a.integer, a.fraction),
null,
true // yes, replace zero offset with Z
);
}
}
using Postman, I sent the object, with that field being 2022-02-08T15:22:24+00:00
I get this error from in the logs from Spring:
Failed to deserialize java.time.Instant: (java.time.format.DateTimeParseException) Text '2022-02-08T15:14:28+00:00' could not be parsed at index 19\n
It looks like it cannot parse the time starting at +00:00. I thought the Z indicated Zulu time/ GMT/ UTC time, which is a 0 offset. Is "yyyy-MM-dd'T'HH:mm:ssZ" not a valid date format? My requirements say I must use that pattern. In Postman the pattern I am using to send the data is YYYY-MM-DDTHH:mm:ssZ. There is some disconnect between the 0 offset and the Z value, I thought the deserializer would take care of that, but I think not. Any suggestions?
Thanks

Related

How can we dynamically change the timezone in java rest api response?

We have api: call_summary/
{
"id": 2,
"number: "xyz",
"call_time": "2021-10-11T03:50:23Z"
}
We have multiple users with various timezones like ADT, EDT, IST, etc. When users access this API the call_time should change according to user timezone. I tried to use #JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "IST"), but this won't allow us to change the call_time dynamically.
Is there any way to do it using annotations or filters?
I would recommend storing call_time in two columns, UTC and users local time-zone.
By doing so, it will eliminate complexity and confusion at both ends (server and client)
Check the following link, it may help you: Pass browser timezone to the backend springboot application to generate reports with dates as per the browser timezone. According to the latter, you can use TimeZone as input to your controller. You could do something like the following:
#RestController
public class TestController {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
#GetMapping(value = "/test")
public String generate(TimeZone timezone) throws IOException {
return LocalDateTime.now().atZone(timezone.toZoneId()).format(formatter);
}
}
Alternatively, you could get the timezone from HttpServletRequest.
JSON Serialize is the best fit for this custom data conversion as per user or user role. I created the converter class and called by #JsonSerialize(converter = LocalDateTimeToStringConverter.class) and #JsonDeserialize(converter = StringToLocalDatetimeConverter.class). It worked as per my expection.
For reference, I attached the sample link below.
Custom conversion using JSON-Serialize-Deserialize

deserializing error with date - JSON decoding error

I am getting a response from an api and it is failing to deserialize a date which looks like this "2021-09-10T21:48:35.352+0000". I have set up attribute in my class like below
#JsonProperty("processedAt")
private LocalDateTime processedAt;
org.springframework.core.codec.DecodingException: JSON decoding error: Cannot deserialize value of type java.time.LocalDateTime from String "2021-09-10T21:48:35.352+0000": Failed to deserialize java.time.LocalDateTime: (java.time.format.DateTimeParseException) Text '2021-09-10T21:48:35.352+0000' could not be parsed, unparsed text found at index 23;
what format of date should I be using in class to get the date out and not fail on deserializing. thanks in advance
By default Jackson does not support this date time format pattern. Therefore you need to explicitly mention that using #JsonFormat annotation. Like this,
#JsonProperty("processedAt")
#JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ")
private LocalDateTime processedAt;
Looking at your datetime format, you should probably use OffsetDateTime instead of LocalDateTime. Otherwise your timezone will be ignored (If you local timezone is +0000, then LocalDateTime should be fine).
#JsonProperty("processedAt")
#JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ")
private OffsetDateTime processedAt;
Update
If you use OffsetDateTime, then you don't need to explicitly mention the pattern using #JsonFormat annotation. Something like this sufficient.
#JsonProperty("processedAt")
private OffsetDateTime processedAt;
I did not check but you are using LocalDateTime and are trying to deserialize a ZonedDateTime. Maybe change the type?
That is also in agreement with the error message that states that the error is at position 23. Exactly where the unexpected zone information starts.

Unable to obtain OffsetDateTime from String

I have to obtain OffsetDateTime from a string value
"2008-11-15T17:52:58"
I have tried various ways but it gives this error. Please look at the below code snippet and error and provide the comments.
First way to try :
ZonedDateTime.parse("2008-11-15T17:52:58", DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")).toOffsetDateTime();
Second try :
OffsetDateTime.parse("2014-06-09T17:15:04+02:00", DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ"));
I am getting this error.
Text '2008-11-15T17: 52: 58' could not be parsed at index 19
Your pattern yyyy-MM-dd'T'HH:mm:ss.SSSZ does not include zone offset to parse +02:00. This format looks like RFC-3339 which is already pre-created in DateTimeFormatter.ISO_DATE_TIME
If you need OffsetDateTime while your input has no offset, you should ask yourself what offset to use.
There is parseBest method in DateTimeFormatter that can try multiple temporal queries and gave you back what it found:
Stream.of("2008-11-15T17:52:58", "2014-06-09T17:15:04+02:00").map(s -> {
return DateTimeFormatter.ISO_DATE_TIME.parseBest(s,
OffsetDateTime::from, LocalDateTime::from);
}).forEach(ta -> {
System.out.println("Type : " + ta.getClass());
System.out.println("Value : " + ta.toString());
});
Here is output:
Type : class java.time.LocalDateTime
Value : 2008-11-15T17:52:58
Type : class java.time.OffsetDateTime
Value : 2014-06-09T17:15:04+02:00
As you can see, your first input 2008-11-15T17:52:58 has no offset info so it can't be parsed into OffsetDateTime. You can easily detect that and call .atOffset(...) if you know what offset to use
Here is modified version to detect missing offset and convert LocalDateTime to OffsetDateTime (if you know what offset to use)
Stream.of("2008-11-15T17:52:58", "2014-06-09T17:15:04+02:00").map(s -> {
return DateTimeFormatter.ISO_DATE_TIME.parseBest(s,
OffsetDateTime::from, LocalDateTime::from);
}).forEach(ta -> {
final OffsetDateTime odt;
if (ta instanceof OffsetDateTime) {
odt = (OffsetDateTime) ta;
} else {
//here is 2-hour offset hardcoded. If you need OffsetDateTime
//you should also know offset somehow
odt = ((LocalDateTime) ta).atOffset(ZoneOffset.ofHours(2));
}
System.out.println("Type : " + odt.getClass());
System.out.println("Value : " + odt.toString());
});
Output
Type : class java.time.OffsetDateTime
Value : 2008-11-15T17:52:58+02:00
Type : class java.time.OffsetDateTime
Value : 2014-06-09T17:15:04+02:00
If you can get your string like 2014-06-09T17:15:04+02:00, then it contains a UTC offset, here +02:00, that is 2 hours 0 minutes. In this case you’re set:
OffsetDateTime parsedDateTime = OffsetDateTime.parse("2014-06-09T17:15:04+02:00");
System.out.println(parsedDateTime);
2014-06-09T17:15:04+02:00
We don’t even need to specify a formatter. The string is in ISO 8601 format, and OffsetDateTime and the other classes of java.time parse the most common ISO 8601 variants as their default.
If you get the string like 2008-11-15T17:52:58, the offset is missing, so it cannot be converted directly to an OffsetDateTime since, as the name says, an OffsetDateTime includes an offset. You cannot assign first and last name to a person if you are only told the first name.
If you know that some known time zone was understood and intended, you may convert to anOffsetDateTime, though. If you know the familiy name of a person’s family, maybe you can assume that it is also this person’s last name? It’s probably easier to understand if we make the conversion explicit in the code. We first parse into a LocalDateTime. Local in some of the class names of java.time means “without time zone or offset”, so this is the right class for your string. And the format is still ISO 8601, so we still don’t need the formatter.
OffsetDateTime calculatedDateTime = LocalDateTime.parse("2008-11-15T17:52:58")
.atZone(ZoneId.of("Asia/Karachi"))
.toOffsetDateTime();
System.out.println(calculatedDateTime);
2008-11-15T17:52:58+05:00
The conversion takes any summer time (DST) and other time changes in the specified time zone into account.
What went wrong in your code?
ZonedDateTime.parse("2008-11-15T17:52:58", DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")).toOffsetDateTime();
Your pattern yyyy-MM-dd'T'HH:mm:ss.SSSZ requires that your string contains three decimals of fraction on the seconds and a UTC offset without colon, for example 2008-11-15T17:52:58.000+0200. One way to see this is to use the formatter for formatting and printng the result:
System.out.println(ZonedDateTime.now(ZoneId.of("Asia/Kolkata"))
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")));
2021-02-13T02:59:04.324+0530
Since the string you tried to parse neither included fraction of second nor offset, parsing failed.
Your second attempt had the same problem. There still was no fraction of second in your string. This time there was an offset, but with colon between hours and minutes, which a single Z does not match. You might have used xxx for such an offset.
Link
Wikipedia article: ISO 8601

How to convert an Epoch timestamp into an object of type java.time.Instant Accurately in Java?

I have a Spring Boot JPA Application that interacts with a 3rd party API.
The response payload of the API has a key
"created_at": 1591988071
I need to parse this field into java.time.Instant so that I can do some comparisons with the value I have in the Database.
I have learned that I can use the below mentioned piece of code.
Instant instant = Instant.ofEpochSecond(1591988071);
Output :
2020-06-12T18:54:31Z
But to be honest, this output is off by a couple of hours.
I found another approach, wherein if I use
String dateAsText = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
.format(new Date(1591988071 * 1000L));
System.out.println(dateAsText);
I get the desired output but in String format.
2020-06-13 00:24:31
Can someone tell me how to obtain the above String output but converted into type java.time.Instant ?
It is likely you're in a different timezone than UTC. The instant is giving you time in UTC. That's indicated by the Z at the end of your first output.
You would want to look at atZone
Instant instant = Instant.ofEpochSecond(1591988071);
System.out.println(instant);
final ZonedDateTime losAngeles = instant.atZone(ZoneId.of("America/Los_Angeles"));
System.out.println(losAngeles);
final ZonedDateTime mumbai = instant.atZone(ZoneId.of("UTC+0530"));
System.out.println(mumbai);
This gives you something you might expect
2020-06-12T18:54:31Z
2020-06-12T11:54:31-07:00[America/Los_Angeles]
2020-06-13T00:24:31+05:30[UTC+05:30]

How do I convert an ISO 8601 date to DateTime type in ActiveJDBC?

The toJson method gives as result a date type with format ISO 8601.
With console output, the someEntityInstance.toString() method produces:
{Valid=true, Activation_Date=2018-02-11 05:00:00.0, Creation_Date=2018-02-11 05:00:00.0}
But with the someEntityInstance.toJson(false) the date is converted to ISO 8601:
{Valid=true, Activation_Date=2018-02-11T05:00:00Z, Creation_Date=2018-02-11T05:00:00.0Z}
And when I try to save the object it generates an exception:
"com.mysql.jdbc.MysqlDataTruncation: Data truncation: Incorrect datetime value: '2018-02-11T05:00:00Z' for column 'Activation_Date'
What do I need to do to automatically convert the generated date to the standard DateTime format?
ActiveJDBC is a Pass-through framework. This means that the data type you set on a model:
model.set("activation_date", date);
and whatever you read from the database:
YourModel m = YourModel.findById(1);
Object activationCode = m.get("activation_code");
can be different.
Whenever you read something from the database, your attributes have types returned by the driver. However, when you set a value, you set the type too, and it is up to the driver to convert the type you set to the underlying DB type.
I have a feeling that the two examples you have are from different life-cycles of your model. In order to guard from this, you can write typed setters:
public class YourModel extends Model{
public YourModel setActivactionDate(java.sql.Timestamp t){
set("activation_date", t);
}
}
UPDATE: the toJson method does in fact convert to ISO format:
https://github.com/javalite/activejdbc/blob/master/activejdbc/src/main/java/org/javalite/activejdbc/Model.java#L1047
What you can do is to add a date or timestamp converter:
http://javalite.io/data_conversions#converters

Categories