Jackson date serialization XMLGregorianCalendar incorrectly when time is undefined - kotlin

Currently I am trying to serialize the XMLGregorianCalendar with jackson XML to 2 different formats. One completely filled with date, time and timezone and one filled with only the date. The first one now works correctly and I will just get 2022-03-23T15:30:00.000+00:00 back. Only the second one which should only return the date gives back 2022-03-22T23:00:00.000+00:00 instead of 2022-03-23. I know that you can most likely change it inside the setDateFormat in the jackson config but am not sure how the configuration should look like. Currently the code that I have looks like this:
Jackson config:
val objectMapperXml: ObjectMapper = XmlMapper().apply {
enable(ToXmlGenerator.Feature.WRITE_XML_DECLARATION)
setAnnotationIntrospector(
AnnotationIntrospector.pair(
JakartaXmlBindAnnotationIntrospector(TypeFactory.defaultInstance()),
JacksonXmlAnnotationIntrospector(false)
)
)
disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
}
XMLGregorianCalendar for the dateonly extension:
val ZonedDateTime.toXMLGregorianCalenderDaysOnly: XMLGregorianCalendar
get() {
val cal = GregorianCalendar.from(this)
return DatatypeFactory.newInstance().newXMLGregorianCalendar(cal).apply {
hour = DatatypeConstants.FIELD_UNDEFINED
minute = DatatypeConstants.FIELD_UNDEFINED
second = DatatypeConstants.FIELD_UNDEFINED
millisecond = DatatypeConstants.FIELD_UNDEFINED
timezone = DatatypeConstants.FIELD_UNDEFINED
}
}
For the XMLGregorianCalendar I put all the values that I don't want to see as UNDEFINED. How can I solve this serialization issue with jackson XML?

Related

Kotlin: How to detect 'date change'?

I will use Timer() to execute function by 5 minutes in Kotlin.
And when I execute function by 5m, if a day passed,I want count var to be 0.
So my idea was
declare two vars
var todayDate = LocalDate.now() // 2019-09-23
var todayCount:Int = 0
After that I will check this vars in 5 minutes by using Timer()
Then todayDate value differs from previous todayDate, then I can detect date change.
However, I don't know how to compare current todayDate and previous todayDate.
Any idea? or is there any other way to know day change?
For your specific question about comparing dates you can use the isEqual() method on your LocalDate instance (docs). Something like the following would likely do what you want:
// initial state
var todayDate = LocalDate.now()
var todayCount = 0
// in each timer iteration:
val now = LocalDate.now()
if (!todayDate.isEqual(now)) {
// it's a new day
todayCount = 0
todayDate = now
} else {
// it's the same day
++todayCount
}
However if you're talking about Android and using its Timer class, you need to be aware that that runs on a background thread and you will need to persist your todayDate and todayCount values somewhere (which could be app preferences, your app DB, etc.).

How to get the first value from a list without exceptions in Kotlin?

I have a date value in format "2021-07-14T13:00:00.000+0300" (or similar). I want to convert it to Date. In this case I have to traverse a loop of different formats and check if they fail.
import java.text.*
import java.util.*
val formats = listOf(
"yyyy-MM-dd'T'HH:mm:ss.SSSZ",
"dd.MM.yyyy, EEEE, HH:mm" // And many others.
)
val date = "2021-07-14T13:00:00.000+0300"
val locale = Locale.getDefault()
for (format in formats) {
try {
return SimpleDateFormat(format, locale).parse(date)
} catch (e: ParseException) {
}
}
// If nothing found, return current date.
return Date()
How to convert this for-loop to something like map? So that we can get the first value without exception?
val result = formats.map { ... }
Another option, while still using firstNotNullOfOrNull(), is to use parse() with a ParsePosition object whose properties you can safely ignore when combined with setLenient(false)*.
The advantage of the parse​(String, ParsePosition) version over parse​(String) is that it returns null when it can't parse the date, instead of throwing an error, so the try-catch overhead per iteration can be avoided.
Along with that, since you're defaulting to the current date if all formats fail, you can avoid the nullable Date type result with an Elvis op at the very end.
val result: Date = formats.firstNotNullOfOrNull { format ->
with (SimpleDateFormat(format, locale)) {
setLenient(false) // may not be required, see below
parse(date, ParsePosition(0)) // is null or Date
}
} ?: Date()
Btw, setLenient(false) may not be required because on v15, there's no leniency for SimpleDateFormat.parse() in the docs...but it does behave leniently. Setting it to true above or leaving it out, and parsing a date of "2021-07-14T53:00:00.000+0300" (note the '53') produced Fri Jul 16 02:00:00 UTC 2021. With no leniency, it produces null. The leniency is mentioned on the abstract base class DateFormat.parse(String, ParsePosition) but not for SimpleDateFormat.parse(String, ParsePosition).
So if you're expecting non-pattern-matching dates rather than invalid-but-pattern-matching dates, the above loop could be reduced to:
val result: Date = formats.firstNotNullOfOrNull { format ->
SimpleDateFormat(format, locale).parse(date, ParsePosition(0))
} ?: Date()
Use firstNotNullOfOrNull().
val result: Date? = formats.firstNotNullOfOrNull { format ->
try {
SimpleDateFormat(format, locale).parse(date)
} catch (e: ParseException) {
null
}
}

Compare ISO time in JMETER assertion

I am reading the measurements and I have 2 ISO time stamps.
Now using the JSON extractor, I have parsed the time into 2 variables namely "time1" and "time2"
Now I want to compare the time and decide which one is greater.
Sample time that I had parsed to the variables are like below,
time1: 2021-07-01T00:00:03Z
time2: 2021-07-01T00:00:02Z
Now I want to compare and print the value saying time1 is greater than time2 and the returned response is in descending order.
I tried the below snippet in JSR223:
String time1 = vars.get("time1");
String time2 = vars.get("time2");
OffsetDateTime created = OffsetDateTime.parse(time1);
OffsetDateTime updated = OffsetDateTime.parse(time2);
if (updated.isAfter(created)) {
System.out.println("PASSED");
} else {
System.out.println("FAILED");
}
Working example with import and using log.info
import java.time.OffsetDateTime;
String time1 = vars.get("time1");
String time2 = vars.get("time2");
OffsetDateTime created = OffsetDateTime.parse(time1);
OffsetDateTime updated = OffsetDateTime.parse(time2);
if (updated.isAfter(created)) {
log.info("PASSED");
} else {
log.info("FAILED");
}

Kotlin SimpleDateFormat parse wrong timezone

My mobile timezone was GMT+7, I have a code to convert a specific date time(GMT+0) to a specific timezone(GMT+3):
var strDate = "2020-07-10 04:00:00+0000"
var result: Date?
var dateFormatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ")
dateFormatter.timeZone = TimeZone.getTimeZone("Asia/Jerusalem")
result = dateFormatter.parse(strDate)
The problem is result always return "Fri Jul 10 11:00:00 GMT+07:00 2020"
But I expected it will return date object "Fri Jul 10 07:00:00 GMT+03:00 2020", any idea what's wrong with my code?
It's recommended to use java.time and stop using java.util.Date, java.util.Calendar along with java.text.SimpleDateFormat because of problems like this one.
In your code, the target time zone is obviously not applied to the date but it isn't obvious why it isn't.
A different problem might be pattern you are using because your example String does not contain any unit of time smaller than seconds but the pattern tries to consider .SSS (which made the code fail in the Kotlin Playground).
Switch to java.time and handle this with modern classes, such as OffsetDateTime for parsing this String (it doesn't contain information about a specific time zone, just an offset of zero hours) and ZonedDateTime as the target object (this considers a real time zone which may have different offsets depending things like Daylight Saving Time).
You could do it like this:
import java.time.ZoneId
import java.time.ZonedDateTime
import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter
fun main() {
// this example is in UTC (+0000 --> no offset / offset of 0 hours)
var strDate = "2020-07-10 04:00:00+0000"
// create a formatter that can parse Strings of this pattern
// ([] represents optional units to be parsed)
var dateFormatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss[.SSS]Z")
// and parse the String to an OffsetDateTime using this formatter
var resultOfParsing = OffsetDateTime.parse(strDate, dateFormatter)
// then print the parsed result
println(resultOfParsing)
// create the target time zone
var timeZone = ZoneId.of("Asia/Jerusalem")
// then use the target zone for a zone shift
var jerusalemTime: ZonedDateTime = resultOfParsing.atZoneSameInstant(timeZone)
// and print the result
println(jerusalemTime)
// you could use your formatter defined above for a differently formatted output, too
println(jerusalemTime.format(dateFormatter))
}
which outputs (including all intermediate results):
2020-07-10T04:00Z
2020-07-10T07:00+03:00[Asia/Jerusalem]
2020-07-10 07:00:00.000+0300

How to deserialize dates with offset ("2019-01-29+01:00") to `java.time` related classes?

I've refactored some legacy code within Spring Boot (2.1.2) system and migrated from java.util.Date to java.time based classes (jsr310). The system expects the dates in a ISO8601 formated string, whereas some are complete timestamps with time information (e.g. "2019-01-29T15:29:34+01:00") while others are only dates with offset (e.g. "2019-01-29+01:00"). Here is the DTO (as Kotlin data class):
data class Dto(
// ...
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssXXX")
#JsonProperty("processingTimestamp")
val processingTimestamp: OffsetDateTime,
// ...
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-ddXXX")
#JsonProperty("orderDate")
val orderDate: OffsetDateTime,
// ...
)
While Jackson perfectly deserializes processingTimestamp, it fails with orderDate:
Caused by: java.time.DateTimeException: Unable to obtain OffsetDateTime from TemporalAccessor: {OffsetSeconds=32400},ISO resolved to 2018-10-23 of type java.time.format.Parsed
at java.time.OffsetDateTime.from(OffsetDateTime.java:370) ~[na:1.8.0_152]
at com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer.deserialize(InstantDeserializer.java:207) ~[jackson-datatype-jsr310-2.9.8.jar:2.9.8]
This makes sense to me, since OffsetDateTime cannot find any time information necessary to construct the instant. If I change to val orderDate: LocalDate Jackson can successfully deserialize, but then the offset information is gone (which I need to convert to Instant later).
Question
My current workaround is to use OffsetDateTime, in combination with a custom deserializer (see below). But I'm wondering, if there is a better solution for this?
Also, I'd wish for a more appropriate data type like OffsetDate, but I cannot find it in java.time.
PS
I was asking myself if "2019-01-29+01:00" is a valid for ISO8601. However, since I found that java.time.DateTimeFormatter.ISO_DATE is can correctly parse it and I cannot change the format how the clients send data, I put aside this question.
Workaround
data class Dto(
// ...
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-ddXXX")
#JsonProperty("catchDate")
#JsonDeserialize(using = OffsetDateDeserializer::class)
val orderDate: OffsetDateTime,
// ...
)
class OffsetDateDeserializer(
private val formatter: DateTimeFormatter = DateTimeFormatter.ISO_DATE
) : JSR310DateTimeDeserializerBase<OffsetDateTime>(OffsetDateTime::class.java, formatter) {
override fun deserialize(parser: JsonParser, context: DeserializationContext): OffsetDateTime? {
if (parser.hasToken(JsonToken.VALUE_STRING)) {
val string = parser.text.trim()
if (string.isEmpty()) {
return null
}
val parsed: TemporalAccessor = formatter.parse(string)
val offset = if(parsed.isSupported(ChronoField.OFFSET_SECONDS)) ZoneOffset.from(parsed) else ZoneOffset.UTC
val localDate = LocalDate.from(parsed)
return OffsetDateTime.of(localDate.atStartOfDay(), offset)
}
throw context.wrongTokenException(parser, _valueClass, parser.currentToken, "date with offset must be contained in string")
}
override fun withDateFormat(otherFormatter: DateTimeFormatter?): JsonDeserializer<OffsetDateTime> = OffsetDateDeserializer(formatter)
}
As #JodaStephen explained in the comments, OffsetDate was not included in java.time to have a minimal set of classes. So, OffsetDateTime is the best option.
He also suggested to use DateTimeFormatterBuilder and parseDefaulting to create a DateTimeFormatter instance, to directly create OffsetDateTime from the formatters parsing result (TemporalAccessor). AFAIK, I still need to create a custom deserializer to use the formatter. Here is code, which solved my problem:
class OffsetDateDeserializer: JsonDeserializer<OffsetDateTime>() {
private val formatter = DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_DATE)
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
.parseDefaulting(ChronoField.MILLI_OF_SECOND, 0)
.parseDefaulting(ChronoField.OFFSET_SECONDS, 0)
.toFormatter()
override fun deserialize(parser: JsonParser, context: DeserializationContext): OffsetDateTime? {
if (parser.hasToken(JsonToken.VALUE_STRING)) {
val string = parser.text.trim()
if (string.isEmpty()) {
return null
}
try {
return OffsetDateTime.from(formatter.parse(string))
} catch (e: DateTimeException){
throw context.wrongTokenException(parser, OffsetDateTime::class.java, parser.currentToken, "error while parsing date: ${e.message}")
}
}
throw context.wrongTokenException(parser, OffsetDateTime::class.java, parser.currentToken, "date with offset must be contained in string")
}
}